Sfoglia il codice sorgente

feat: ✨ feat (documento dependente) adicionado documento no dependente

foi adicionada a obrigatoriedade para preencher um documento ao adicionar dependente, que fica visualizavel para o adm aprovar/recusar

fase:dev | origin:escopo
Gustavo Zanatta 2 giorni fa
parent
commit
aa7ba9b877

+ 12 - 1
src/api/profile.js

@@ -11,7 +11,18 @@ export const getDependentsByUser = async (userId) => {
 };
 
 export const createDependent = async (dependent) => {
-  const { data } = await api.post("/user-dependent", dependent);
+  const form = new FormData();
+  form.append("responsible_user_id", dependent.responsible_user_id);
+  form.append("name", dependent.name);
+  form.append("kinship", dependent.kinship);
+
+  if (dependent.document instanceof File) {
+    form.append("document", dependent.document);
+  }
+
+  const { data } = await api.post("/user-dependent", form, {
+    headers: { "Content-Type": "multipart/form-data" },
+  });
   return data.payload;
 };
 

+ 21 - 11
src/components/defaults/DefaultFilePicker.vue

@@ -55,7 +55,7 @@
         </template>
 
         <div
-          v-else-if="type === 'image'"
+          v-else-if="isImagePreview"
           class="file-picker-image"
           :style="{ backgroundImage: `url('${preview}')` }"
         />
@@ -128,9 +128,16 @@ const objectUrl = ref(null);
 
 const required = computed(() => props.rules.some((r) => r?.$id === "required"));
 
+const isImagePreview = computed(() => {
+  if (internalFile.value instanceof File) {
+    return internalFile.value.type.startsWith("image/");
+  }
+  return props.type === "image";
+});
+
 const preview = computed(() => {
   if (internalFile.value instanceof File) {
-    return props.type === "image" ? objectUrl.value : "file_selected";
+    return isImagePreview.value ? objectUrl.value : "file_selected";
   }
   return props.initialImage || null;
 });
@@ -160,14 +167,17 @@ function handleDrop(event) {
   isDragging.value = false;
   const file = event.dataTransfer?.files?.[0];
   if (!file) return;
-  const acceptedMime = props.accept;
-  if (acceptedMime.endsWith("/*")) {
-    const baseMime = acceptedMime.replace("/*", "");
-    if (file.type.startsWith(baseMime + "/")) internalFile.value = file;
-  } else {
-    if (acceptedMime.split(",").map((m) => m.trim()).includes(file.type)) {
-      internalFile.value = file;
-    }
+  const acceptedMimes = props.accept
+    .split(",")
+    .map((m) => m.trim())
+    .filter(Boolean);
+
+  const matches = acceptedMimes.some((mime) =>
+    mime.endsWith("/*") ? file.type.startsWith(mime.slice(0, -1)) : mime === file.type,
+  );
+
+  if (matches) {
+    internalFile.value = file;
   }
 }
 
@@ -185,7 +195,7 @@ watch(internalFile, (newFile) => {
     objectUrl.value = null;
   }
   if (newFile instanceof File) {
-    if (props.type === "image") {
+    if (newFile.type.startsWith("image/")) {
       objectUrl.value = URL.createObjectURL(newFile);
     }
     model.value = newFile;

+ 2 - 0
src/i18n/locales/en.json

@@ -470,6 +470,8 @@
     "admission": "Admission",
     "dependent": "Dependent",
     "dependents": "Dependents",
+    "dependent_document": "Dependent document",
+    "dependent_document_hint": "Upload an image or PDF of the document (max. 10 MB). The dependent stays pending until the administration approves it.",
     "import_afastamentos": "Absences",
     "search_placeholder": "Search Members",
     "no_dependents": "No dependents registered",

+ 2 - 0
src/i18n/locales/es.json

@@ -470,6 +470,8 @@
     "admission": "Admisión",
     "dependent": "Dependiente",
     "dependents": "Dependientes",
+    "dependent_document": "Documento del dependiente",
+    "dependent_document_hint": "Envíe una imagen o PDF del documento (máx. 10 MB). El dependiente quedará pendiente hasta la aprobación de la administración.",
     "import_afastamentos": "Ausencias",
     "search_placeholder": "Buscar por asociados",
     "no_dependents": "Ningún dependiente registrado",

+ 2 - 0
src/i18n/locales/pt.json

@@ -470,6 +470,8 @@
     "admission": "Admissão",
     "dependent": "Dependente",
     "dependents": "Dependentes",
+    "dependent_document": "Documento do dependente",
+    "dependent_document_hint": "Envie uma imagem ou PDF do documento (máx. 10 MB). O dependente ficará pendente até a aprovação da administração.",
     "import_afastamentos": "Afastamentos",
     "search_placeholder": "Buscar por associados",
     "no_dependents": "Nenhum dependente cadastrado",

+ 18 - 20
src/pages/associado/profile/components/AddEditDependentDialog.vue

@@ -19,19 +19,22 @@
             :options="kinshipOptions"
             emit-value
             map-options
-            :class="dependent ? 'col-md-6 col-12' : 'col-12'"
+            class="col-12"
           />
-          <DefaultSelect
-            v-if="dependent"
-            v-model="form.status"
-            v-model:error="validationErrors.status"
+          <DefaultFilePicker
+            v-if="!dependent"
+            v-model="documentFile"
             :rules="[inputRules.required]"
-            :label="$t('common.terms.status')"
-            :options="statusOptions"
-            emit-value
-            map-options
-            class="col-md-6 col-12"
+            :error="!!validationErrors.document"
+            :error-message="validationErrors.document"
+            :label="$t('associado.dependent_document')"
+            type="file"
+            accept="image/*,application/pdf"
+            class="col-12"
           />
+          <div v-if="!dependent" class="col-12 text-caption text-grey-7">
+            {{ $t("associado.dependent_document_hint") }}
+          </div>
         </q-card-section>
         <q-card-actions>
           <q-space />
@@ -46,7 +49,6 @@
             :label="dependent ? $t('common.actions.save') : $t('common.actions.add')"
             type="submit"
             :loading="loading"
-            :disable="!hasUpdatedFields"
           />
         </q-card-actions>
       </q-form>
@@ -55,7 +57,7 @@
 </template>
 
 <script setup>
-import { computed, useTemplateRef } from "vue";
+import { computed, ref, useTemplateRef } from "vue";
 import { useInputRules } from "src/composables/useInputRules";
 import { useDialogPluginComponent } from "quasar";
 import { useI18n } from "vue-i18n";
@@ -66,6 +68,7 @@ import { useSubmitHandler } from "src/composables/useSubmitHandler";
 import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
 import DefaultInput from "src/components/defaults/DefaultInput.vue";
 import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
+import DefaultFilePicker from "src/components/defaults/DefaultFilePicker.vue";
 
 defineEmits([...useDialogPluginComponent.emits]);
 
@@ -91,16 +94,11 @@ const kinshipOptions = computed(() => [
   { label: t("associado.kinship_options.outro"), value: "outro" },
 ]);
 
-const statusOptions = computed(() => [
-  { label: t("associado.dependent_statuses.approved"), value: "approved" },
-  { label: t("associado.dependent_statuses.refused"),  value: "refused" },
-  { label: t("associado.dependent_statuses.pending"),  value: "pending" },
-]);
+const documentFile = ref(null);
 
-const { form, getUpdatedFields, hasUpdatedFields } = useFormUpdateTracker({
+const { form, getUpdatedFields } = useFormUpdateTracker({
   name: dependent?.name ?? "",
   kinship: dependent?.kinship ?? "",
-  status: dependent?.status ?? "pending",
   responsible_user_id: userId,
 });
 
@@ -113,7 +111,7 @@ const onOKClick = async () => {
   if (dependent) {
     await submitForm(() => updateDependent(dependent.id, getUpdatedFields.value));
   } else {
-    await submitForm(() => createDependent({ ...form }));
+    await submitForm(() => createDependent({ ...form, document: documentFile.value }));
   }
 };
 </script>