fix: Full screen editor

- fix: z-index of command box when full screen editor is open.
- fix: templates form
- rename route meta
- updates default status constants.
This commit is contained in:
Abhinav Raut
2025-01-21 02:40:46 +05:30
parent 4dee94949d
commit 561ee1b760
15 changed files with 258 additions and 153 deletions
+1 -1
View File
@@ -12,7 +12,7 @@
height: 100%;
overflow-y: scroll;
padding-bottom: 100px;
@apply bg-slate-50;
@apply bg-accent
}
@layer base {
@@ -3,15 +3,19 @@
<CustomBreadcrumb :links="breadcrumbLinks" />
</div>
<Spinner v-if="isLoading"></Spinner>
<TemplateForm :initial-values="template" :submitForm="submitForm"
:class="{ 'opacity-50 transition-opacity duration-300': isLoading }" :isLoading="formLoading" />
<TemplateForm
:initial-values="template"
:submitForm="submitForm"
:class="{ 'opacity-50 transition-opacity duration-300': isLoading }"
:isLoading="formLoading"
/>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import api from '@/api'
import TemplateForm from './TemplateForm.vue'
import { useRouter } from 'vue-router'
import { useRouter, useRoute } from 'vue-router'
import { CustomBreadcrumb } from '@/components/ui/breadcrumb'
import { Spinner } from '@/components/ui/spinner'
import { handleHTTPError } from '@/utils/http'
@@ -23,6 +27,7 @@ const isLoading = ref(false)
const formLoading = ref(false)
const emitter = useEmitter()
const router = useRouter()
const route = useRoute()
const props = defineProps({
id: {
@@ -42,14 +47,17 @@ const submitForm = async (values) => {
await api.createTemplate(values)
toastDescription = 'Template created successfully'
router.push('/admin/templates')
emitter.emit(EMITTER_EVENTS.REFRESH_LIST, {
model: 'templates'
})
}
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
title: 'Saved',
description: toastDescription,
title: 'Success',
description: toastDescription
})
} catch (error) {
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
title: 'Could not save template',
title: 'Error',
variant: 'destructive',
description: handleHTTPError(error).message
})
@@ -74,10 +82,18 @@ onMounted(async () => {
const resp = await api.getTemplate(props.id)
template.value = resp.data.data
} catch (error) {
console.log(error)
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
title: 'Error',
variant: 'destructive',
description: handleHTTPError(error).message
})
} finally {
isLoading.value = false
}
} else {
template.value = {
type: route.query.type
}
}
})
</script>
@@ -4,31 +4,38 @@
<FormItem v-auto-animate>
<FormLabel>Name</FormLabel>
<FormControl>
<Input type="text" placeholder="Template name" v-bind="componentField" :disabled="!isOutgoingTemplate" />
<Input
type="text"
placeholder="Template name"
v-bind="componentField"
:disabled="!isOutgoingTemplate"
/>
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<div v-if="!isOutgoingTemplate">
<FormField v-slot="{ componentField }" name="subject">
<FormItem>
<FormLabel>Subject</FormLabel>
<FormControl>
<Input type="text" placeholder="Subject for email" v-bind="componentField" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
</div>
<FormField v-slot="{ componentField }" name="subject" v-if="!isOutgoingTemplate">
<FormItem>
<FormLabel>Subject</FormLabel>
<FormControl>
<Input type="text" placeholder="Subject for email" v-bind="componentField" />
</FormControl>
<FormMessage />
</FormItem>
</FormField>
<FormField v-slot="{ componentField, handleChange }" name="body">
<FormItem>
<FormLabel>Body</FormLabel>
<FormControl>
<CodeEditor v-model="componentField.modelValue" @update:modelValue="handleChange"></CodeEditor>
<CodeEditor
v-model="componentField.modelValue"
@update:modelValue="handleChange"
></CodeEditor>
</FormControl>
<FormDescription v-if="isOutgoingTemplate">{{ `Make sure the template has \{\{ template "content" . \}\}` }}
<FormDescription v-if="isOutgoingTemplate">
{{ `Make sure the template has \{\{ template "content" . \}\} only once.` }}
</FormDescription>
<FormMessage />
</FormItem>
@@ -67,7 +74,7 @@ import {
FormDescription
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import CodeEditor from '@/components/common/CodeEditor.vue';
import CodeEditor from '@/components/common/CodeEditor.vue'
import { Checkbox } from '@/components/ui/checkbox'
import { Label } from '@/components/ui/label'
const props = defineProps({
@@ -86,12 +93,13 @@ const props = defineProps({
},
isLoading: {
type: Boolean,
required: false,
required: false
}
})
const form = useForm({
validationSchema: toTypedSchema(formSchema)
validationSchema: toTypedSchema(formSchema),
initialValues: props.initialValues
})
const onSubmit = form.handleSubmit((values) => {
@@ -1,48 +1,48 @@
<template>
<template v-if="router.currentRoute.value.path === '/admin/templates'">
<div class="flex justify-between mb-5">
<div></div>
<div class="flex justify-end mb-4">
<Button @click="navigateToAddTemplate"> New template </Button>
</div>
<template v-if="router.currentRoute.value.path === '/admin/templates'">
<div class="flex justify-between mb-5">
<div></div>
<div class="flex justify-end mb-4">
<Button @click="navigateToNewTemplate" :disabled="templateType !== 'email_outgoing'">
New template
</Button>
</div>
<div>
<Spinner v-if="isLoading"></Spinner>
<Tabs default-value="email_outgoing" v-model="templateType">
<TabsList class="grid w-full grid-cols-2 mb-5">
<TabsTrigger value="email_outgoing">Outgoing email templates</TabsTrigger>
<TabsTrigger value="email_notification">Email notification templates</TabsTrigger>
</TabsList>
<TabsContent value="email_outgoing">
<DataTable :columns="outgoingEmailTemplatesColumns" :data="templates" />
</TabsContent>
<TabsContent value="email_notification">
<DataTable :columns="emailNotificationTemplates" :data="templates" />
</TabsContent>
</Tabs>
</div>
</template>
<template v-else>
<router-view/>
</template>
</div>
<div>
<Spinner v-if="isLoading" />
<Tabs default-value="email_outgoing" v-model="templateType">
<TabsList class="grid w-full grid-cols-2 mb-5">
<TabsTrigger value="email_outgoing">Outgoing email templates</TabsTrigger>
<TabsTrigger value="email_notification">Email notification templates</TabsTrigger>
</TabsList>
<TabsContent value="email_outgoing">
<DataTable :columns="outgoingEmailTemplatesColumns" :data="templates" />
</TabsContent>
<TabsContent value="email_notification">
<DataTable :columns="emailNotificationTemplates" :data="templates" />
</TabsContent>
</Tabs>
</div>
</template>
<template v-else>
<router-view />
</template>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue'
import DataTable from '@/components/admin/DataTable.vue'
import { emailNotificationTemplates, outgoingEmailTemplatesColumns } from '@/components/admin/templates/dataTableColumns.js'
import {
emailNotificationTemplates,
outgoingEmailTemplatesColumns
} from '@/components/admin/templates/dataTableColumns.js'
import { Button } from '@/components/ui/button'
import { useRouter } from 'vue-router'
import { Spinner } from '@/components/ui/spinner'
import { useEmitter } from '@/composables/useEmitter'
import { EMITTER_EVENTS } from '@/constants/emitterEvents.js'
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from '@/components/ui/tabs'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { useStorage } from '@vueuse/core'
import api from '@/api'
@@ -66,6 +66,12 @@ const fetchAll = async () => {
isLoading.value = true
const resp = await api.getTemplates(templateType.value)
templates.value = resp.data.data
} catch (error) {
emit.emit(EMITTER_EVENTS.SHOW_TOAST, {
title: 'Error',
variant: 'destructive',
description: error.message
})
} finally {
isLoading.value = false
}
@@ -75,11 +81,14 @@ const refreshList = (data) => {
if (data?.model === 'templates') fetchAll()
}
const navigateToAddTemplate = () => {
router.push('/admin/templates/new')
const navigateToNewTemplate = () => {
router.push({
name: 'new-template',
query: { type: templateType.value }
})
}
watch(templateType, () => {
fetchAll()
})
}, { immediate: true })
</script>
@@ -14,7 +14,7 @@ import { handleHTTPError } from '@/utils/http'
import api from '@/api'
const router = useRouter()
const emit = useEmitter()
const emitter = useEmitter()
const props = defineProps({
template: {
type: Object,
@@ -32,11 +32,11 @@ const editTemplate = (id) => {
const deleteTemplate = async (id) => {
try {
await api.deleteTemplate(id)
emit.emit(EMITTER_EVENTS.REFRESH_LIST, {
emitter.emit(EMITTER_EVENTS.REFRESH_LIST, {
model: 'templates'
})
} catch (error) {
emit.emit(EMITTER_EVENTS.SHOW_TOAST, {
emitter.emit(EMITTER_EVENTS.SHOW_TOAST, {
title: 'Error',
variant: 'destructive',
description: handleHTTPError(error).message
@@ -10,7 +10,7 @@ export const formSchema = z
}),
type: z.string().optional(),
subject: z.string().optional(),
is_default: z.boolean().optional(),
is_default: z.boolean().optional().default(false),
})
.superRefine((data, ctx) => {
if (data.type !== 'email_outgoing' && !data.subject) {
+14 -19
View File
@@ -1,5 +1,5 @@
<template>
<CommandDialog :open="open" @update:open="handleOpenChange">
<CommandDialog :open="open" @update:open="handleOpenChange" class="z-[51]">
<CommandInput placeholder="Type a command or search..." @keydown="onInputKeydown" />
<CommandList class="!min-h-[400px]">
<CommandEmpty>
@@ -203,9 +203,19 @@ const showDatePicker = ref(false)
const selectedDate = ref(null)
const selectedTime = ref('12:00')
const keys = useMagicKeys()
const cmdK = keys['meta+k']
const ctrlK = keys['ctrl+k']
const { Meta_K, Ctrl_K } = useMagicKeys({
passive: false,
onEventFired(e) {
if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
e.preventDefault()
}
}
})
watch([Meta_K, Ctrl_K], ([mac, win]) => {
if (mac || win) handleOpenChange()
})
const highlightedMacro = ref(null)
function handleApplyMacro(macro) {
@@ -290,31 +300,16 @@ function onInputKeydown(e) {
}
}
function preventDefaultKey(event) {
if ((event.metaKey || event.ctrlKey) && event.key === 'k') {
event.preventDefault()
event.stopPropagation()
return false
}
}
onMounted(() => {
emitter.on(EMITTER_EVENTS.SET_NESTED_COMMAND, (command) => {
setNestedCommand(command)
open.value = true
})
window.addEventListener('keydown', preventDefaultKey)
watchHighlightedMacro()
})
onUnmounted(() => {
emitter.off(EMITTER_EVENTS.SET_NESTED_COMMAND)
window.removeEventListener('keydown', preventDefaultKey)
})
watch([cmdK, ctrlK], ([mac, win]) => {
if (mac || win) handleOpenChange()
})
const watchHighlightedMacro = () => {
@@ -94,7 +94,7 @@ const getSelectionText = (from, to, doc) => doc.textBetween(from, to)
const editorConfig = {
extensions: [
// Lists are unstyled in tailwind, so we need to add classes to them.
// Lists are unstyled in tailwind, so need to add classes to them.
StarterKit.configure({
bulletList: {
HTMLAttributes: {
@@ -136,7 +136,7 @@ const editorConfig = {
const editor = ref(
useEditor({
...editorConfig,
content: textContent.value,
content: htmlContent.value,
onSelectionUpdate: ({ editor }) => {
const { from, to } = editor.state.selection
selectedText.value = getSelectionText(from, to, editor.state.doc)
@@ -251,6 +251,15 @@ onUnmounted(() => {
padding: 10px;
}
.fullscreen-tiptap-editor {
@apply p-0;
.ProseMirror {
min-height: 600px !important;
width: 90%;
scrollbar-width: none;
}
}
.tiptap {
a {
color: #0066cc;
@@ -1,24 +1,77 @@
<template>
<div>
<!-- Fullscreen editor -->
<Dialog :open="isEditorFullscreen" @update:open="isEditorFullscreen = $event">
<DialogContent class="max-w-[70%] max-h-[70%] h-[70%] m-0 p-6">
<div v-if="isEditorFullscreen">
<Editor
v-model:selectedText="selectedText"
v-model:isBold="isBold"
v-model:isItalic="isItalic"
v-model:htmlContent="htmlContent"
v-model:textContent="textContent"
:placeholder="editorPlaceholder"
:aiPrompts="aiPrompts"
@aiPromptSelected="handleAiPromptSelected"
:contentToSet="contentToSet"
v-model:cursorPosition="cursorPosition"
:clearContent="clearEditorContent"
:setInlineImage="setInlineImage"
:insertContent="insertContent"
<Dialog :open="isEditorFullscreen" @update:open="isEditorFullscreen = false">
<DialogContent
class="max-w-[80%] max-h-[80%] h-[80%] w-full m-0 p-6"
@escapeKeyDown="isEditorFullscreen = false"
>
<div v-if="isEditorFullscreen" class="h-full flex flex-col fullscreen-tiptap-editor">
<!-- Message type toggle -->
<div class="flex justify-between px-2 border-b py-2">
<Tabs v-model="messageType">
<TabsList>
<TabsTrigger value="reply"> Reply </TabsTrigger>
<TabsTrigger value="private_note"> Private note </TabsTrigger>
</TabsList>
</Tabs>
<div
class="flex items-center mr-2 cursor-pointer"
@click="isEditorFullscreen = !isEditorFullscreen"
>
<!-- <Minimize2 size="16" /> -->
</div>
</div>
<!-- Main Editor -->
<div class="flex-grow overflow-y-auto">
<Editor
v-model:selectedText="selectedText"
v-model:isBold="isBold"
v-model:isItalic="isItalic"
v-model:htmlContent="htmlContent"
v-model:textContent="textContent"
:placeholder="editorPlaceholder"
:aiPrompts="aiPrompts"
@aiPromptSelected="handleAiPromptSelected"
:contentToSet="contentToSet"
@send="handleSend"
v-model:cursorPosition="cursorPosition"
:clearContent="clearEditorContent"
:setInlineImage="setInlineImage"
:insertContent="insertContent"
class="h-full"
/>
</div>
<!-- Macro preview -->
<MacroActionsPreview
v-if="conversationStore.conversation?.macro?.actions?.length > 0"
:actions="conversationStore.conversation.macro.actions"
:onRemove="conversationStore.removeMacroAction"
/>
<!-- Attachments preview -->
<AttachmentsPreview
:attachments="attachments"
:onDelete="handleOnFileDelete"
v-if="attachments.length > 0"
/>
<!-- Bottom menu bar -->
<ReplyBoxBottomMenuBar
class="mt-1"
:handleFileUpload="handleFileUpload"
:handleInlineImageUpload="handleInlineImageUpload"
:isBold="isBold"
:isItalic="isItalic"
@toggleBold="toggleBold"
@toggleItalic="toggleItalic"
:enableSend="enableSend"
:handleSend="handleSend"
@emojiSelect="handleEmojiSelect"
>
</ReplyBoxBottomMenuBar>
</div>
</DialogContent>
</Dialog>
@@ -235,6 +288,7 @@ const handleInlineImageUpload = (event) => {
}
const handleSend = async () => {
isEditorFullscreen.value = false
try {
// Send message if there is text content in the editor.
if (hasTextContent.value) {
@@ -304,7 +358,13 @@ const handleEmojiSelect = (emoji) => {
watch(
() => conversationStore.conversation.macro,
() => {
contentToSet.value = conversationStore.conversation.macro.message_content
// hack: Quill editor adds <p><br></p> replace with <p></p>
if (conversationStore.conversation?.macro?.message_content) {
contentToSet.value = conversationStore.conversation.macro.message_content.replace(
/<p><br><\/p>/g,
'<p></p>'
)
}
},
{ deep: true }
)
@@ -1,7 +1,7 @@
<template>
<div v-if="conversationStore.current">
<ConversationSideBarContact :conversation="conversationStore.current" class="p-3" />
<Accordion type="multiple" collapsible class="border-t" :default-value="[]">
<Accordion type="multiple" collapsible class="border-t" :default-value="['Actions', 'Information']">
<AccordionItem value="Actions">
<AccordionTrigger class="bg-accent p-2"> Actions </AccordionTrigger>
<AccordionContent class="space-y-5 p-3">
+40 -31
View File
@@ -15,6 +15,7 @@ import {
SidebarGroupContent,
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuSubButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubItem,
@@ -37,7 +38,7 @@ import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu'
import { computed } from 'vue'
import { useUserStore } from '@/stores/user'
@@ -468,11 +469,11 @@ const hasConversationOpen = computed(() => {
<CollapsibleContent>
<SidebarMenuSub>
<SidebarMenuSubItem v-for="child in item.children" :key="child.title">
<SidebarMenuButton :isActive="isActiveParent(child.href)" asChild>
<SidebarMenuSubButton size="sm" :isActive="isActiveParent(child.href)" asChild>
<router-link :to="child.href">
<span>{{ child.title }}</span>
</router-link>
</SidebarMenuButton>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
</SidebarMenuSub>
</CollapsibleContent>
@@ -565,17 +566,17 @@ const hasConversationOpen = computed(() => {
<CollapsibleContent>
<SidebarMenuSub>
<SidebarMenuSubItem>
<SidebarMenuButton
<SidebarMenuSubButton size="sm"
:isActive="isActiveParent('/inboxes/assigned')"
asChild
>
<router-link :to="{ name: 'inbox', params: { type: 'assigned' } }">
<span>My inbox</span>
</router-link>
</SidebarMenuButton>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
<SidebarMenuSubItem>
<SidebarMenuButton
<SidebarMenuSubButton size="sm"
:isActive="isActiveParent('/inboxes/unassigned')"
asChild
>
@@ -584,14 +585,17 @@ const hasConversationOpen = computed(() => {
>
<span>Unassigned</span>
</router-link>
</SidebarMenuButton>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
<SidebarMenuSubItem>
<SidebarMenuButton :isActive="isActiveParent('/inboxes/all')" asChild>
<SidebarMenuSubButton size="sm"
:isActive="isActiveParent('/inboxes/all')"
asChild
>
<router-link :to="{ name: 'inbox', params: { type: 'all' } }">
<span>All</span>
</router-link>
</SidebarMenuButton>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
</SidebarMenuSub>
</CollapsibleContent>
@@ -613,9 +617,9 @@ const hasConversationOpen = computed(() => {
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
<SidebarMenuSubItem v-for="team in userTeams" :key="team.id">
<SidebarMenuButton
<SidebarMenuSub v-for="team in userTeams" :key="team.id">
<SidebarMenuSubItem>
<SidebarMenuSubButton size="sm"
:isActive="isActiveParent(`/teams/${team.id}`)"
asChild
>
@@ -624,7 +628,7 @@ const hasConversationOpen = computed(() => {
>
{{ team.emoji }}<span>{{ team.name }}</span>
</router-link>
</SidebarMenuButton>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
</SidebarMenuSub>
</CollapsibleContent>
@@ -649,16 +653,20 @@ const hasConversationOpen = computed(() => {
</router-link>
</SidebarMenuButton>
</CollapsibleTrigger>
<SidebarMenuAction>
<ChevronRight
class="transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90"
v-if="userViews.length"
/>
</SidebarMenuAction>
<CollapsibleContent>
<SidebarMenuSub>
<SidebarMenuSubItem v-for="view in userViews" :key="view.id">
<SidebarMenuButton
<SidebarMenuSub v-for="view in userViews" :key="view.id">
<SidebarMenuSubItem>
<SidebarMenuSubButton size="sm"
:isActive="isActiveParent(`/views/${view.id}`)"
asChild
>
@@ -667,22 +675,23 @@ const hasConversationOpen = computed(() => {
>
<span>{{ view.name }}</span>
</router-link>
</SidebarMenuButton>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuAction>
</SidebarMenuSubButton>
<SidebarMenuAction>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<EllipsisVertical />
</SidebarMenuAction>
</DropdownMenuTrigger>
<DropdownMenuContent side="right">
<DropdownMenuItem @click="() => editView(view)">
<span>Edit</span>
</DropdownMenuItem>
<DropdownMenuItem @click="() => deleteView(view)">
<span>Delete</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem @click="() => editView(view)">
<span>Edit</span>
</DropdownMenuItem>
<DropdownMenuItem @click="() => deleteView(view)">
<span>Delete</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuAction>
</SidebarMenuSubItem>
</SidebarMenuSub>
</CollapsibleContent>
@@ -6,7 +6,8 @@ import { Dialog, DialogContent } from '@/components/ui/dialog'
const props = defineProps({
open: { type: Boolean, required: false },
defaultOpen: { type: Boolean, required: false },
modal: { type: Boolean, required: false }
modal: { type: Boolean, required: false },
class: { type: String, required: false }
})
const emits = defineEmits(['update:open'])
@@ -15,7 +16,7 @@ const forwarded = useForwardPropsEmits(props, emits)
<template>
<Dialog v-bind="forwarded">
<DialogContent class="overflow-hidden p-0 shadow-lg">
<DialogContent :class="['overflow-hidden p-0 shadow-lg', props.class]">
<Command
class="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5"
>
+3 -8
View File
@@ -6,18 +6,13 @@ export const CONVERSATION_LIST_TYPE = {
ALL: 'all'
}
export const CONVERSATION_VIEWS_INBOXES = {
'assigned': 'My inbox',
'unassigned': 'Unassigned',
'all': 'All',
}
export const CONVERSATION_DEFAULT_STATUSES = {
OPEN: 'Open',
PENDING: 'Pending',
IN_PROGRESS: 'In Progress',
WAITING: 'Waiting',
SNOOZED: 'Snoozed',
RESOLVED: 'Resolved',
CLOSED: 'Closed',
SNOOZED: 'Snoozed',
}
export const CONVERSATION_DEFAULT_STATUSES_LIST = Object.values(CONVERSATION_DEFAULT_STATUSES);
+11 -8
View File
@@ -166,7 +166,7 @@ const routes = [
{
path: 'new',
component: () => import('@/components/admin/business_hours/CreateOrEditBusinessHours.vue'),
meta: { title: 'Add Business Hours' }
meta: { title: 'New Business Hours' }
},
{
path: ':id/edit',
@@ -185,7 +185,7 @@ const routes = [
{
path: 'new',
component: () => import('@/components/admin/sla/CreateEditSLA.vue'),
meta: { title: 'Add SLA' }
meta: { title: 'New SLA' }
},
{
path: ':id/edit',
@@ -310,14 +310,17 @@ const routes = [
children: [
{
path: ':id/edit',
name: 'edit-template',
props: true,
component: () => import('@/components/admin/templates/AddEditTemplate.vue'),
component: () => import('@/components/admin/templates/CreateEditTemplate.vue'),
meta: { title: 'Edit Template' }
},
{
path: 'new',
component: () => import('@/components/admin/templates/AddEditTemplate.vue'),
meta: { title: 'Add Template' }
name: 'new-template',
props: true,
component: () => import('@/components/admin/templates/CreateEditTemplate.vue'),
meta: { title: 'New Template' }
}
]
},
@@ -329,13 +332,13 @@ const routes = [
{
path: ':id/edit',
props: true,
component: () => import('@/components/admin/oidc/AddEditOIDC.vue'),
component: () => import('@/components/admin/oidc/CreateEditOIDC.vue'),
meta: { title: 'Edit OIDC' }
},
{
path: 'new',
component: () => import('@/components/admin/oidc/AddEditOIDC.vue'),
meta: { title: 'Add OIDC' }
component: () => import('@/components/admin/oidc/CreateEditOIDC.vue'),
meta: { title: 'New OIDC' }
}
]
},