ProfileServiceDataDialog.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. <template>
  2. <q-dialog ref="dialogRef" persistent maximized transition-show="slide-left" transition-hide="slide-right">
  3. <div class="bg-page full-height column no-shadow">
  4. <div class="row items-center q-px-md q-pb-sm bg-white shadow-profile bg-surface dialog-safe-header">
  5. <q-btn v-close-popup icon="mdi-chevron-left" flat round dense color="primary" />
  6. <q-space />
  7. <span class="gradient-diarista font16 fontbold">{{ $t('profile.service_data.title') }}</span>
  8. <q-space />
  9. <div style="width: 32px"></div>
  10. </div>
  11. <div class="col overflow-auto">
  12. <div class="q-px-md q-pt-lg q-pb-xl justify-between column full-height">
  13. <div class="text-center q-mb-lg">
  14. <q-img :src="LogoDiariaCampos" style="max-width: 160px;" />
  15. </div>
  16. <div class="q-mb-lg">
  17. <div class="text-text q-mb-sm font12 fontbold">
  18. {{ $t('profile.service_data.price_8h_label') }}
  19. </div>
  20. <DefaultCurrencyInput
  21. v-model="form.daily_price_8h"
  22. class="q-mb-xs bg-surface"
  23. input-class="text-text"
  24. :error="!!priceError"
  25. :error-message="priceError"
  26. hide-bottom-space
  27. label=""
  28. />
  29. <div class="text-text text-center q-mb-lg font10 fontmedium">
  30. {{ $t('profile.service_data.price_min_max') }}
  31. </div>
  32. <div class="text-text text-center q-mb-sm font12 fontmedium">
  33. {{ $t('profile.service_data.shorter_services') }}
  34. </div>
  35. <div class="row q-col-gutter-sm q-mb-sm">
  36. <div class="col-4">
  37. <div class="text-text text-center q-mb-xs font11 fontbold">
  38. {{ $t('profile.service_data.up_to_6h') }}
  39. </div>
  40. <q-input
  41. :model-value="formatCurrency(form.daily_price_6h)"
  42. outlined
  43. dense
  44. readonly
  45. class="bg-surface"
  46. input-class="text-text text-center"
  47. />
  48. </div>
  49. <div class="col-4">
  50. <div class="text-text text-center q-mb-xs font11 fontbold">
  51. {{ $t('profile.service_data.up_to_4h') }}
  52. </div>
  53. <q-input
  54. :model-value="formatCurrency(form.daily_price_4h)"
  55. outlined
  56. dense
  57. readonly
  58. class="bg-surface"
  59. input-class="text-text text-center"
  60. />
  61. </div>
  62. <div class="col-4">
  63. <div class="text-text text-center q-mb-xs font11 fontbold">
  64. {{ $t('profile.service_data.up_to_2h') }}
  65. </div>
  66. <q-input
  67. :model-value="formatCurrency(form.daily_price_2h)"
  68. outlined
  69. dense
  70. readonly
  71. class="bg-surface"
  72. input-class="text-text text-center"
  73. />
  74. </div>
  75. </div>
  76. </div>
  77. <div class="text-text text-center font12">
  78. <span class="fontbold">
  79. {{ $t('profile.service_data.services_subtitle') }}
  80. </span>
  81. <div class="text-text q-mb-md text-center font12">
  82. {{ $t('profile.service_data.change_in_profile') }}
  83. </div>
  84. <div class="q-mb-lg">
  85. <div v-if="loadingServiceTypes" class="row justify-center q-py-md">
  86. <q-spinner color="primary" size="28px" />
  87. </div>
  88. <div v-else-if="serviceOptions.length" class="row q-col-gutter-sm">
  89. <div v-for="service in serviceOptions" :key="service.value" class="col-6">
  90. <q-checkbox
  91. :model-value="isServiceSelected(service.value)"
  92. :label="service.label"
  93. :disable="togglingService[service.value]"
  94. color="primary"
  95. class="text-text"
  96. keep-color
  97. @update:model-value="(checked) => onServiceToggle(service, checked)"
  98. />
  99. </div>
  100. </div>
  101. <div v-else class="text-grey-7 text-center q-py-md font12">
  102. {{ $t('profile.service_data.no_services') }}
  103. </div>
  104. <div class="text-grey-6 text-center q-mt-md font12">
  105. {{ $t('profile.service_data.services_hint') }}
  106. </div>
  107. </div>
  108. </div>
  109. <q-btn
  110. unelevated
  111. rounded
  112. no-caps
  113. color="primary"
  114. padding="4px 12px"
  115. class="full-width q-py-md"
  116. :label="$t('common.actions.update')"
  117. :loading="saving"
  118. :disable="!!priceError || saving"
  119. @click="save"
  120. />
  121. </div>
  122. </div>
  123. </div>
  124. </q-dialog>
  125. </template>
  126. <script setup>
  127. import { ref, computed, watch, onMounted } from 'vue';
  128. import { useDialogPluginComponent, useQuasar } from 'quasar';
  129. import { useI18n } from 'vue-i18n';
  130. import { getPublicServiceTypes } from 'src/api/serviceType';
  131. import { getProviderServiceTypes, createProviderServiceType, deleteProviderServiceType } from 'src/api/providerServiceType';
  132. import { getUser } from 'src/api/user';
  133. import { updateProvider } from 'src/api/provider';
  134. import { userStore } from 'src/stores/user';
  135. import DefaultCurrencyInput from 'src/components/defaults/DefaultCurrencyInput.vue';
  136. import LogoDiariaCampos from 'src/assets/logo_diaria_campos_login.svg';
  137. defineEmits([...useDialogPluginComponent.emits]);
  138. const { dialogRef, onDialogOK } = useDialogPluginComponent();
  139. const $q = useQuasar();
  140. const { t } = useI18n();
  141. const user = userStore();
  142. const form = ref({
  143. daily_price_8h: null,
  144. daily_price_6h: null,
  145. daily_price_4h: null,
  146. daily_price_2h: null,
  147. });
  148. const serviceOptions = ref([]);
  149. const providerServiceTypes = ref([]);
  150. const loadingServiceTypes = ref(false);
  151. const togglingService = ref({});
  152. const saving = ref(false);
  153. const priceError = computed(() => {
  154. const value = Number(form.value.daily_price_8h);
  155. if (!form.value.daily_price_8h && form.value.daily_price_8h !== 0) return '';
  156. if (Number.isNaN(value)) return t('validation.rules.required');
  157. if (value < 100) return t('profile.service_data.price_min_error');
  158. if (value > 500) return t('profile.service_data.price_max_error');
  159. return '';
  160. });
  161. const formatCurrency = (value) => {
  162. if (value === null || value === undefined || Number.isNaN(Number(value))) return 'R$ 0,00';
  163. return Number(value).toLocaleString('pt-BR', {
  164. style: 'currency',
  165. currency: 'BRL',
  166. minimumFractionDigits: 2,
  167. });
  168. };
  169. const isServiceSelected = (serviceTypeId) => {
  170. return providerServiceTypes.value.some((pst) => pst.service_type_id === serviceTypeId);
  171. };
  172. watch(
  173. () => form.value.daily_price_8h,
  174. (value) => {
  175. const price = Number(value);
  176. if (!price || Number.isNaN(price)) {
  177. form.value.daily_price_6h = null;
  178. form.value.daily_price_4h = null;
  179. form.value.daily_price_2h = null;
  180. return;
  181. }
  182. form.value.daily_price_6h = Number((price * 0.85).toFixed(2));
  183. form.value.daily_price_4h = Number((price * 0.55).toFixed(2));
  184. form.value.daily_price_2h = Number((price * 0.30).toFixed(2));
  185. },
  186. { immediate: true },
  187. );
  188. const loadData = async () => {
  189. try {
  190. const userData = await getUser();
  191. form.value.daily_price_8h = userData.provider_daily_price_8h ?? null;
  192. form.value.daily_price_6h = userData.provider_daily_price_6h ?? null;
  193. form.value.daily_price_4h = userData.provider_daily_price_4h ?? null;
  194. form.value.daily_price_2h = userData.provider_daily_price_2h ?? null;
  195. } catch (error) {
  196. console.error('Erro ao carregar dados do prestador:', error);
  197. }
  198. };
  199. const loadServiceTypes = async () => {
  200. loadingServiceTypes.value = true;
  201. try {
  202. const [allTypes, providerTypes] = await Promise.all([
  203. getPublicServiceTypes(),
  204. getProviderServiceTypes(user.user.provider_id),
  205. ]);
  206. serviceOptions.value = (Array.isArray(allTypes) ? allTypes : [])
  207. .filter((item) => item?.is_active !== false)
  208. .map((item) => ({ label: item.description, value: Number(item.id) }))
  209. .filter((item) => Number.isInteger(item.value) && item.value > 0);
  210. providerServiceTypes.value = Array.isArray(providerTypes) ? providerTypes : [];
  211. } catch (error) {
  212. console.error('Erro ao carregar tipos de serviço:', error);
  213. $q.notify({ type: 'negative', message: t('http.errors.failed') });
  214. } finally {
  215. loadingServiceTypes.value = false;
  216. }
  217. };
  218. const onServiceToggle = async (service, checked) => {
  219. togglingService.value = { ...togglingService.value, [service.value]: true };
  220. try {
  221. if (checked) {
  222. const created = await createProviderServiceType(user.user.provider_id, {
  223. service_type_id: service.value,
  224. });
  225. providerServiceTypes.value = [...providerServiceTypes.value, created];
  226. } else {
  227. const existing = providerServiceTypes.value.find((pst) => pst.service_type_id === service.value);
  228. if (existing) {
  229. await deleteProviderServiceType(user.user.provider_id, existing.id);
  230. providerServiceTypes.value = providerServiceTypes.value.filter((pst) => pst.id !== existing.id);
  231. }
  232. }
  233. } catch {
  234. $q.notify({ type: 'negative', message: t('http.errors.failed') });
  235. } finally {
  236. const updated = { ...togglingService.value };
  237. delete updated[service.value];
  238. togglingService.value = updated;
  239. }
  240. };
  241. const save = async () => {
  242. if (priceError.value) return;
  243. saving.value = true;
  244. try {
  245. await updateProvider(user.user.provider_id, {
  246. daily_price_8h: form.value.daily_price_8h,
  247. daily_price_6h: form.value.daily_price_6h,
  248. daily_price_4h: form.value.daily_price_4h,
  249. daily_price_2h: form.value.daily_price_2h,
  250. });
  251. onDialogOK();
  252. } catch {
  253. $q.notify({ type: 'negative', message: t('http.errors.failed') });
  254. } finally {
  255. saving.value = false;
  256. }
  257. };
  258. onMounted(async () => {
  259. await Promise.all([loadData(), loadServiceTypes()]);
  260. });
  261. </script>
  262. <style scoped lang="scss">
  263. :deep(.q-field--outlined .q-field__control) {
  264. border-radius: 10px;
  265. }
  266. </style>