Ver código fonte

criando funcao de anexar fotos no backoffice

Gustavo Zanatta 3 dias atrás
pai
commit
bbd359ad9a

+ 4 - 2
src/api/client.js

@@ -15,8 +15,10 @@ export const createClient = async (client) => {
   return data.payload;
 };
 
-export const updateClient = async (client, id) => {
-  const { data } = await api.put(`/client/${id}`, client);
+export const updateClient = async (client, id, hasFile = false) => {
+  const { data } = hasFile
+    ? await api.post(`/client/${id}`, client, { headers: { 'Content-Type': 'multipart/form-data' } })
+    : await api.put(`/client/${id}`, client);
   return data.payload;
 };
 

+ 4 - 2
src/api/provider.js

@@ -21,8 +21,10 @@ export const createProvider = async (provider) => {
   return data.payload;
 };
 
-export const updateProvider = async (provider, id) => {
-  const { data } = await api.put(`/provider/${id}`, provider);
+export const updateProvider = async (provider, id, hasFile = false) => {
+  const { data } = hasFile
+    ? await api.post(`/provider/${id}`, provider, { headers: { 'Content-Type': 'multipart/form-data' } })
+    : await api.put(`/provider/${id}`, provider);
   return data.payload;
 };
 

+ 58 - 13
src/pages/client/components/AddEditClientDialog.vue

@@ -1,6 +1,6 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
-    <q-card class="q-dialog-plugin" style="width: 1000px; max-width: 90vw">
+    <q-card class="q-dialog-plugin" style="width: 1100px; max-width: 90vw">
       <DefaultDialogHeader :title="title" @close="onDialogCancel" />
       
       <q-tabs
@@ -26,6 +26,30 @@
         <q-tab-panel name="data">
           <q-form ref="formRef" @submit="onOKClick">
             <q-card-section class="row q-col-gutter-sm">
+              <div v-if="client" class="col-12 flex flex-center column q-pb-sm">
+                <q-avatar size="80px" color="grey-3">
+                  <img v-if="avatarPreview" :src="avatarPreview" style="object-fit: cover; width: 100%; height: 100%;" />
+                  <q-icon v-else name="mdi-account" size="50px" color="grey-6" />
+                </q-avatar>
+                <input
+                  ref="fileInputRef"
+                  type="file"
+                  accept="image/jpeg,image/png,image/webp"
+                  style="display: none;"
+                  @change="onFileSelected"
+                />
+                <q-btn
+                  flat
+                  no-caps
+                  dense
+                  color="primary"
+                  size="sm"
+                  class="q-mt-xs"
+                  label="Alterar foto"
+                  @click="fileInputRef.click()"
+                />
+              </div>
+
               <UserSelect
                 v-model="selectedUser"
                 :label="$t('common.terms.user')"
@@ -58,7 +82,7 @@
                 label="OK"
                 :type="'submit'"
                 :loading="loading"
-                :disable="!hasUpdatedFields"
+                :disable="!hasUpdatedFields && !avatarFile"
               />
             </q-card-actions>
           </q-form>
@@ -96,7 +120,7 @@
 </template>
 
 <script setup>
-import { ref, useTemplateRef, watch, computed } from 'vue';
+import { ref, useTemplateRef, watch, computed, onMounted } from 'vue';
 import { useInputRules } from 'src/composables/useInputRules';
 import { useDialogPluginComponent } from 'quasar';
 import { useI18n } from 'vue-i18n';
@@ -135,7 +159,10 @@ const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
   useDialogPluginComponent();
 
 const formRef = useTemplateRef('formRef');
+const fileInputRef = useTemplateRef('fileInputRef');
 const tab = ref('data');
+const avatarFile = ref(null);
+const avatarPreview = ref(null);
 
 const { form, getUpdatedFields, hasUpdatedFields } = useFormUpdateTracker({
   user_id: client ? client?.user_id : null,
@@ -162,22 +189,40 @@ const validateDocument = (val) => {
   return validateCpfCnpj(val) || t('validation.rules.cpf') + ' / ' + t('validation.rules.cnpj');
 };
 
+const onFileSelected = (event) => {
+  const file = event.target.files[0];
+  if (!file) return;
+  avatarFile.value = file;
+  avatarPreview.value = URL.createObjectURL(file);
+};
+
 const onOKClick = async () => {
-  let response;
   if (client) {
-    await submitForm(() => {
-      response = updateClient(getUpdatedFields.value, client.id);
-    });
+    if (avatarFile.value) {
+      const formData = new FormData();
+      const fields = getUpdatedFields.value;
+      Object.entries(fields).forEach(([key, value]) => {
+        if (value !== null && value !== undefined) {
+          formData.append(key, String(value));
+        }
+      });
+      formData.append('avatar', avatarFile.value);
+      formData.append('_method', 'PUT');
+      await submitForm(() => updateClient(formData, client.id, true));
+    } else {
+      await submitForm(() => updateClient(getUpdatedFields.value, client.id));
+    }
   } else {
-    await submitForm(() => {
-      response = createClient({ ...form });
-    });
-  }
-  if(response.data.success == true) {
-    onDialogOK(true);
+    await submitForm(() => createClient({ ...form }));
   }
 };
 
+onMounted(() => {
+  if (client) {
+    avatarPreview.value = client.profile_media?.url ?? null;
+  }
+});
+
 watch(selectedUser, () => {
   form.user_id = selectedUser.value?.value;
 });

+ 90 - 6
src/pages/provider/components/AddEditProviderDialog.vue

@@ -1,3 +1,4 @@
+<!-- eslint-disable @intlify/vue-i18n/no-raw-text -->
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
     <q-card class="q-dialog-plugin column full-width" style="width: 900px; max-width: 80vw;height: 90vh;">
@@ -117,10 +118,30 @@
 
                 <div class="col-12 q-mt-md">
                   <div class="row q-col-gutter-md">
-                    <div class="col-auto flex items-center">
+                    <div class="col-auto flex flex-center column">
                       <q-avatar size="80px" color="grey-3">
-                        <q-icon name="mdi-account" size="50px" color="grey-6" />
+                        <img v-if="avatarPreview" :src="avatarPreview" style="object-fit: cover; width: 100%; height: 100%;" />
+                        <q-icon v-else name="mdi-account" size="50px" color="grey-6" />
                       </q-avatar>
+                      <input
+                        v-if="provider"
+                        ref="fileInputRef"
+                        type="file"
+                        accept="image/jpeg,image/png,image/webp"
+                        style="display: none;"
+                        @change="onFileSelected"
+                      />
+                      <q-btn
+                        v-if="provider"
+                        flat
+                        no-caps
+                        dense
+                        color="primary"
+                        size="sm"
+                        class="q-mt-xs"
+                        label="Alterar foto"
+                        @click="fileInputRef.click()"
+                      />
                     </div>
 
                     <div class="col row q-col-gutter-sm">
@@ -156,12 +177,33 @@
 
                       </div>
 
-                      <div class="col-md-6 col-12">
+                      <div class="col-12">
 
                         <div class="text-subtitle2 text-grey-7 q-mb-sm">
                           {{ $t('provider.fields.document_verified') }}
                         </div>
 
+                        <div v-if="documentFrontUrl || documentBackUrl" class="row q-col-gutter-sm q-mb-sm">
+                          <div v-if="documentFrontUrl" class="col">
+                            <div class="text-caption text-grey-6 q-mb-xs">Frente</div>
+                            <q-img
+                              :src="documentFrontUrl"
+                              fit="cover"
+                              style="height: 110px; width: 300px; object-fit: contain;border-radius: 6px; cursor: pointer;"
+                              @click="docLightbox = documentFrontUrl"
+                            />
+                          </div>
+                          <div v-if="documentBackUrl" class="col">
+                            <div class="text-caption text-grey-6 q-mb-xs">Verso</div>
+                            <q-img
+                              :src="documentBackUrl"
+                              fit="cover"
+                              style="height: 110px; width: 300px; object-fit: contain;border-radius: 6px; cursor: pointer;"
+                              @click="docLightbox = documentBackUrl"
+                            />
+                          </div>
+                        </div>
+
                         <q-checkbox
                           v-model="form.document_verified"
                           label="Validar documentos"
@@ -186,7 +228,7 @@
                   type="submit"
                   unelevated
                   :loading="loading"
-                  :disable="!hasUpdatedFields"
+                  :disable="!hasUpdatedFields && !avatarFile"
                 />
               </q-card-actions>
             </q-form>
@@ -223,13 +265,20 @@
       </div>
     </q-card>
   </q-dialog>
+
+  <q-dialog v-model="docLightboxOpen" maximized>
+    <div class="flex flex-center bg-black" style="height: 100%;" @click="docLightbox = null">
+      <q-img :src="docLightbox" fit="contain" style="max-height: 100vh; max-width: 100vw;" />
+      <q-btn flat round icon="mdi-close" color="white" class="absolute-top-right q-ma-sm" @click.stop="docLightbox = null" />
+    </div>
+  </q-dialog>
 </template>
 <script setup>
 import { ref, useTemplateRef, onMounted, watch, computed } from "vue";
 import { useInputRules } from "src/composables/useInputRules";
 import { useDialogPluginComponent } from "quasar";
 import { useI18n } from "vue-i18n";
-import { createProvider, updateProvider } from "src/api/provider";
+import { createProvider, updateProvider, getProvider } from "src/api/provider";
 import { useFormUpdateTracker } from "src/composables/useFormUpdateTracker";
 import { useSubmitHandler } from "src/composables/useSubmitHandler";
 import { dynamicCpfCnpjMask, validateCpfCnpj, calculateDailyPrices } from "src/helpers/utils";
@@ -274,7 +323,17 @@ const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
   useDialogPluginComponent();
 
 const formRef = useTemplateRef("formRef");
+const fileInputRef = useTemplateRef("fileInputRef");
 const tab = ref("data");
+const avatarFile = ref(null);
+const avatarPreview = ref(null);
+const documentFrontUrl = ref(null);
+const documentBackUrl = ref(null);
+const docLightbox = ref(null);
+const docLightboxOpen = computed({
+  get: () => !!docLightbox.value,
+  set: (v) => { if (!v) docLightbox.value = null; },
+});
 
 const { form, getUpdatedFields, hasUpdatedFields } = useFormUpdateTracker({
   user_id: provider ? provider?.user_id : null,
@@ -310,9 +369,29 @@ const validateDocument = (val) => {
   return validateCpfCnpj(val) || t("validation.rules.cpf") + " / " + t("validation.rules.cnpj");
 };
 
+const onFileSelected = (event) => {
+  const file = event.target.files[0];
+  if (!file) return;
+  avatarFile.value = file;
+  avatarPreview.value = URL.createObjectURL(file);
+};
+
 const onOKClick = async () => {
   if (provider) {
-    await submitForm(() => updateProvider(getUpdatedFields.value, provider.id));
+    if (avatarFile.value) {
+      const formData = new FormData();
+      const fields = getUpdatedFields.value;
+      Object.entries(fields).forEach(([key, value]) => {
+        if (value !== null && value !== undefined) {
+          formData.append(key, String(value));
+        }
+      });
+      formData.append('avatar', avatarFile.value);
+      formData.append('_method', 'PUT');
+      await submitForm(() => updateProvider(formData, provider.id, true));
+    } else {
+      await submitForm(() => updateProvider(getUpdatedFields.value, provider.id));
+    }
   } else {
     await submitForm(() => createProvider({ ...form }));
   }
@@ -338,6 +417,11 @@ onMounted(async () => {
       label: provider.user?.name || "",
       value: provider.user?.id,
     };
+    avatarPreview.value = provider.profile_media?.url ?? null;
+
+    const full = await getProvider(provider.id);
+    documentFrontUrl.value = full.document_front_media?.url ?? null;
+    documentBackUrl.value = full.document_back_media?.url ?? null;
   }
 });
 </script>