mirror of
https://github.com/abhinavxd/libredesk.git
synced 2026-10-03 20:20:30 +00:00
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:
@@ -12,7 +12,7 @@
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
padding-bottom: 100px;
|
||||
@apply bg-slate-50;
|
||||
@apply bg-accent
|
||||
}
|
||||
|
||||
@layer base {
|
||||
|
||||
+23
-7
@@ -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) {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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);
|
||||
@@ -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' }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user