ProfilePage.vue 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. <template>
  2. <q-page class="column q-gutter-md">
  3. <DefaultHeaderPage />
  4. <q-card bordered class="q-py-md q-pr-sm" flat style="border-radius: 10px">
  5. <div class="column q-gutter-md">
  6. <div class="column q-gutter-sm">
  7. <div class="text-subtitle1 text-weight-bold text-primary">
  8. Dados pessoais
  9. </div>
  10. <q-separator />
  11. <div class="grid-2">
  12. <q-input
  13. :model-value="userData.name"
  14. bg-color="white"
  15. label="Nome"
  16. outlined
  17. readonly
  18. />
  19. <q-input
  20. :model-value="userData.document"
  21. bg-color="white"
  22. label="Número de documento"
  23. outlined
  24. readonly
  25. />
  26. </div>
  27. </div>
  28. <div class="column q-gutter-sm">
  29. <div class="text-subtitle1 text-weight-bold text-primary">Acesso</div>
  30. <q-separator />
  31. <div class="grid-3">
  32. <q-input
  33. :model-value="userData.email"
  34. bg-color="white"
  35. label="Email"
  36. outlined
  37. readonly
  38. />
  39. <q-input
  40. bg-color="white"
  41. label="Senha"
  42. outlined
  43. readonly
  44. type="password"
  45. />
  46. <q-input
  47. bg-color="white"
  48. label="Confirmar Senha"
  49. outlined
  50. readonly
  51. type="password"
  52. />
  53. </div>
  54. </div>
  55. <div class="row justify-end">
  56. <q-btn
  57. class="btn-custom-default"
  58. color="secondary"
  59. label="Alterar"
  60. text-color="primary"
  61. @click="openEditDialog"
  62. />
  63. </div>
  64. </div>
  65. <q-inner-loading :showing="loading">
  66. <q-spinner color="primary" size="40px" />
  67. </q-inner-loading>
  68. </q-card>
  69. <ProfileEditDialog
  70. v-model="editDialog"
  71. v-model:form="editForm"
  72. :input-rules="inputRules"
  73. :server-errors="serverErrors"
  74. :submitting="submitting"
  75. @close="closeEditDialog"
  76. @submit="submitProfileUpdate"
  77. />
  78. </q-page>
  79. </template>
  80. <script setup>
  81. import { computed, onMounted, ref, watch } from "vue";
  82. import { updateUser } from "src/api/user";
  83. import { useInputRules } from "src/composables/useInputRules";
  84. import { useSubmitHandler } from "src/composables/useSubmitHandler";
  85. import { useFormUpdateTracker } from "src/composables/useFormUpdateTracker";
  86. import { userStore } from "src/stores/user";
  87. import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
  88. import ProfileEditDialog from "./components/ProfileEditDialog.vue";
  89. defineOptions({ name: "ProfilePage" });
  90. const { inputRules } = useInputRules();
  91. const currentUserStore = userStore();
  92. const {
  93. execute,
  94. loading: submitting,
  95. serverErrors,
  96. } = useSubmitHandler(() => closeEditDialog());
  97. const {
  98. form: editForm,
  99. getUpdatedFields,
  100. hasUpdatedFields,
  101. resetUpdateForm,
  102. setUpdateFormAsOriginal,
  103. } = useFormUpdateTracker({
  104. name: "",
  105. document: "",
  106. email: "",
  107. password: "",
  108. confirmPassword: "",
  109. });
  110. const loading = ref(false);
  111. const editDialog = ref(false);
  112. const userData = computed(() => {
  113. const user = currentUserStore.user ?? {};
  114. return {
  115. id: user?.id ?? null,
  116. name: user?.name ?? user?.full_name ?? "",
  117. document: user?.document_number ?? "",
  118. email: user?.email ?? "",
  119. };
  120. });
  121. const loadUser = async () => {
  122. loading.value = true;
  123. try {
  124. await currentUserStore.fetchUser();
  125. } finally {
  126. loading.value = false;
  127. }
  128. };
  129. const syncFormWithUser = () => {
  130. Object.assign(editForm, {
  131. name: userData.value.name,
  132. document: userData.value.document,
  133. email: userData.value.email,
  134. password: "",
  135. confirmPassword: "",
  136. });
  137. setUpdateFormAsOriginal();
  138. };
  139. const openEditDialog = () => {
  140. syncFormWithUser();
  141. serverErrors.value = {};
  142. editDialog.value = true;
  143. };
  144. const closeEditDialog = () => {
  145. editDialog.value = false;
  146. resetUpdateForm();
  147. };
  148. const buildPayload = () => {
  149. const updated = { ...getUpdatedFields.value };
  150. if (updated.document) {
  151. updated.document_number = updated.document;
  152. delete updated.document;
  153. }
  154. if (!updated.password) {
  155. delete updated.password;
  156. }
  157. delete updated.confirmPassword;
  158. return updated;
  159. };
  160. const submitProfileUpdate = async () => {
  161. if (!userData.value.id) return;
  162. if (!hasUpdatedFields.value) {
  163. closeEditDialog();
  164. return;
  165. }
  166. await execute(async () => {
  167. const payload = buildPayload();
  168. await updateUser(payload, userData.value.id);
  169. await loadUser();
  170. setUpdateFormAsOriginal();
  171. });
  172. };
  173. watch(userData, syncFormWithUser, { immediate: true });
  174. onMounted(loadUser);
  175. </script>
  176. <style scoped>
  177. .grid-2 {
  178. display: grid;
  179. grid-template-columns: 1fr 240px;
  180. gap: 12px;
  181. }
  182. .grid-3 {
  183. display: grid;
  184. grid-template-columns: repeat(3, 1fr);
  185. gap: 12px;
  186. }
  187. @media (max-width: 900px) {
  188. .grid-2,
  189. .grid-3 {
  190. grid-template-columns: 1fr;
  191. }
  192. }
  193. </style>