NextSchedulesDetailsDialog.vue 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. <template>
  2. <q-dialog ref="dialogRef" @hide="onDialogHide">
  3. <q-card class="next-schedule-dialog-card bg-surface shadow-card" :flat="false">
  4. <div class="row justify-end q-pt-sm q-pr-sm">
  5. <q-btn flat round dense icon="close" color="grey-6" size="sm" @click="onDialogCancel" />
  6. </div>
  7. <q-card-section class="column items-center q-pt-xs q-pb-sm">
  8. <q-avatar size="72px" class="q-mb-sm">
  9. <img v-if="schedule.customer_photo" :src="schedule.customer_photo" style="object-fit:cover" />
  10. <span v-else class="full-width full-height flex flex-center" :style="avatarColors[schedule.id % avatarColors.length]" style="font-size:20px;border-radius:50%">{{ (schedule.customer_name ?? schedule.client_name)?.slice(0,2).toUpperCase() ?? '??' }}</span>
  11. </q-avatar>
  12. <div class="text-text font16 fontbold">
  13. {{ schedule.customer_name ?? schedule.client_name }}
  14. </div>
  15. <div class="text-price font16 fontbold q-mt-xs">{{ formatCurrency(schedule.total_amount) }}</div>
  16. <div class="text-grey-6 font12 q-mt-xxs">{{ periodLabel }}</div>
  17. </q-card-section>
  18. <q-separator class="q-mx-lg" />
  19. <q-card-section class="column items-center q-py-sm text-text font12">
  20. <div class="text-center">
  21. <span class="fontbold">{{ weekdayLabel }}</span>
  22. <span class="text-grey-7">{{', ' + dayMonthLabel }}</span>
  23. </div>
  24. <div class="text-center q-mt-xxs">
  25. {{ $t('common.from') }}
  26. <span class="fontbold">{{ schedule.start_time?.slice(0, 5) }}</span>
  27. {{ $t('common.to') }}
  28. <span class="fontbold">{{ schedule.end_time?.slice(0, 5) }}</span>
  29. </div>
  30. </q-card-section>
  31. <q-separator class="q-mx-lg" />
  32. <q-card-section class="column items-center q-py-sm">
  33. <div class="row items-center q-gutter-x-xs">
  34. <q-icon
  35. :name="schedule.offers_meal ? 'mdi-silverware' : 'mdi-close-circle-outline'"
  36. :color="schedule.offers_meal ? 'secondary' : 'grey-5'"
  37. size="18px"
  38. />
  39. <span class="font12" :class="schedule.offers_meal ? 'text-text' : 'text-grey-6'">
  40. {{ schedule.offers_meal
  41. ? $t('provider.dashboard.next_schedules.offers_meal')
  42. : $t('provider.dashboard.next_schedules.no_meal') }}
  43. </span>
  44. </div>
  45. </q-card-section>
  46. <q-separator class="q-mx-lg" />
  47. <q-card-section class="column items-center q-py-sm q-px-lg">
  48. <div class="row items-start q-gutter-x-xs no-wrap">
  49. <div class="col-1 q-my-auto">
  50. <q-icon name="mdi-map-marker-outline" color="primary" size="20px" class="q-mt-xxs flex-shrink-0" />
  51. </div>
  52. <div class="col">
  53. <span class="text-grey-8 text-center font12">{{ formattedAddress || 'N/A' }}</span>
  54. </div>
  55. </div>
  56. <div class="text-grey-6 text-center font12 q-mt-xs">
  57. {{ $t('provider.dashboard.solicitations.distance_prefix') }}
  58. <strong>{{ (schedule.distance_km ?? 0) + ' km' }}</strong>
  59. {{ $t('provider.dashboard.solicitations.distance_suffix') }}
  60. </div>
  61. <q-btn
  62. flat
  63. no-caps
  64. dense
  65. color="primary"
  66. size="sm"
  67. icon="mdi-content-copy"
  68. :label="$t('provider.dashboard.next_schedules.copy_address')"
  69. class="q-mt-xs"
  70. @click="copyAddress"
  71. />
  72. </q-card-section>
  73. <q-card-section class="q-pt-xs q-pb-sm q-px-lg">
  74. <q-btn
  75. unelevated
  76. rounded
  77. no-caps
  78. color="primary"
  79. size="sm"
  80. class="full-width btn-close font14 fontbold"
  81. :label="$t('provider.dashboard.cancel_schedule.close')"
  82. @click="onDialogCancel"
  83. />
  84. </q-card-section>
  85. <q-card-section class="q-pt-none q-pb-md text-center">
  86. <div class="row justify-center q-gutter-x-lg">
  87. <q-btn
  88. flat
  89. no-caps
  90. color="grey-7"
  91. size="sm"
  92. :label="$t('provider.dashboard.next_schedules.btn_cancel_service')"
  93. @click="openCancelDialog"
  94. />
  95. <q-btn
  96. flat
  97. no-caps
  98. color="grey-7"
  99. size="sm"
  100. :label="$t('provider.dashboard.next_schedules.btn_need_help')"
  101. @click="openHelp"
  102. />
  103. </div>
  104. </q-card-section>
  105. </q-card>
  106. </q-dialog>
  107. </template>
  108. <script setup>
  109. import { computed } from 'vue'
  110. import { useDialogPluginComponent, useQuasar } from 'quasar'
  111. import { useI18n } from 'vue-i18n'
  112. import { formatCurrency, formatAddress } from 'src/helpers/utils'
  113. import { labelsPeriodTypes } from 'src/helpers/arraysOptions/labelsPeriodTypes.js'
  114. import ScheduleCancelDialog from './ScheduleCancelDialog.vue'
  115. import ProfileHelpDialog from 'src/components/profile/ProfileHelpDialog.vue'
  116. const avatarColors = [
  117. { background: '#ffd5df', color: '#932e57' },
  118. { background: '#d7e8ff', color: '#2158a8' },
  119. { background: '#dfd', color: '#2a7a3b' },
  120. { background: '#ffe5cc', color: '#8a4500' },
  121. ];
  122. const props = defineProps({
  123. schedule: {
  124. type: Object,
  125. required: true
  126. }
  127. })
  128. const { t } = useI18n()
  129. const $q = useQuasar()
  130. const { dialogRef, onDialogHide, onDialogCancel, onDialogOK } = useDialogPluginComponent()
  131. const periodLabel = computed(() => {
  132. const found = labelsPeriodTypes.find(l => l.value == props.schedule.period_type)
  133. return found ? t(found.label) : ''
  134. })
  135. const parseLocalDate = (dateStr) => {
  136. if (!dateStr) return null
  137. const s = String(dateStr)
  138. const iso = s.match(/^(\d{4})-(\d{2})-(\d{2})/)
  139. if (iso) return new Date(+iso[1], +iso[2] - 1, +iso[3])
  140. const dmy = s.match(/^(\d{2})\/(\d{2})\/(\d{4})/)
  141. if (dmy) return new Date(+dmy[3], +dmy[2] - 1, +dmy[1])
  142. return null
  143. }
  144. const weekdayLabel = computed(() => {
  145. const d = parseLocalDate(props.schedule.date)
  146. if (!d) return ''
  147. const w = d.toLocaleDateString('pt-BR', { weekday: 'long' })
  148. return w.charAt(0).toUpperCase() + w.slice(1)
  149. })
  150. const dayMonthLabel = computed(() => {
  151. const d = parseLocalDate(props.schedule.date)
  152. if (!d) return ''
  153. return d.toLocaleDateString('pt-BR', { day: '2-digit', month: '2-digit' })
  154. })
  155. const formattedAddress = computed(() => formatAddress(props.schedule.address))
  156. const copyAddress = () => {
  157. const addr = formattedAddress.value
  158. if (addr) navigator.clipboard.writeText(addr)
  159. $q.notify({ message: t('provider.dashboard.next_schedules.address_copied'), color: 'positive' })
  160. }
  161. const openCancelDialog = () => {
  162. $q.dialog({
  163. component: ScheduleCancelDialog,
  164. componentProps: { schedule: props.schedule }
  165. }).onOk(() => {
  166. onDialogOK({ action: 'cancelled', id: props.schedule.id })
  167. })
  168. }
  169. const openHelp = () => {
  170. $q.dialog({ component: ProfileHelpDialog })
  171. }
  172. </script>
  173. <style scoped lang="scss">
  174. .next-schedule-dialog-card {
  175. width: 320px;
  176. max-width: 92vw;
  177. border-radius: 20px !important;
  178. overflow: hidden;
  179. }
  180. .text-price {
  181. color: var(--q-primary);
  182. }
  183. .btn-close {
  184. padding: 10px 0;
  185. }
  186. .flex-shrink-0 {
  187. flex-shrink: 0;
  188. }
  189. .q-mt-xxs {
  190. margin-top: 2px;
  191. }
  192. </style>