ServiceSelectionSheet.vue 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. <template>
  2. <q-dialog
  3. ref="dialogRef"
  4. position="bottom"
  5. @hide="onDialogHide"
  6. >
  7. <q-card class="bg-surface text-text full-width sheet-card">
  8. <q-card-section class="row items-center q-pb-none">
  9. <q-space />
  10. <q-btn
  11. color="grey-6"
  12. dense
  13. flat
  14. icon="mdi-close-circle-outline"
  15. round
  16. @click="onDialogCancel"
  17. />
  18. </q-card-section>
  19. <q-separator class="q-mt-sm" />
  20. <q-card-section class="q-pt-sm q-pb-md">
  21. <div
  22. v-if="availableServiceTypes.length === 0"
  23. class="text-center text-grey-6 text-body2 q-py-md"
  24. >
  25. {{ $t("scheduling_page.no_slots_available") }}
  26. </div>
  27. <div
  28. v-for="type in availableServiceTypes"
  29. :key="type.key"
  30. class="row items-center no-wrap q-py-sm"
  31. >
  32. <div class="col">
  33. <div class="row items-center no-wrap q-gutter-x-xs">
  34. <span class="font14 fontbold text-text">
  35. {{ type.label }}
  36. </span>
  37. <q-btn
  38. color="primary"
  39. dense
  40. flat
  41. icon="mdi-information-outline"
  42. size="xs"
  43. @click="openInfo(type)"
  44. />
  45. </div>
  46. <div class="font12 fontmedium text-grey-6">
  47. {{ type.hours }}
  48. </div>
  49. </div>
  50. <div class="row items-center no-wrap q-gutter-x-sm">
  51. <span class="font12 fontmedium text-text">
  52. {{ formatServicePrice(type.price) }}
  53. </span>
  54. <q-btn
  55. color="secondary"
  56. no-caps
  57. padding="2px 16px"
  58. rounded
  59. size="sm"
  60. unelevated
  61. :label="$t('common.actions.select')"
  62. @click="onDialogOK({ action: 'servicePackage', serviceType: type, date: selectedDate, provider })"
  63. />
  64. </div>
  65. </div>
  66. </q-card-section>
  67. </q-card>
  68. </q-dialog>
  69. </template>
  70. <script setup>
  71. import { computed } from "vue";
  72. import { useDialogPluginComponent, useQuasar } from "quasar";
  73. import { useI18n } from "vue-i18n";
  74. import { formatCurrency } from "src/helpers/utils";
  75. import ServiceTypeInfoDialog from "./ServiceTypeInfoDialog.vue";
  76. defineEmits([...useDialogPluginComponent.emits]);
  77. const props = defineProps({
  78. partialBlocks: { default: () => [], required: false, type: Array },
  79. provider: { required: true, type: Object },
  80. selectedDate: { required: true, type: String },
  81. });
  82. const $q = useQuasar();
  83. const { dialogRef, onDialogCancel, onDialogHide, onDialogOK } =
  84. useDialogPluginComponent();
  85. const { t } = useI18n();
  86. const formatServicePrice = (price) =>
  87. price === null || price === undefined
  88. ? t("search_page.no_price")
  89. : formatCurrency(price);
  90. const slotConflicts = (slotStart, slotEnd, blocks) =>
  91. blocks.some((b) => {
  92. const blockEnd = parseInt(b.end_hour);
  93. const blockStart = parseInt(b.init_hour);
  94. return slotEnd >= blockStart && slotStart <= blockEnd;
  95. });
  96. const hasValidSlots = (hoursCount) => {
  97. for (let s = 7; s + hoursCount <= 20; s++) {
  98. if (!slotConflicts(s, s + hoursCount, props.partialBlocks)) return true;
  99. }
  100. return false;
  101. };
  102. const availableServiceTypes = computed(() =>
  103. [
  104. {
  105. description: t("scheduling_page.service_types.integral.description"),
  106. hours: t("scheduling_page.service_types.integral.hours"),
  107. hoursCount: 8,
  108. key: "integral",
  109. label: t("scheduling_page.service_types.integral.label"),
  110. basePrice:
  111. props.provider?.daily_price_8h_base ??
  112. props.provider?.daily_price_8h ??
  113. null,
  114. price: props.provider?.daily_price_8h ?? null,
  115. },
  116. {
  117. description: t("scheduling_page.service_types.padrao.description"),
  118. hours: t("scheduling_page.service_types.padrao.hours"),
  119. hoursCount: 6,
  120. key: "padrao",
  121. label: t("scheduling_page.service_types.padrao.label"),
  122. basePrice:
  123. props.provider?.daily_price_6h_base ??
  124. props.provider?.daily_price_6h ??
  125. null,
  126. price: props.provider?.daily_price_6h ?? null,
  127. },
  128. {
  129. description: t("scheduling_page.service_types.meio_periodo.description"),
  130. hours: t("scheduling_page.service_types.meio_periodo.hours"),
  131. hoursCount: 4,
  132. key: "meio_periodo",
  133. label: t("scheduling_page.service_types.meio_periodo.label"),
  134. basePrice:
  135. props.provider?.daily_price_4h_base ??
  136. props.provider?.daily_price_4h ??
  137. null,
  138. price: props.provider?.daily_price_4h ?? null,
  139. },
  140. {
  141. description: t("scheduling_page.service_types.diaria_rapida.description"),
  142. hours: t("scheduling_page.service_types.diaria_rapida.hours"),
  143. hoursCount: 2,
  144. key: "diaria_rapida",
  145. label: t("scheduling_page.service_types.diaria_rapida.label"),
  146. basePrice:
  147. props.provider?.daily_price_2h_base ??
  148. props.provider?.daily_price_2h ??
  149. null,
  150. price: props.provider?.daily_price_2h ?? null,
  151. },
  152. ].filter((type) => hasValidSlots(type.hoursCount)),
  153. );
  154. const openInfo = (type) => {
  155. $q.dialog({
  156. component: ServiceTypeInfoDialog,
  157. componentProps: { serviceType: type },
  158. });
  159. };
  160. </script>
  161. <style scoped lang="scss">
  162. .sheet-card {
  163. border-radius: 20px 20px 0 0;
  164. }
  165. </style>