NextSchedulesDetailsDialog.vue 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. <template>
  2. <q-dialog
  3. ref="dialogRef"
  4. @hide="onDialogHide"
  5. >
  6. <q-card
  7. class="next-schedule-dialog-card bg-surface"
  8. :flat="false"
  9. >
  10. <q-card-section class="column items-center q-pt-lg q-pb-sm">
  11. <q-avatar
  12. class="fontbold text-h5 q-mb-sm"
  13. size="80px"
  14. :style="avatarStyle"
  15. >
  16. <img
  17. v-if="details?.provider_photo"
  18. :src="details.provider_photo"
  19. />
  20. <span v-else>
  21. {{ schedule.provider_name?.slice(0, 2).toUpperCase() ?? "??" }}
  22. </span>
  23. </q-avatar>
  24. <div class="provider-name font16 fontbold">
  25. {{ getFirstName(schedule.provider_name) || "—" }}
  26. <span
  27. v-if="providerAge !== null"
  28. class="text-grey-6"
  29. >
  30. {{
  31. "(" +
  32. providerAge +
  33. " " +
  34. $t("dashboard_client.next_schedules.provider_age_unit") +
  35. ")"
  36. }}
  37. </span>
  38. </div>
  39. <div class="text-text font12 fontbold">
  40. {{ schedule.address?.district }}
  41. </div>
  42. <div
  43. v-if="schedule.address"
  44. class="font9 fontmedium text-grey-7 q-mt-xs"
  45. >
  46. <q-icon
  47. class="q-mr-xs"
  48. color="text"
  49. name="mdi-map-marker"
  50. size="14px"
  51. />
  52. {{ formatAddress(schedule.address) }}
  53. </div>
  54. </q-card-section>
  55. <q-separator class="q-mx-lg" />
  56. <q-card-section class="q-py-sm">
  57. <template v-if="loadingDetails">
  58. <div class="row justify-center q-py-sm">
  59. <q-spinner-dots
  60. color="primary"
  61. size="24px"
  62. />
  63. </div>
  64. </template>
  65. <template v-else>
  66. <div
  67. v-for="service in servicesList"
  68. :key="service.label"
  69. class="row col-12 items-center q-gutter-x-sm q-mb-xs text-center"
  70. >
  71. <div class="full-width">
  72. <q-icon
  73. color="secondary"
  74. name="mdi-check"
  75. size="12px"
  76. />
  77. <span class="text-grey-8 font10 fontmedium q-pl-sm">
  78. {{ service.label }}
  79. </span>
  80. </div>
  81. </div>
  82. </template>
  83. </q-card-section>
  84. <q-separator class="q-mx-lg divisoria-tracejada" />
  85. <q-card-section class="q-py-md q-px-lg font12 fontmedium">
  86. <div class="detail-row">
  87. <span class="detail-label text-primary q-pr-sm">
  88. {{ $t("dashboard_client.pending_schedules.detail_date") }}
  89. </span>
  90. <span class="detail-value">
  91. {{ fullDateLabel }}
  92. </span>
  93. </div>
  94. <div class="detail-row">
  95. <span class="detail-label text-primary q-pr-sm">
  96. {{ $t("dashboard_client.pending_schedules.detail_time") }}
  97. </span>
  98. <span class="detail-value">
  99. {{ schedule.start_time?.slice(0, 5) }}
  100. {{ $t("dashboard_client.next_schedules.to") }}
  101. {{ schedule.end_time?.slice(0, 5) }}
  102. </span>
  103. </div>
  104. <div
  105. v-if="priceRangeLabel"
  106. class="detail-row"
  107. >
  108. <span class="detail-label text-primary q-pr-sm">
  109. {{ $t("dashboard_client.pending_schedules.detail_price_range") }}
  110. </span>
  111. <span class="detail-value">
  112. {{ priceRangeLabel }}
  113. </span>
  114. </div>
  115. <div class="detail-row-total font16 fontbold">
  116. <span class="detail-label text-primary q-pr-sm">
  117. {{ $t("dashboard_client.pending_schedules.detail_total") }}
  118. </span>
  119. <span class="total-value">
  120. {{ formatCurrency(total) }}
  121. </span>
  122. </div>
  123. <q-separator class="q-my-sm divisoria-tracejada" />
  124. </q-card-section>
  125. <q-card-section class="q-pt-none q-pb-sm q-px-lg">
  126. <q-btn
  127. class="close-btn full-width"
  128. color="primary"
  129. no-caps
  130. rounded
  131. unelevated
  132. :label="$t('dashboard_client.next_schedules.btn_close')"
  133. @click="onDialogCancel"
  134. />
  135. </q-card-section>
  136. <q-card-section class="q-pt-xs q-pb-md text-center">
  137. <div class="row justify-center q-gutter-x-lg">
  138. <q-btn
  139. color="grey-7"
  140. flat
  141. no-caps
  142. size="sm"
  143. :label="$t('dashboard_client.pending_schedules.btn_cancel')"
  144. @click="openCancelDialog"
  145. />
  146. <q-btn
  147. color="grey-7"
  148. flat
  149. no-caps
  150. size="sm"
  151. :label="$t('dashboard_client.next_schedules.btn_help')"
  152. @click="openHelp"
  153. />
  154. </div>
  155. </q-card-section>
  156. </q-card>
  157. </q-dialog>
  158. </template>
  159. <script setup>
  160. import { avatarColors } from "src/helpers/avatarColors";
  161. import { computed, onMounted, ref } from "vue";
  162. import { formatAddress, formatCurrency, getFirstName } from "src/helpers/utils";
  163. import { formatLongDate } from "src/helpers/scheduleDate";
  164. import { getScheduleClienteDetails } from "src/api/dashboard";
  165. import {
  166. getSchedulePaymentType,
  167. getScheduleTotalWithPlatformFee,
  168. } from "src/helpers/paymentPlatformFees";
  169. import { useDialogPluginComponent, useQuasar } from "quasar";
  170. import { useI18n } from "vue-i18n";
  171. import { usePaymentPlatformFees } from "src/composables/usePaymentPlatformFees";
  172. import ProfileHelpDialog from "src/components/profile/ProfileHelpDialog.vue";
  173. import ScheduleCancelDialog from "./ScheduleCancelDialog.vue";
  174. defineEmits([...useDialogPluginComponent.emits]);
  175. const props = defineProps({
  176. schedule: { type: Object, required: true },
  177. });
  178. const $q = useQuasar();
  179. const { dialogRef, onDialogHide, onDialogCancel, onDialogOK } =
  180. useDialogPluginComponent();
  181. const { platformFees, loadPlatformFees } = usePaymentPlatformFees();
  182. const { t } = useI18n();
  183. const details = ref(null);
  184. const loadingDetails = ref(true);
  185. const avatarStyle = computed(
  186. () => avatarColors[props.schedule.id % avatarColors.length],
  187. );
  188. const fullDateLabel = computed(() => {
  189. return props.schedule.formatted_date || formatLongDate(props.schedule.date);
  190. });
  191. const priceRangeLabel = computed(() => {
  192. const minPrice = details.value?.min_price;
  193. const maxPrice = details.value?.max_price;
  194. if (minPrice == null || maxPrice == null) return null;
  195. return `${formatCurrency(minPrice)} - ${formatCurrency(maxPrice)}`;
  196. });
  197. const providerAge = computed(() => {
  198. if (!details.value?.provider_birth_date) return null;
  199. const birth = new Date(details.value.provider_birth_date);
  200. const today = new Date();
  201. let age = today.getFullYear() - birth.getFullYear();
  202. const m = today.getMonth() - birth.getMonth();
  203. if (m < 0 || (m === 0 && today.getDate() < birth.getDate())) age--;
  204. return age;
  205. });
  206. const servicesList = computed(() => {
  207. const scheduleType =
  208. details.value?.schedule_type ?? props.schedule.schedule_type;
  209. const specialities = details.value?.specialities ?? [];
  210. if (scheduleType === "custom" && specialities.length) {
  211. return specialities.map((speciality) => ({
  212. label: speciality.description,
  213. }));
  214. }
  215. return [{ label: t("dashboard_client.next_schedules.default_service") }];
  216. });
  217. const total = computed(() =>
  218. getScheduleTotalWithPlatformFee(
  219. props.schedule,
  220. getSchedulePaymentType(props.schedule),
  221. platformFees.value,
  222. ),
  223. );
  224. const openCancelDialog = () => {
  225. $q.dialog({
  226. component: ScheduleCancelDialog,
  227. componentProps: { schedule: props.schedule },
  228. }).onOk(() => {
  229. onDialogOK({ action: "cancelled", id: props.schedule.id });
  230. });
  231. };
  232. const openHelp = () => {
  233. $q.dialog({ component: ProfileHelpDialog });
  234. };
  235. onMounted(async () => {
  236. try {
  237. details.value = await getScheduleClienteDetails(props.schedule.id);
  238. loadPlatformFees().catch(() => {});
  239. } catch {
  240. $q.notify({ message: t("http.errors.failed"), color: "negative" });
  241. } finally {
  242. loadingDetails.value = false;
  243. }
  244. });
  245. </script>
  246. <style scoped lang="scss">
  247. .next-schedule-dialog-card {
  248. width: 320px;
  249. max-width: 92vw;
  250. border-radius: 20px !important;
  251. overflow: hidden;
  252. }
  253. .provider-name {
  254. color: #8b5cf6;
  255. }
  256. .detail-row {
  257. display: flex;
  258. justify-content: left;
  259. align-items: center;
  260. }
  261. .detail-row-total {
  262. display: flex;
  263. justify-content: center;
  264. align-items: center;
  265. padding: 4px 0;
  266. }
  267. .detail-label {
  268. color: #8a8a9a;
  269. }
  270. .detail-value {
  271. color: #3a3a4a;
  272. }
  273. .total-value {
  274. color: #3a3a4a;
  275. }
  276. .close-btn {
  277. color: white;
  278. height: 48px;
  279. }
  280. .q-mt-xxs {
  281. margin-top: 2px;
  282. }
  283. .divisoria-tracejada {
  284. border-top: 1px dashed #cfcfcf;
  285. }
  286. </style>