AddEditPartnerDialog.vue 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. <template>
  2. <q-dialog ref="dialogRef" @hide="onDialogHide">
  3. <q-card
  4. class="q-dialog-plugin overflow-hidden"
  5. style="width: 860px; max-width: 95vw"
  6. >
  7. <DefaultDialogHeader
  8. :title="partner ? 'Editar Sócio' : 'Adicionar Sócio'"
  9. @close="onDialogCancel"
  10. />
  11. <q-form ref="formRef" @submit="onOKClick">
  12. <q-card-section class="q-pt-none">
  13. <div class="column items-center q-mb-md">
  14. <AvatarImageComponent
  15. ref="avatarRef"
  16. @update:file="onAvatarChange"
  17. />
  18. </div>
  19. <div class="row q-col-gutter-sm">
  20. <DefaultInput
  21. v-model="form.name"
  22. label="Nome completo"
  23. class="col-8"
  24. outlined
  25. :rules="[inputRules.required]"
  26. />
  27. <DefaultInput
  28. v-model="form.role"
  29. label="Função"
  30. class="col-4"
  31. outlined
  32. />
  33. <DefaultInput
  34. v-model="form.social_name"
  35. label="Nome social"
  36. class="col-6"
  37. outlined
  38. />
  39. <DefaultInput
  40. v-model="form.cpf"
  41. label="CPF"
  42. class="col-3"
  43. outlined
  44. :mask="masks.Brasil.cpf"
  45. :rules="[inputRules.required]"
  46. />
  47. <DefaultInput v-model="form.rg" label="RG" class="col-3" outlined />
  48. <DefaultInput
  49. v-model="birthDateDisplay"
  50. label="Data de Nascimento"
  51. class="col-3"
  52. outlined
  53. :mask="masks.Brasil.date"
  54. placeholder="DD/MM/AAAA"
  55. />
  56. <DefaultInput
  57. v-model="form.participation"
  58. label="Participação (%)"
  59. class="col-3"
  60. outlined
  61. type="number"
  62. min="0"
  63. max="100"
  64. />
  65. <DefaultInput
  66. v-model="form.email"
  67. label="E-mail"
  68. class="col-6"
  69. outlined
  70. :rules="[inputRules.email]"
  71. />
  72. <DefaultInput
  73. v-model="form.secondary_email"
  74. label="E-mail Secundário"
  75. class="col-6"
  76. outlined
  77. :rules="[inputRules.email]"
  78. />
  79. <DefaultInput
  80. v-model="form.phone_number"
  81. label="Telefone"
  82. class="col-6"
  83. outlined
  84. :mask="masks.Brasil.telefone"
  85. />
  86. <DefaultInput
  87. v-model="form.cell_number"
  88. label="Celular"
  89. class="col-6"
  90. outlined
  91. :mask="masks.Brasil.celular"
  92. />
  93. <DefaultCepInput
  94. v-model="form.postal_code"
  95. class="col-6"
  96. outlined
  97. @rua="form.street = $event"
  98. @bairro="form.neighborhood = $event"
  99. @uf="stateSelectRef?.selectStateByCode($event)"
  100. @cidade="citySelectRef?.selectCityByName($event)"
  101. />
  102. <DefaultInput
  103. v-model="form.street"
  104. label="Endereço"
  105. class="col-6"
  106. outlined
  107. />
  108. <DefaultInput
  109. v-model="form.address_number"
  110. label="Número"
  111. class="col-6"
  112. outlined
  113. />
  114. <DefaultInput
  115. v-model="form.neighborhood"
  116. label="Bairro"
  117. class="col-4"
  118. outlined
  119. />
  120. <StateSelect
  121. ref="stateSelectRef"
  122. v-model="selectedState"
  123. label="Estado"
  124. class="col-4"
  125. outlined
  126. />
  127. <CitySelect
  128. ref="citySelectRef"
  129. v-model="selectedCity"
  130. label="Cidade"
  131. class="col-4"
  132. outlined
  133. :state="selectedState"
  134. />
  135. <DefaultInput
  136. v-model="form.complement"
  137. label="Complemento"
  138. class="col-12"
  139. outlined
  140. />
  141. </div>
  142. </q-card-section>
  143. <q-card-actions>
  144. <q-space />
  145. <q-btn
  146. outline
  147. color="negative"
  148. label="Cancelar"
  149. @click="onDialogCancel"
  150. />
  151. <q-btn
  152. color="primary-2"
  153. :label="partner ? 'Salvar' : 'Adicionar'"
  154. type="submit"
  155. :loading="loading"
  156. />
  157. </q-card-actions>
  158. </q-form>
  159. </q-card>
  160. </q-dialog>
  161. </template>
  162. <script setup>
  163. import { ref, watch, onMounted } from "vue";
  164. import { useDialogPluginComponent } from "quasar";
  165. import { useInputRules } from "src/composables/useInputRules";
  166. import { useFormUpdateTracker } from "src/composables/useFormUpdateTracker";
  167. import { useSubmitHandler } from "src/composables/useSubmitHandler";
  168. import { createPartner, updatePartner } from "src/api/unit_partner";
  169. import masks from "src/helpers/masks";
  170. import { formatDateDMYtoYMD, formatDateYMDtoDMY } from "src/helpers/utils";
  171. import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
  172. import DefaultInput from "src/components/defaults/DefaultInput.vue";
  173. import DefaultCepInput from "src/components/defaults/DefaultCepInput.vue";
  174. import AvatarImageComponent from "src/components/shared/AvatarImageComponent.vue";
  175. import StateSelect from "src/components/selects/StateSelect.vue";
  176. import CitySelect from "src/components/selects/CitySelect.vue";
  177. defineEmits([...useDialogPluginComponent.emits]);
  178. const { partner, unitId, offlineMode } = defineProps({
  179. partner: {
  180. type: Object,
  181. default: null,
  182. },
  183. unitId: {
  184. type: Number,
  185. default: null,
  186. },
  187. offlineMode: {
  188. type: Boolean,
  189. default: false,
  190. },
  191. });
  192. const { inputRules } = useInputRules();
  193. const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
  194. useDialogPluginComponent();
  195. const formRef = ref(null);
  196. const avatarRef = ref(null);
  197. const stateSelectRef = ref(null);
  198. const citySelectRef = ref(null);
  199. const selectedState = ref(null);
  200. const selectedCity = ref(null);
  201. const avatarChanged = ref(false);
  202. const avatarFile = ref(null);
  203. // Exibe DD/MM/YYYY para o usuário; form.birth_date armazena YYYY-MM-DD para o backend
  204. const birthDateDisplay = ref(
  205. partner?.birth_date ? formatDateYMDtoDMY(partner.birth_date) : null,
  206. );
  207. watch(birthDateDisplay, (val) => {
  208. try {
  209. form.birth_date = val?.length === 10 ? formatDateDMYtoYMD(val) : null;
  210. } catch {
  211. form.birth_date = null;
  212. }
  213. });
  214. const { form, getFormAsFormData } = useFormUpdateTracker({
  215. unit_id: unitId,
  216. name: partner?.name ?? null,
  217. social_name: partner?.social_name ?? null,
  218. role: partner?.role ?? null,
  219. cpf: partner?.cpf ?? null,
  220. rg: partner?.rg ?? null,
  221. birth_date: partner?.birth_date ?? null,
  222. participation: partner?.participation ?? null,
  223. email: partner?.email ?? null,
  224. secondary_email: partner?.secondary_email ?? null,
  225. phone_number: partner?.phone_number ?? null,
  226. cell_number: partner?.cell_number ?? null,
  227. postal_code: partner?.postal_code ?? null,
  228. street: partner?.street ?? null,
  229. address_number: partner?.address_number ?? null,
  230. neighborhood: partner?.neighborhood ?? null,
  231. complement: partner?.complement ?? null,
  232. city_id: partner?.city_id ?? null,
  233. state_id: partner?.state_id ?? null,
  234. });
  235. watch(selectedState, (state) => {
  236. form.state_id = state?.value ?? null;
  237. });
  238. watch(selectedCity, (city) => {
  239. form.city_id = city?.value ?? null;
  240. });
  241. function onAvatarChange(file) {
  242. avatarChanged.value = true;
  243. avatarFile.value = file;
  244. }
  245. const { loading, execute } = useSubmitHandler({
  246. formRef,
  247. onSuccess: (result) => onDialogOK(result),
  248. });
  249. async function onOKClick() {
  250. if (offlineMode) {
  251. const partnerData = { ...form };
  252. if (avatarFile.value instanceof File) {
  253. if (partner?.avatar_url?.startsWith("blob:")) {
  254. URL.revokeObjectURL(partner.avatar_url);
  255. }
  256. partnerData.avatar = avatarFile.value;
  257. partnerData.avatar_url = URL.createObjectURL(avatarFile.value);
  258. } else if (partner?.avatar_url) {
  259. partnerData.avatar_url = partner.avatar_url;
  260. }
  261. onDialogOK(partnerData);
  262. return;
  263. }
  264. await execute(() => {
  265. const formData = getFormAsFormData();
  266. if (avatarChanged.value) {
  267. formData.append("avatar", avatarFile.value ?? "");
  268. }
  269. if (partner) {
  270. return updatePartner(partner.id, formData);
  271. }
  272. return createPartner(formData);
  273. });
  274. }
  275. onMounted(() => {
  276. if (!partner) return;
  277. if (partner.avatar_url) {
  278. avatarRef.value?.setImageUrl(partner.avatar_url);
  279. }
  280. if (partner.state_id) {
  281. stateSelectRef.value?.selectStateById(partner.state_id);
  282. }
  283. if (partner.city_id) {
  284. citySelectRef.value?.selectCityById(partner.city_id);
  285. }
  286. });
  287. </script>