This commit is contained in:
sajjadtalkhabi
2026-05-21 15:51:32 +03:30
parent 8990ceaddd
commit 74227d5021
71 changed files with 2014 additions and 1666 deletions
@@ -17,7 +17,7 @@
:class="`ticket-details__row--${senderClass(message)}`"
>
<div class="ticket-details__bubble">
<p class="ticket-details__text">{{ message.text }}</p>
<p class="ticket-details__text">{{ message.message }}</p>
<span class="ticket-details__time">{{ messageTime(message) }}</span>
</div>
</div>
@@ -26,19 +26,6 @@
<div class="ticket-details__divider" />
<div v-if="attachment" class="ticket-details__attachment">
<SvgIcon name="file" :size="16" color="var(--color-prim-gray)" />
<span class="ticket-details__attachment-name">{{ attachment.name }}</span>
<button
type="button"
class="ticket-details__attachment-remove"
aria-label="حذف فایل"
@click="removeAttachment"
>
<SvgIcon name="close" :size="14" color="var(--color-error)" />
</button>
</div>
<form class="ticket-details__compose" @submit.prevent="onSend">
<button
type="submit"
@@ -65,20 +52,6 @@
>
<SvgIcon name="mood" :size="30" color="currentColor" />
</button>
<button
type="button"
class="ticket-details__compose-btn"
aria-label="پیوست فایل"
@click="triggerFilePicker"
>
<SvgIcon name="attach-file" :size="30" color="currentColor" />
</button>
<input
ref="fileInput"
type="file"
class="ticket-details__file-input"
@change="onFileSelected"
/>
</div>
</form>
</div>
@@ -110,7 +83,6 @@ import SvgIcon from '@/components/icons/SvgIcon.vue'
import NoItems from '@/components/blocks/NoItems.vue'
import { formatJalaaliDate } from '@/utils/date-utils'
import LineTitleBlock from '@/components/LineTitleBlock.vue'
import { objectToFormData } from '@/utils/object-to-formdata'
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
import SkeletonLoaderBlock from '@/components/blocks/SkeletonLoaderBlock.vue'
import {
@@ -167,28 +139,21 @@ const { data: ticket, isLoading } = useAdminTicketQuery(ticketId, {
const messages = computed(() => ticket.value?.messages ?? [])
const ticketDate = computed(
() => ticket.value?.faCreatedAt || formatJalaaliDate(ticket.value?.createdAt) || '—'
)
const ticketDate = computed(() => formatJalaaliDate(ticket.value?.createdAt) || '—')
const senderClass = (message) => (message.sender === 'admin' ? 'admin' : 'user')
// Anyone whose id matches the ticket's student is "the student"; everyone else
// (admin, counselor) renders on the opposite side of the thread.
const senderClass = (message) => (message.senderId === ticket.value?.studentId ? 'user' : 'admin')
const messageTime = (message) => message.time || message.faSentAt || message.sentAt || '—'
const messageTime = (message) => {
if (!message.createdAt) return '—'
const d = new Date(message.createdAt)
if (Number.isNaN(d.getTime())) return '—'
return d.toLocaleTimeString('fa-IR', { hour: '2-digit', minute: '2-digit' })
}
const text = ref('')
const attachment = ref(null)
const canSend = computed(() => text.value.trim().length > 0 || !!attachment.value)
const fileInput = ref(null)
const triggerFilePicker = () => fileInput.value?.click()
const onFileSelected = (event) => {
const file = event.target?.files?.[0]
if (file) attachment.value = file
if (event.target) event.target.value = ''
}
const removeAttachment = () => {
attachment.value = null
}
const canSend = computed(() => text.value.trim().length > 0)
const emojiOpen = ref(false)
const emojiButton = ref(null)
@@ -244,12 +209,10 @@ const sendMutation = useSendAdminTicketMessageMutation()
const onSend = async () => {
if (!canSend.value || !ticketId.value) return
const value = text.value.trim()
const file = attachment.value
text.value = ''
attachment.value = null
emojiOpen.value = false
const payload = file ? objectToFormData({ text: value, attachment: file }) : { text: value }
await sendMutation.mutateAsync({ id: ticketId.value, payload })
// Backend POST /admin/tickets/:id/messages — body is { message: string }.
await sendMutation.mutateAsync({ id: ticketId.value, payload: { message: value } })
await queryClient.invalidateQueries({ queryKey: adminTicketsKeys.all })
}