Переглянути джерело

feat: :sparkles: crud especialidades

crud especialidades
Gustavo Zanatta 1 місяць тому
батько
коміт
9923dfd341

+ 26 - 0
src/api/speciality.js

@@ -0,0 +1,26 @@
+import api from "src/api";
+
+export const getSpeciality = async (id) => {
+  const { data } = await api.get("/speciality/" + id);
+  return data.payload;
+};
+
+export const getSpecialities = async () => {
+  const { data } = await api.get("/specialities");
+  return data.payload;
+};
+
+export const createSpeciality = async (speciality) => {
+  const { data } = await api.post("/speciality", speciality);
+  return data.payload;
+};
+
+export const updateSpeciality = async (speciality, id) => {
+  const { data } = await api.put(`/speciality/${id}`, speciality);
+  return data.payload;
+};
+
+export const deleteSpeciality = async (id) => {
+  const { data } = await api.delete(`/speciality/${id}`);
+  return data.payload;
+};

+ 111 - 0
src/components/speciality/SpecialitySelect.vue

@@ -0,0 +1,111 @@
+<template>
+  <q-select
+    v-model="selectedSpeciality"
+    v-bind="$attrs"
+    use-input
+    hide-selected
+    fill-input
+    clearable
+    :options="specialityOptions"
+    :label
+    :rules
+    :loading
+    :placeholder="$t('common.actions.search') + ' ' + $t('ui.navigation.speciality')"
+    :error
+    :error-message
+    @filter="filterFn"
+  >
+    <template #no-option>
+      <q-item>
+        <q-item-section class="text-grey">
+          {{ $t("http.errors.no_records_found") }}
+        </q-item-section>
+      </q-item>
+    </template>
+  </q-select>
+</template>
+
+<script setup>
+import { getSpecialities } from "src/api/speciality";
+import { ref, onMounted } from "vue";
+import { normalizeString } from "src/helpers/utils";
+import { useI18n } from "vue-i18n";
+
+const { label, rules, initialId } = defineProps({
+  label: {
+    type: String,
+    default: () => useI18n().t("ui.navigation.speciality"),
+  },
+  rules: {
+    type: Array,
+    default: () => [],
+  },
+  initialId: {
+    type: Number,
+    required: false,
+    default: null,
+  },
+  error: {
+    type: Boolean,
+    default: false,
+  },
+  errorMessage: {
+    type: String,
+    default: "",
+  },
+});
+
+const selectedSpeciality = defineModel({ type: Object });
+
+const loading = ref(false);
+const baseOptions = ref([]);
+const specialityOptions = ref([]);
+
+const filterFn = async (val, update) => {
+  const needle = normalizeString(val);
+  specialityOptions.value = baseOptions.value.filter((v) => {
+    return normalizeString(v.label).includes(needle);
+  });
+  update();
+};
+
+const selectSpecialityByName = (name) => {
+  if (selectedSpeciality.value?.label === name) {
+    return;
+  }
+  selectedSpeciality.value = baseOptions.value.find((speciality) => speciality.label === name);
+};
+
+const selectSpecialityById = (id) => {
+  if (selectedSpeciality.value?.value === id) {
+    return;
+  }
+  selectedSpeciality.value = baseOptions.value.find((speciality) => speciality.value === id);
+};
+
+onMounted(async () => {
+  try {
+    loading.value = true;
+    const baseSpecialities = await getSpecialities();
+    // Filtrar apenas ativos
+    const activeSpecialities = baseSpecialities.filter((speciality) => speciality.active === true);
+    baseOptions.value = activeSpecialities.map((speciality) => ({
+      label: speciality.description,
+      value: speciality.id,
+    }));
+    specialityOptions.value = baseOptions.value;
+    if (initialId) {
+      selectSpecialityById(initialId);
+    }
+  } catch (e) {
+    console.log(e);
+  } finally {
+    loading.value = false;
+  }
+});
+
+defineExpose({
+  selectSpecialityByName,
+  selectSpecialityById,
+});
+</script>

+ 11 - 1
src/i18n/locales/en.json

@@ -272,6 +272,15 @@
       "delete_confirm": "Are you sure you want to delete this address?"
     }
   },
+  "speciality": {
+    "singular": "Speciality",
+    "plural": "Specialities",
+    "add": "Add Speciality",
+    "edit": "Edit Speciality",
+    "fields": {
+      "description": "Description"
+    }
+  },
   "provider": {
     "singular": "Provider",
     "plural": "Providers",
@@ -354,7 +363,8 @@
       "exit": "Exit",
       "admin": "Admin",
       "user": "User",
-      "service_type": "Service Type"
+      "service_type": "Service Type",
+      "speciality": "Speciality"
     }
   },
   "charts": {

+ 11 - 1
src/i18n/locales/es.json

@@ -272,6 +272,15 @@
       "delete_confirm": "¿Está seguro de que desea eliminar esta dirección?"
     }
   },
+  "speciality": {
+    "singular": "Especialidad",
+    "plural": "Especialidades",
+    "add": "Agregar Especialidad",
+    "edit": "Editar Especialidad",
+    "fields": {
+      "description": "Descripción"
+    }
+  },
   "provider": {
     "singular": "Proveedor",
     "plural": "Proveedores",
@@ -354,7 +363,8 @@
       "exit": "Salir",
       "admin": "Admin",
       "user": "Usuario",
-      "service_type": "Tipo de Servicio"
+      "service_type": "Tipo de Servicio",
+      "speciality": "Especialidad"
     }
   },
   "charts": {

+ 11 - 1
src/i18n/locales/pt.json

@@ -272,6 +272,15 @@
       "delete_confirm": "Tem certeza que deseja excluir este endereço?"
     }
   },
+  "speciality": {
+    "singular": "Especialidade",
+    "plural": "Especialidades",
+    "add": "Adicionar Especialidade",
+    "edit": "Editar Especialidade",
+    "fields": {
+      "description": "Descrição"
+    }
+  },
   "provider": {
     "singular": "Prestador",
     "plural": "Prestadores",
@@ -354,7 +363,8 @@
       "exit": "Sair",
       "admin": "Admin",
       "user": "Usuário",
-      "service_type": "Tipo de Serviço"
+      "service_type": "Tipo de Serviço",
+      "speciality": "Especialidade"
     }
   },
   "charts": {

+ 112 - 0
src/pages/speciality/SpecialitiesPage.vue

@@ -0,0 +1,112 @@
+<template>
+  <div>
+    <DefaultHeaderPage />
+    <div>
+      <DefaultTable
+        ref="tableRef"
+        :columns="columns"
+        :api-call="getSpecialities"
+        :delete-function="deleteSpeciality"
+        :mostrar-selecao-de-colunas="false"
+        :mostrar-botao-fullscreen="false"
+        :mostrar-toggle-inativos="false"
+        open-item
+        add-item
+        @on-row-click="onRowClick"
+        @on-add-item="onAddItem"
+      />
+    </div>
+  </div>
+</template>
+<script setup>
+import { defineAsyncComponent, useTemplateRef } from "vue";
+import { useQuasar } from "quasar";
+import { useI18n } from "vue-i18n";
+import { permissionStore } from "src/stores/permission";
+import { getSpecialities, deleteSpeciality } from "src/api/speciality";
+
+import DefaultTable from "src/components/defaults/DefaultTable.vue";
+import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
+
+const AddEditSpecialityDialog = defineAsyncComponent(() =>
+  import("src/pages/speciality/components/AddEditSpecialityDialog.vue")
+);
+
+const permission_store = permissionStore();
+const $q = useQuasar();
+const tableRef = useTemplateRef("tableRef");
+const { t } = useI18n();
+
+const columns = [
+  {
+    name: "description",
+    label: t("speciality.fields.description"),
+    field: "description",
+    align: "left",
+    sortable: true,
+  },
+  {
+    name: "active",
+    label: t("common.status.active"),
+    field: (row) => (row.active ? t("common.status.yes") : t("common.status.no")),
+    align: "left",
+    sortable: true,
+  },
+  {
+    name: "created_at",
+    label: t("common.terms.created_at"),
+    field: "created_at",
+    align: "left",
+    sortable: true,
+  },
+  {
+    name: "actions",
+    required: true,
+  },
+];
+
+const onRowClick = ({ row }) => {
+  if (permission_store.getAccess("config.speciality", "edit") === false) {
+    $q.loading.hide();
+    $q.notify({
+      type: "negative",
+      message: t("validation.permissions.edit"),
+    });
+    return;
+  }
+  $q.dialog({
+    component: AddEditSpecialityDialog,
+    componentProps: {
+      speciality: row,
+      title: () =>
+        useI18n().t("common.actions.edit") + " " + useI18n().t("ui.navigation.speciality"),
+    },
+  }).onOk(async (success) => {
+    if (success) {
+      tableRef.value.refresh();
+    }
+  });
+};
+
+const onAddItem = () => {
+  if (permission_store.getAccess("config.speciality", "add") === false) {
+    $q.loading.hide();
+    $q.notify({
+      type: "negative",
+      message: t("validation.permissions.add"),
+    });
+    return;
+  }
+  $q.dialog({
+    component: AddEditSpecialityDialog,
+    componentProps: {
+      title: () =>
+        useI18n().t("common.actions.add") + " " + useI18n().t("ui.navigation.speciality"),
+    },
+  }).onOk(async (success) => {
+    if (success) {
+      tableRef.value.refresh();
+    }
+  });
+};
+</script>

+ 89 - 0
src/pages/speciality/components/AddEditSpecialityDialog.vue

@@ -0,0 +1,89 @@
+<template>
+  <q-dialog ref="dialogRef" @hide="onDialogHide">
+    <q-card class="q-dialog-plugin overflow-hidden" style="width: 1000px; max-width: 90vw">
+      <DefaultDialogHeader :title="title" @close="onDialogCancel" />
+        <q-form ref="formRef" @submit="onOKClick">
+          <q-card-section class="row q-col-gutter-sm">
+            <q-input
+              v-model="form.description"
+              fill-mask
+              unmasked-value
+              :label="$t('speciality.fields.description')"
+              :rules="[inputRules.required]"
+              :error="!!serverErrors?.description"
+              :error-message="serverErrors?.description"
+              class="col-12"
+            />
+
+            <div class="col-12">
+              <q-checkbox
+                v-model="form.active"
+                :label="$t('common.status.active')"
+              />
+            </div>
+
+          </q-card-section>
+          <q-card-actions align="center">
+            <q-btn color="primary" label="Cancel" @click="onDialogCancel" />
+            <q-space />
+            <q-btn color="primary" label="OK" :type="'submit'" :loading="loading" :disable="!hasUpdatedFields" />
+          </q-card-actions>
+        </q-form>
+    </q-card>
+  </q-dialog>
+</template>
+<script setup>
+import { useTemplateRef } from "vue";
+import { useInputRules } from "src/composables/useInputRules";
+import { useDialogPluginComponent } from "quasar";
+import { useI18n } from "vue-i18n";
+import { createSpeciality, updateSpeciality } from "src/api/speciality";
+import { useFormUpdateTracker } from "src/composables/useFormUpdateTracker";
+import { useSubmitHandler } from "src/composables/useSubmitHandler";
+import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
+
+defineEmits([
+  ...useDialogPluginComponent.emits,
+]);
+
+const { speciality, title } = defineProps({
+  speciality: {
+    type: Object,
+    default: null,
+  },
+  title: {
+    type: Function,
+    default: () => useI18n().t("common.terms.title"),
+  },
+});
+
+const { inputRules } = useInputRules();
+
+const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
+  useDialogPluginComponent();
+
+const formRef = useTemplateRef("formRef");
+const { form, getUpdatedFields, hasUpdatedFields } = useFormUpdateTracker({
+  description: speciality ? speciality?.description : "",
+  active: speciality ? speciality?.active : true,
+});
+
+const {
+  loading,
+  serverErrors,
+  execute: submitForm,
+} = useSubmitHandler({
+  onSuccess: () => onDialogOK(true),
+  formRef: formRef,
+});
+
+
+const onOKClick = async () => {
+  if (speciality) {
+    await submitForm(() => updateSpeciality(getUpdatedFields.value, speciality.id));
+  } else {
+    await submitForm(() => createSpeciality({ ...form }));
+  }
+};
+
+</script>

+ 22 - 0
src/router/routes/speciality.route.js

@@ -0,0 +1,22 @@
+export default [
+  {
+    path: "/speciality",
+    name: "SpecialitiesPage",
+    component: () => import("pages/speciality/SpecialitiesPage.vue"),
+    meta: {
+      title: "ui.navigation.speciality",
+      requireAuth: true,
+      requiredPermission: "config.speciality",
+      breadcrumbs: [
+        {
+          name: "DashboardPage",
+          title: "ui.navigation.dashboard",
+        },
+        {
+          name: "SpecialityPage",
+          title: "ui.navigation.speciality",
+        },
+      ],
+    },
+  },
+];

+ 9 - 0
src/stores/navigation.js

@@ -75,6 +75,15 @@ export const navigationStore = defineStore("navigation", () => {
           permission: false,
           permissionScope: "config.service_type",
         },
+        {
+          type: "single",
+          title: "ui.navigation.speciality",
+          name: "SpecialitiesPage",
+          icon: "mdi-star-outline",
+          disable: false,
+          permission: false,
+          permissionScope: "config.speciality",
+        },
         {
           type: "single",
           title: "ui.navigation.users",