feat: discussions new topic
This commit is contained in:
@@ -70,6 +70,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<TextEditor
|
||||
class="mt-5"
|
||||
:content="newReply"
|
||||
@change="(val) => (newReply = val)"
|
||||
placeholder="Type your reply here..."
|
||||
@@ -92,6 +93,7 @@ import { timeAgo } from '../utils'
|
||||
import UserAvatar from '@/components/UserAvatar.vue'
|
||||
import { ChevronLeft, MoreHorizontal } from 'lucide-vue-next'
|
||||
import { ref, inject, onMounted } from 'vue'
|
||||
import { createToast } from '../utils'
|
||||
|
||||
const showTopics = defineModel('showTopics')
|
||||
const newReply = ref('')
|
||||
@@ -147,13 +149,23 @@ const postReply = () => {
|
||||
{
|
||||
validate() {
|
||||
if (!newReply.value) {
|
||||
return __('Reply cannot be empty')
|
||||
return 'Reply cannot be empty'
|
||||
}
|
||||
},
|
||||
onSuccess() {
|
||||
newReply.value = ''
|
||||
replies.reload()
|
||||
},
|
||||
onError() {
|
||||
createToast({
|
||||
title: 'Error',
|
||||
text: err.messages?.[0] || err,
|
||||
icon: 'x',
|
||||
iconClasses: 'bg-red-600 text-white rounded-md p-px',
|
||||
position: 'top-center',
|
||||
timeout: 10,
|
||||
})
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<div v-if="topics.data">
|
||||
<div>
|
||||
<Button class="float-right" @click="openQuestionModal()">
|
||||
{{ __('Ask a Question') }}
|
||||
</Button>
|
||||
<div class="text-xl font-semibold">
|
||||
{{ __(title) }}
|
||||
</div>
|
||||
<div>
|
||||
<Button class="float-right" @click="openTopicModal()">
|
||||
{{ __('New {0}').format(title) }}
|
||||
</Button>
|
||||
<div class="text-xl font-semibold">
|
||||
{{ __(title) }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="topics.data?.length">
|
||||
<div v-if="showTopics" v-for="(topic, index) in topics.data">
|
||||
<div
|
||||
@click="showReplies(topic)"
|
||||
@@ -37,6 +37,24 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex justify-center border mt-5 p-5 rounded-md">
|
||||
<MessageSquareIcon class="w-10 h-10 stroke-1.5 text-gray-800 mr-2" />
|
||||
<div>
|
||||
<div class="text-xl font-semibold mb-2">
|
||||
{{ __(emptyStateTitle) }}
|
||||
</div>
|
||||
<div>
|
||||
{{ __(emptyStateText) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<DiscussionModal
|
||||
v-model="showTopicModal"
|
||||
:title="__('New {0}').format(title)"
|
||||
:doctype="props.doctype"
|
||||
:docname="props.docname"
|
||||
v-model:reloadTopics="topics"
|
||||
/>
|
||||
</template>
|
||||
<script setup>
|
||||
import { createResource, Button } from 'frappe-ui'
|
||||
@@ -44,11 +62,13 @@ import UserAvatar from '@/components/UserAvatar.vue'
|
||||
import { timeAgo } from '../utils'
|
||||
import { ref, onMounted, inject } from 'vue'
|
||||
import DiscussionReplies from '@/components/DiscussionReplies.vue'
|
||||
import DiscussionModal from '@/components/Modals/DiscussionModal.vue'
|
||||
import { MessageSquareIcon, MessagesSquare } from 'lucide-vue-next'
|
||||
|
||||
const showTopics = ref(true)
|
||||
const currentTopic = ref(null)
|
||||
const socket = inject('$socket')
|
||||
const showQuestionModal = ref(false)
|
||||
const showTopicModal = ref(false)
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
@@ -63,6 +83,14 @@ const props = defineProps({
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
emptyStateTitle: {
|
||||
type: String,
|
||||
default: 'No topics yet',
|
||||
},
|
||||
emptyStateText: {
|
||||
type: String,
|
||||
default: 'Be the first to start a discussion',
|
||||
},
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
@@ -74,9 +102,11 @@ onMounted(() => {
|
||||
const topics = createResource({
|
||||
url: 'lms.lms.utils.get_discussion_topics',
|
||||
cache: ['topics', props.doctype, props.docname],
|
||||
params: {
|
||||
doctype: props.doctype,
|
||||
docname: props.docname,
|
||||
makeParams() {
|
||||
return {
|
||||
doctype: props.doctype,
|
||||
docname: props.docname,
|
||||
}
|
||||
},
|
||||
auto: true,
|
||||
})
|
||||
@@ -86,7 +116,7 @@ const showReplies = (topic) => {
|
||||
currentTopic.value = topic
|
||||
}
|
||||
|
||||
const openQuestionModal = () => {
|
||||
showQuestionModal.value = true
|
||||
const openTopicModal = () => {
|
||||
showTopicModal.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
114
frontend/src/components/Modals/DiscussionModal.vue
Normal file
114
frontend/src/components/Modals/DiscussionModal.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:options="{
|
||||
title: props.title,
|
||||
size: '2xl',
|
||||
actions: [
|
||||
{
|
||||
label: 'Submit',
|
||||
variant: 'solid',
|
||||
onClick: (close) => submitTopic(close),
|
||||
},
|
||||
],
|
||||
}"
|
||||
>
|
||||
<template #body-content>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>
|
||||
<div class="mb-1.5 text-sm text-gray-600">
|
||||
{{ __('Title') }}
|
||||
</div>
|
||||
<Input type="text" v-model="topic.title" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="mb-1.5 text-sm text-gray-600">
|
||||
{{ __('Details') }}
|
||||
</div>
|
||||
<TextEditor
|
||||
:content="topic.reply"
|
||||
@change="(val) => (topic.reply = val)"
|
||||
:editable="true"
|
||||
:fixedMenu="true"
|
||||
editorClass="prose-sm max-w-none border-b border-x bg-gray-100 rounded-b-md py-1 px-2 min-h-[7rem]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
<script setup>
|
||||
import { Dialog, Input, TextEditor, createResource } from 'frappe-ui'
|
||||
import { reactive, defineModel } from 'vue'
|
||||
|
||||
const topics = defineModel('reloadTopics')
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
doctype: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
docname: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const topic = reactive({
|
||||
title: '',
|
||||
reply: '',
|
||||
})
|
||||
|
||||
const topicResource = createResource({
|
||||
url: 'frappe.client.insert',
|
||||
makeParams(values) {
|
||||
return {
|
||||
doc: {
|
||||
doctype: 'Discussion Topic',
|
||||
reference_doctype: props.doctype,
|
||||
reference_docname: props.docname,
|
||||
title: topic.title,
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const replyResource = createResource({
|
||||
url: 'frappe.client.insert',
|
||||
makeParams(values) {
|
||||
return {
|
||||
doc: {
|
||||
doctype: 'Discussion Reply',
|
||||
topic: values.topic,
|
||||
reply: topic.reply,
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const submitTopic = (close) => {
|
||||
topicResource.submit(
|
||||
{},
|
||||
{
|
||||
onSuccess(data) {
|
||||
replyResource.submit(
|
||||
{
|
||||
topic: data.name,
|
||||
},
|
||||
{
|
||||
onSuccess() {
|
||||
topic.title = ''
|
||||
topic.reply = ''
|
||||
topics.value.reload()
|
||||
close()
|
||||
},
|
||||
}
|
||||
)
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user