AddEditReviewDialog.vue 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. <template>
  2. <q-dialog ref="dialogRef" @hide="onDialogHide">
  3. <q-card class="q-dialog-plugin" style="width: 800px; max-width: 90vw">
  4. <DefaultDialogHeader :title="title" @close="onDialogCancel" />
  5. <q-form ref="formRef" @submit="onOKClick">
  6. <q-card-section class="row q-col-gutter-sm">
  7. <q-select
  8. v-if="!review"
  9. v-model="selectedSchedule"
  10. :label="$t('reviews.schedule')"
  11. :options="scheduleOptions"
  12. :rules="[inputRules.required]"
  13. :error="!!serverErrors?.schedule_id"
  14. :error-message="serverErrors?.schedule_id"
  15. :loading="loadingSchedules"
  16. option-value="value"
  17. option-label="label"
  18. emit-value
  19. map-options
  20. class="col-12"
  21. @update:model-value="onScheduleChange"
  22. />
  23. <q-input
  24. v-else
  25. :model-value="review.schedule_label"
  26. :label="$t('reviews.schedule')"
  27. readonly
  28. class="col-12"
  29. />
  30. <q-select
  31. v-if="!review"
  32. v-model="form.origin"
  33. :label="$t('reviews.origin')"
  34. :options="originOptions"
  35. :rules="[inputRules.required]"
  36. :error="!!serverErrors?.origin"
  37. :error-message="serverErrors?.origin"
  38. emit-value
  39. map-options
  40. class="col-12 col-md-6"
  41. @update:model-value="onOriginChange"
  42. />
  43. <q-input
  44. v-else
  45. :model-value="$t(`reviews.origins.${review.origin}`)"
  46. :label="$t('reviews.origin')"
  47. readonly
  48. class="col-12 col-md-6"
  49. />
  50. <template v-if="!review">
  51. <ProviderSelect
  52. v-if="form.origin === 'providers'"
  53. :key="'provider-select'"
  54. v-model="selectedOriginModel"
  55. :label="$t('reviews.origin_id')"
  56. :rules="[inputRules.required]"
  57. :error="!!serverErrors?.origin_id"
  58. :error-message="serverErrors?.origin_id"
  59. class="col-12 col-md-6"
  60. @update:model-value="onOriginIdChange"
  61. />
  62. <ClientSelect
  63. v-else-if="form.origin === 'clients'"
  64. :key="'client-select'"
  65. v-model="selectedOriginModel"
  66. :label="$t('reviews.origin_id')"
  67. :rules="[inputRules.required]"
  68. :error="!!serverErrors?.origin_id"
  69. :error-message="serverErrors?.origin_id"
  70. class="col-12 col-md-6"
  71. @update:model-value="onOriginIdChange"
  72. />
  73. <q-input
  74. v-else
  75. :label="$t('reviews.origin_id')"
  76. readonly
  77. :placeholder="$t('reviews.origin')"
  78. class="col-12 col-md-6"
  79. disable
  80. />
  81. </template>
  82. <div class="col-12">
  83. <div class="text-caption q-mb-xs">{{ $t('reviews.stars') }}</div>
  84. <q-rating
  85. v-model="form.stars"
  86. :max="5"
  87. size="md"
  88. color="amber"
  89. icon="star_border"
  90. icon-selected="star"
  91. class="q-mb-xs"
  92. />
  93. <div
  94. v-if="serverErrors?.stars"
  95. class="text-negative text-caption"
  96. >
  97. {{ serverErrors.stars }}
  98. </div>
  99. </div>
  100. <q-input
  101. v-model="form.comment"
  102. :label="$t('reviews.comment')"
  103. :error="!!serverErrors?.comment"
  104. :error-message="serverErrors?.comment"
  105. type="textarea"
  106. autogrow
  107. class="col-12"
  108. @update:model-value="serverErrors.comment = null"
  109. />
  110. <q-select
  111. v-if="!review"
  112. v-model="selectedImprovements"
  113. :label="$t('reviews.improvements')"
  114. :options="improvementOptions"
  115. :loading="loadingImprovements"
  116. option-value="value"
  117. option-label="label"
  118. emit-value
  119. map-options
  120. multiple
  121. use-chips
  122. class="col-12"
  123. @update:model-value="($event) => attImprovementsOnForm($event)"
  124. />
  125. <div v-else>
  126. <div>{{reviewsImprovements?.length > 0 ? t('reviews.improvements_suggested') : t('reviews.no_improvements_suggested')}}</div>
  127. <q-chip
  128. v-for="improvement in reviewsImprovements"
  129. :key="improvement.id"
  130. class="q-mb-xs"
  131. >
  132. {{ improvement.description }}
  133. </q-chip>
  134. </div>
  135. </q-card-section>
  136. <q-card-actions align="right" class="q-px-md q-pb-md">
  137. <q-btn
  138. flat
  139. :label="$t('common.actions.cancel')"
  140. color="negative"
  141. @click="onDialogCancel"
  142. />
  143. <q-btn
  144. type="submit"
  145. :label="$t('common.actions.save')"
  146. :loading="loading"
  147. :disable="!hasUpdatedFields"
  148. color="primary"
  149. unelevated
  150. />
  151. </q-card-actions>
  152. </q-form>
  153. </q-card>
  154. </q-dialog>
  155. </template>
  156. <script setup>
  157. import { ref, computed, onMounted } from 'vue'
  158. import { useDialogPluginComponent } from 'quasar'
  159. import { useFormUpdateTracker } from 'src/composables/useFormUpdateTracker'
  160. import { useSubmitHandler } from 'src/composables/useSubmitHandler'
  161. import { createReview, updateReview, getFinishedSchedules } from 'src/api/review'
  162. import { createReviewImprovement } from 'src/api/reviewImprovement'
  163. import { getImprovementTypes } from 'src/api/improvementType'
  164. import DefaultDialogHeader from 'src/components/defaults/DefaultDialogHeader.vue'
  165. import ProviderSelect from 'src/components/provider/ProviderSelect.vue'
  166. import ClientSelect from 'src/components/client/ClientSelect.vue'
  167. import { useInputRules } from 'src/composables/useInputRules'
  168. import { useI18n } from 'vue-i18n'
  169. import { format, parseISO } from 'date-fns'
  170. const props = defineProps({
  171. review: {
  172. type: Object,
  173. default: null
  174. },
  175. title: {
  176. type: Function,
  177. default: () => ''
  178. }
  179. })
  180. defineEmits([...useDialogPluginComponent.emits])
  181. const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
  182. const { inputRules } = useInputRules()
  183. const { t } = useI18n()
  184. const formRef = ref(null)
  185. const selectedSchedule = ref(props.review ? props.review.schedule_id : null)
  186. const selectedOriginModel = ref(null)
  187. const selectedImprovements = ref([])
  188. const scheduleOptions = ref([])
  189. const improvementOptions = ref([])
  190. const loadingSchedules = ref(false)
  191. const loadingImprovements = ref(false)
  192. const reviewsImprovements = ref([])
  193. const originOptions = computed(() => [
  194. { label: t('reviews.origins.providers'), value: 'providers' },
  195. { label: t('reviews.origins.clients'), value: 'clients' }
  196. ])
  197. const { form, hasUpdatedFields } = useFormUpdateTracker({
  198. schedule_id: props.review ? props.review.schedule_id : null,
  199. origin: props.review ? props.review.origin : null,
  200. origin_id: props.review ? props.review.origin_id : null,
  201. stars: props.review ? Number(props.review.stars) : 0,
  202. comment: props.review ? props.review.comment : null,
  203. improvements_ids: props.review ? props.review.improvements?.map(i => i.id) : [],
  204. })
  205. const {
  206. loading,
  207. serverErrors,
  208. execute: submitForm,
  209. } = useSubmitHandler({
  210. onSuccess: () => onDialogOK(true),
  211. formRef: formRef,
  212. })
  213. const onScheduleChange = (val) => {
  214. form.schedule_id = val
  215. serverErrors.schedule_id = null
  216. }
  217. const onOriginChange = () => {
  218. selectedOriginModel.value = null
  219. form.origin_id = null
  220. serverErrors.origin = null
  221. serverErrors.origin_id = null
  222. }
  223. const onOriginIdChange = (selected) => {
  224. form.origin_id = selected?.value ?? null
  225. serverErrors.origin_id = null
  226. }
  227. const onOKClick = async () => {
  228. await submitForm(async () => {
  229. if (props.review) {
  230. return updateReview(props.review.id, {
  231. stars: form.stars,
  232. comment: form.comment,
  233. })
  234. } else {
  235. const created = await createReview({ ...form })
  236. for (const improvementTypeId of selectedImprovements.value) {
  237. await createReviewImprovement({
  238. review_id: created.id,
  239. improvement_type_id: improvementTypeId,
  240. })
  241. }
  242. return created
  243. }
  244. })
  245. }
  246. const attImprovementsOnForm = (event) => {
  247. form.improvements_ids = event;
  248. serverErrors.improvements_ids = null;
  249. }
  250. onMounted(async () => {
  251. loadingSchedules.value = true
  252. loadingImprovements.value = true
  253. try {
  254. const [schedules, improvements] = await Promise.all([
  255. getFinishedSchedules(),
  256. getImprovementTypes(),
  257. ])
  258. scheduleOptions.value = schedules.map((s) => ({
  259. value: s.id,
  260. label: `${s.id} - ${s.client_name ?? '?'} - ${s.provider_name ?? '?'} - ${s.date ? format(parseISO(s.date), 'dd/MM/yyyy') : '?'}`,
  261. }))
  262. improvementOptions.value = improvements.map((i) => ({
  263. value: i.id,
  264. label: i.description,
  265. }))
  266. reviewsImprovements.value = props.review?.reviews_improvements;
  267. } catch (error) {
  268. console.error('Error loading data:', error)
  269. } finally {
  270. loadingSchedules.value = false
  271. loadingImprovements.value = false
  272. }
  273. })
  274. </script>