AdminTicketShow.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. <template>
  2. <div class="AdminTicketShow"
  3. :style="localOptions.style">
  4. <div class="title">
  5. <div class="static-title">
  6. مشاهده پیام
  7. </div>
  8. <div class="dynamic-title">
  9. <template v-if="entityLoading">
  10. <q-skeleton type="text"
  11. class="q-ml-lg"
  12. width="100px" />
  13. </template>
  14. <template v-else>
  15. {{ packageTitle }}
  16. </template>
  17. </div>
  18. <div class="back-action">
  19. <q-btn flat
  20. color="grey"
  21. @click="$router.go(-1)">
  22. بازگشت
  23. >
  24. </q-btn>
  25. </div>
  26. </div>
  27. <q-card class="form"
  28. flat>
  29. <entity-edit v-if="mounted"
  30. ref="entityEdit"
  31. v-model:value="inputs"
  32. title="اطلاعات کاربری"
  33. :api="api"
  34. :entity-id-key="entityIdKey"
  35. :entity-param-key="entityParamKey"
  36. :show-route-name="showRouteName"
  37. :show-close-button="false"
  38. :show-edit-button="false"
  39. :show-expand-button="false"
  40. :show-save-button="false"
  41. :show-reload-button="false"
  42. :default-layout="false"
  43. :redirect-after-edit="false"
  44. :before-load-input-data="beforeLoadInputData"
  45. :after-load-input-data="afterLoadInputData" />
  46. <div class="row q-mt-lg justify-end">
  47. <div class="col-md-4 col-12">
  48. <q-btn color="primary"
  49. class="full-width"
  50. :loading="entityLoading"
  51. @click="edit">
  52. ثبت تغییرات
  53. </q-btn>
  54. </div>
  55. </div>
  56. <q-card v-if="mounted"
  57. class="q-mt-md">
  58. <q-card-section>
  59. <div class="row justify-center">
  60. <div class="full-width">
  61. <q-skeleton v-if="entityLoading"
  62. type="rect"
  63. width="100%"
  64. height="200px" />
  65. <template v-else>
  66. <q-chat-message v-for="message in repliesInfo"
  67. :key="message.id"
  68. :avatar="message.creator_info.picture ? message.creator_info.picture : 'assets/images/web/sample-avatar.svg'"
  69. :name="message.creator_info.firstname + ' ' + message.creator_info.lastname"
  70. text-html
  71. :text="[message.body]"
  72. :sent="!isUserMessage(authenticatedUser, message)">
  73. <template v-slot:stamp>
  74. <div>
  75. {{ miladiToShamsi(message.creation_time) }}
  76. </div>
  77. <q-btn icon="clear"
  78. round
  79. color="red"
  80. flat
  81. size="xs"
  82. @click="removeMessage(message.id)" />
  83. <q-icon v-if="isUserMessage(authenticatedUser, message)"
  84. :name="message.read ? 'done_all' : 'done'" />
  85. </template>
  86. </q-chat-message>
  87. <div>
  88. <q-input v-model="replyText"
  89. bottom-slots
  90. autogrow
  91. :loading="entityLoading">
  92. <template v-slot:after>
  93. <q-btn round
  94. dense
  95. flat
  96. icon="send"
  97. :loading="entityLoading"
  98. @click="sendReply" />
  99. </template>
  100. </q-input>
  101. </div>
  102. <div class="q-mt-md">
  103. <q-btn outline
  104. color="red"
  105. @click="closeTicket">
  106. بستن تیکت
  107. </q-btn>
  108. </div>
  109. </template>
  110. </div>
  111. </div>
  112. </q-card-section>
  113. </q-card>
  114. </q-card>
  115. <!-- <show-source :source-type="ticket.source_type"-->
  116. <!-- :source-id="ticket.source_id"-->
  117. <!-- :source="ticket.source" />-->
  118. <admin-invoice-show v-if="ticket.source_type === 'INVOICE' && ticket.source_id"
  119. :options="{invoiceId: ticket.source_id, showBackBtn: false}"
  120. class="q-mt-lg" />
  121. <q-card v-if="ticket.source_type === 'INVOICE' && ticket.source_id"
  122. class="q-mt-lg">
  123. <q-card-section>
  124. اقساط
  125. </q-card-section>
  126. <q-separator />
  127. <q-card-section>
  128. <installment-offers :installment-offers="instalmentOffers"
  129. :loading="entityLoading"
  130. :can-accept="false"
  131. :can-reject="false"
  132. @onReject="onReject"
  133. @onAccept="onAccept" />
  134. </q-card-section>
  135. <q-separator />
  136. <q-card-section>
  137. <create-installment :invoice-id="ticket.source_id"
  138. @onCreated="onCreateInstallmentOffer" />
  139. </q-card-section>
  140. </q-card>
  141. </div>
  142. </template>
  143. <script>
  144. import { shallowRef } from 'vue'
  145. import { EntityEdit } from 'quasar-crud'
  146. import { User } from 'src/models/User.js'
  147. import Assist from 'src/assets/js/Assist.js'
  148. import { Ticket } from 'src/models/Ticket.js'
  149. import { mixinWidget } from 'src/mixin/Mixins.js'
  150. import { APIGateway } from 'src/api/APIGateway.js'
  151. import { FormBuilderAssist } from 'quasar-form-builder'
  152. import BtnControl from 'src/components/Control/btn.vue'
  153. import { InstalmentOfferList } from 'src/models/InstalmentOffer.js'
  154. import { TicketCategory, TicketCategoryList } from 'src/models/TicketCategory.js'
  155. // import ShowSource from 'src/components/Widgets/Ticket/AdminTicketShow/ShowSource.vue'
  156. import AdminInvoiceShow from 'src/components/Widgets/Invoice/AdminInvoiceShow/AdminInvoiceShow.vue'
  157. import InstallmentOffers from 'src/components/Widgets/Ticket/AdminTicketShow/InstallmentOffers.vue'
  158. import CreateInstallment from 'src/components/Widgets/Ticket/AdminTicketShow/CreateInstallment.vue'
  159. const BtnControlComp = shallowRef(BtnControl)
  160. export default {
  161. name: 'AdminTicketShow',
  162. components: {
  163. EntityEdit,
  164. // ShowSource,
  165. AdminInvoiceShow,
  166. CreateInstallment,
  167. InstallmentOffers
  168. },
  169. mixins: [mixinWidget],
  170. data () {
  171. const ticketId = this.$route.params.id
  172. return {
  173. replyText: null,
  174. mounted: false,
  175. entityLoading: true,
  176. packageTitle: '',
  177. ticket: new Ticket(),
  178. authenticatedUser: new User(),
  179. api: APIGateway.ticket.APIAdresses.byId(ticketId),
  180. ticketCategoryList: new TicketCategoryList(),
  181. entityIdKey: 'id',
  182. entityParamKey: 'id',
  183. showRouteName: 'Admin.Ticket.Show',
  184. indexRouteName: 'Admin.Ticket.List',
  185. inputs: [
  186. { type: 'separator', name: 'space', label: 'مشخصات کاربر', className: 'custom-separator', col: 'col-12' },
  187. { type: 'hidden', name: 'userId', responseKey: 'creator_info.id', disable: true },
  188. { type: 'input', name: 'creator_info.firstname', responseKey: 'creator_info.firstname', label: 'نام', placeholder: ' ', disable: true, col: 'col-md-2 col-12' },
  189. { type: 'input', name: 'creator_info.lastname', responseKey: 'creator_info.lastname', label: 'نام خانوادگی', placeholder: ' ', disable: true, col: 'col-md-2 col-12' },
  190. { type: 'input', name: 'creator_info.mobile_number', responseKey: 'creator_info.mobile_number', label: 'شماره موبایل', placeholder: ' ', disable: true, col: 'col-md-3 col-12' },
  191. { type: 'input', name: 'creator_info.email', responseKey: 'creator_info.email', label: 'ایمیل', placeholder: ' ', disable: true, col: 'col-md-3 col-12' },
  192. { type: BtnControlComp, name: 'btn', responseKey: 'btn', label: 'مشخصات کاربر', placeholder: ' ', atClick: () => {}, col: 'col-md-2 col-12' },
  193. { type: 'separator', name: 'space', label: 'مشخصات تیکت', className: 'custom-separator', col: 'col-12' },
  194. {
  195. type: 'select',
  196. name: 'category_info__type',
  197. responseKey: 'category_info.type',
  198. options: (new TicketCategory()).typeEnums,
  199. label: 'معاونت',
  200. placeholder: ' ',
  201. col: 'col-md-4 col-12'
  202. },
  203. { type: 'select', name: 'category', responseKey: 'category', placeholder: ' ', options: [], label: 'دسته', col: 'col-md-4 col-12' },
  204. { type: 'select', name: 'status', responseKey: 'status', options: (new Ticket()).statusEnums, multiple: false, label: 'وضعیت', placeholder: ' ', readonly: true, col: 'col-md-4 col-12' },
  205. { type: 'file', name: 'attachment', responseKey: 'attachment', placeholder: ' ', label: 'ضمیمه', readonly: true, col: 'col-md-6 col-12' },
  206. { type: 'dateTime', name: 'creation_time', responseKey: 'creation_time', outsideLabel: 'تاریخ ایجاد', readonly: true, col: 'col-md-6 col-12' },
  207. { type: 'input', name: 'title', responseKey: 'title', label: 'عنوان', placeholder: ' ', readonly: true, col: 'col-md-12 col-12' },
  208. { type: 'inputEditor', name: 'body', responseKey: 'body', label: 'متن', placeholder: ' ', readonly: true, col: 'col-md-12 col-12' },
  209. { type: 'hidden', name: 'source_type', responseKey: 'source_type', value: null },
  210. { type: 'hidden', name: 'source_id', responseKey: 'source_id', value: null },
  211. { type: 'hidden', name: 'id', responseKey: 'id' },
  212. { type: 'hidden', name: 'owner', responseKey: 'owner' },
  213. { type: 'hidden', name: 'replies_info', responseKey: 'replies_info' }
  214. ]
  215. }
  216. },
  217. computed: {
  218. selectedCategoryInfoType () {
  219. return FormBuilderAssist.getInputsByName(this.inputs, 'category_info__type')?.value
  220. },
  221. instalmentOffers () {
  222. return new InstalmentOfferList(this.ticket.source?.instalment_offers_info)
  223. },
  224. repliesInfo () {
  225. return this.inputs.find(input => input.name === 'replies_info').value
  226. }
  227. },
  228. watch: {
  229. selectedCategoryInfoType () {
  230. FormBuilderAssist.setAttributeByName(this.inputs, 'category', 'value', null)
  231. this.loadCategories()
  232. }
  233. },
  234. created() {
  235. FormBuilderAssist.setAttributeByName(this.inputs, 'btn', 'atClick', () => {
  236. const userId = FormBuilderAssist.getInputsByName(this.inputs, 'userId').value
  237. this.$router.push({ name: 'Admin.User.Show', params: { id: userId } })
  238. })
  239. },
  240. mounted() {
  241. this.loadAuthData()
  242. this.checkSource()
  243. this.loadOptions()
  244. .then(() => {
  245. this.$nextTick(() => {
  246. this.mounted = true
  247. })
  248. })
  249. .catch(() => {
  250. this.mounted = true
  251. })
  252. },
  253. methods: {
  254. closeTicket () {
  255. this.$q.dialog({
  256. title: 'توجه',
  257. message: 'آیا از بستن تیکت اطمینان دارید؟',
  258. cancel: true,
  259. persistent: true
  260. }).onOk(() => {
  261. this.entityLoading = true
  262. APIGateway.ticket.close(this.$route.params.id)
  263. .then(() => {
  264. this.$refs.entityEdit.getData()
  265. })
  266. .catch(() => {
  267. this.entityLoading = false
  268. })
  269. })
  270. },
  271. miladiToShamsi (miladi) {
  272. return Assist.miladiToShamsi(miladi)
  273. },
  274. isUserMessage (authenticatedUser, message) {
  275. return authenticatedUser.id === message.creator_info.id
  276. },
  277. removeMessage (replyMessageId) {
  278. this.$q.dialog({
  279. title: 'توجه',
  280. message: 'آیا از حذف پیام اطمینان دارید؟',
  281. cancel: true,
  282. persistent: true
  283. }).onOk(() => {
  284. this.entityLoading = true
  285. APIGateway.ticket.deleteReplyMessage(replyMessageId)
  286. .then(() => {
  287. this.$refs.entityEdit.getData()
  288. })
  289. .catch(() => {
  290. this.entityLoading = false
  291. })
  292. })
  293. },
  294. onReject (offer) {
  295. this.entityLoading = true
  296. APIGateway.instalmentOffer.reject(offer.id)
  297. .then(() => {
  298. this.$refs.entityEdit.getData()
  299. })
  300. .catch(() => {
  301. this.$refs.entityEdit.getData()
  302. })
  303. },
  304. onAccept (offer) {
  305. this.entityLoading = true
  306. APIGateway.instalmentOffer.accept(offer.id)
  307. .then(() => {
  308. this.$refs.entityEdit.getData()
  309. })
  310. .catch(() => {
  311. this.$refs.entityEdit.getData()
  312. })
  313. },
  314. onCreateInstallmentOffer () {
  315. this.$refs.entityEdit.getData()
  316. },
  317. checkSource () {
  318. if (this.localOptions.defaultSourceType) {
  319. FormBuilderAssist.setAttributeByName(this.inputs, 'source_type', 'value', this.localOptions.defaultSourceType)
  320. }
  321. },
  322. loadAuthData () {
  323. this.authenticatedUser = this.$store.getters['Auth/user']
  324. },
  325. beforeLoadInputData (data) {
  326. this.entityLoading = false
  327. this.ticket = new Ticket(data)
  328. },
  329. afterLoadInputData () {
  330. this.$nextTick(() => {
  331. FormBuilderAssist.setAttributeByName(this.inputs, 'category', 'value', this.ticket.category)
  332. })
  333. },
  334. edit() {
  335. this.entityLoading = true
  336. this.$refs.entityEdit.editEntity()
  337. .then(() => {
  338. this.$refs.entityEdit.getData()
  339. })
  340. .catch(() => {
  341. this.$refs.entityEdit.getData()
  342. })
  343. },
  344. loadOptions () {
  345. return this.loadCategories()
  346. },
  347. loadCategories () {
  348. return new Promise((resolve, reject) => {
  349. const type = this.selectedCategoryInfoType
  350. // if (!type) {
  351. // return
  352. // }
  353. this.categoriesLoading = true
  354. APIGateway.ticketCategory.index({ type })
  355. .then(({ list }) => {
  356. const ticketCategoryList = list
  357. FormBuilderAssist.setAttributeByName(this.inputs, 'category', 'options', ticketCategoryList.list.map(item => {
  358. return {
  359. value: item.id,
  360. label: item.title
  361. }
  362. }))
  363. this.categoriesLoading = false
  364. this.$nextTick(() => {
  365. resolve()
  366. })
  367. })
  368. .catch(() => {
  369. this.categoriesLoading = false
  370. this.$nextTick(() => {
  371. reject()
  372. })
  373. })
  374. })
  375. },
  376. sendReply() {
  377. this.entityLoading = true
  378. const body = this.replyText.replace(/\n/g, '<br>\n')
  379. APIGateway.ticket.reply({
  380. id: this.$route.params.id,
  381. body
  382. })
  383. .then(() => {
  384. this.$refs.entityEdit.getData()
  385. })
  386. .catch(() => {
  387. this.$refs.entityEdit.getData()
  388. })
  389. }
  390. }
  391. }
  392. </script>
  393. <style scoped lang="scss">
  394. .AdminTicketShow {
  395. .title {
  396. font-style: normal;
  397. font-weight: 700;
  398. font-size: 24px;
  399. line-height: 140%;
  400. color: #424242;
  401. margin-bottom: 27px;
  402. display: flex;
  403. flex-flow: row;
  404. position: relative;
  405. .static-title {
  406. }
  407. .dynamic-title {
  408. }
  409. .back-action {
  410. position: absolute;
  411. right: 0;
  412. top: 0;
  413. }
  414. }
  415. :deep(.form) {
  416. padding: 24px;
  417. }
  418. }
  419. </style>