Explorar el Código

feat: adiciona crud de tipo de usuarios e adiciona usuario para franquia

ebagabee hace 1 mes
padre
commit
5bc09d63ff

+ 16 - 0
src/api/user_type.js

@@ -0,0 +1,16 @@
+import api from "src/api";
+
+export const getUserTypes = async () => {
+  const { data } = await api.get("/user-type");
+  return data.payload;
+};
+
+export const createUserType = async (payload) => {
+  const { data } = await api.post("/user-type", payload);
+  return data.payload;
+};
+
+export const deleteUserType = async (id) => {
+  const { data } = await api.delete(`/user-type/${id}`);
+  return data.payload;
+};

+ 15 - 34
src/components/selects/UserTypeSelect.vue

@@ -1,6 +1,6 @@
 <template>
   <DefaultSelect
-    v-model="selectedUserType"
+    v-model="selectedOption"
     v-bind="$attrs"
     use-input
     hide-selected
@@ -15,12 +15,12 @@
 </template>
 
 <script setup>
-import { onMounted, ref, watch } from "vue";
+import { onMounted, ref, computed } from "vue";
 import { userTypes } from "src/api/user";
 import { useI18n } from "vue-i18n";
 import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
 
-const { placeholder, label, type } = defineProps({
+const props = defineProps({
   placeholder: {
     type: String,
     default: () => useI18n().t("common.ui.misc.type"),
@@ -29,24 +29,24 @@ const { placeholder, label, type } = defineProps({
     type: String,
     default: () => useI18n().t("common.ui.misc.type"),
   },
-  type: {
-    type: String,
-    default: null,
-  },
 });
 
-const selectedUserType = defineModel({
-  type: Object,
-});
+const { placeholder, label } = props;
+
+const modelValue = defineModel({ type: String, default: null });
+
 const userTypeOptions = ref([]);
 const filteredOptions = ref([]);
 const isLoading = ref(true);
 
-const selectUserByValue = (value) => {
-  selectedUserType.value = userTypeOptions.value.find(
-    (option) => option.value === value,
-  );
-};
+const selectedOption = computed({
+  get() {
+    return userTypeOptions.value.find((opt) => opt.value === modelValue.value) ?? null;
+  },
+  set(option) {
+    modelValue.value = option?.value ?? null;
+  },
+});
 
 const filterFn = (val, update) => {
   update(() => {
@@ -69,29 +69,10 @@ onMounted(async () => {
       value: key,
     }));
     filteredOptions.value = userTypeOptions.value;
-
-    if (type) {
-      selectedUserType.value = userTypeOptions.value.find(
-        (option) => option.value === type,
-      );
-    }
   } catch (error) {
     console.error("Failed to load user types:", error);
   } finally {
     isLoading.value = false;
   }
 });
-
-watch(
-  () => type,
-  (newType) => {
-    if (newType && userTypeOptions.value.length > 0) {
-      selectUserByValue(newType);
-    }
-  },
-);
-
-defineExpose({
-  selectUserByValue,
-});
 </script>

+ 88 - 0
src/pages/user_types/UserTypesPage.vue

@@ -0,0 +1,88 @@
+<template>
+  <div>
+    <DefaultHeaderPage title="Tipos de Usuários do Sistema" />
+
+    <div class="q-px-sm">
+      <DefaultTable
+        ref="tableRef"
+        title="Lista de Tipos de Usuários"
+        :columns="columns"
+        :api-call="getUserTypes"
+        descricao="tipos"
+        :feminino="false"
+        add-item
+        @on-add-item="onAdd"
+      >
+        <template #body-cell-actions="{ row }">
+          <q-td auto-width>
+            <q-btn
+              v-if="!row.is_system"
+              outline
+              icon="mdi-trash-can-outline"
+              style="width: 36px"
+              color="negative"
+              @click.prevent.stop="onDelete(row)"
+            />
+          </q-td>
+        </template>
+      </DefaultTable>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { defineAsyncComponent, useTemplateRef } from "vue";
+import { useQuasar } from "quasar";
+import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
+import DefaultTable from "src/components/defaults/DefaultTable.vue";
+import { getUserTypes, deleteUserType } from "src/api/user_type";
+
+const AddUserTypeDialog = defineAsyncComponent(
+  () => import("src/pages/user_types/components/AddUserTypeDialog.vue"),
+);
+
+const $q = useQuasar();
+const tableRef = useTemplateRef("tableRef");
+
+const columns = [
+  {
+    name: "label",
+    label: "Nome",
+    field: "label",
+    align: "left",
+    sortable: true,
+  },
+  {
+    name: "slug",
+    label: "Código",
+    field: "slug",
+    align: "left",
+    sortable: true,
+  },
+  {
+    name: "actions",
+    label: "Ações",
+    field: null,
+    align: "center",
+    required: true,
+  },
+];
+
+const onAdd = () => {
+  $q.dialog({ component: AddUserTypeDialog }).onOk(() => {
+    tableRef.value?.refresh();
+  });
+};
+
+const onDelete = (row) => {
+  $q.dialog({
+    title: "Confirmar exclusão",
+    message: `Remover o tipo "${row.label}"?`,
+    cancel: true,
+    persistent: true,
+  }).onOk(async () => {
+    await deleteUserType(row.id);
+    tableRef.value?.refresh();
+  });
+};
+</script>

+ 63 - 0
src/pages/user_types/components/AddUserTypeDialog.vue

@@ -0,0 +1,63 @@
+<template>
+  <q-dialog ref="dialogRef" @hide="onDialogHide">
+    <q-card style="min-width: 400px">
+      <DefaultDialogHeader title="Novo Tipo de Usuário" @close="onDialogCancel" />
+
+      <q-form ref="formRef" @submit="onSave">
+        <q-card-section>
+          <DefaultInput
+            v-model="form.label"
+            label="Nome"
+            outlined
+            autofocus
+            :rules="[inputRules.required]"
+          />
+        </q-card-section>
+
+        <q-card-actions align="right">
+          <q-btn
+            label="Cancelar"
+            color="primary"
+            outline
+            @click="onDialogCancel"
+          />
+          <q-btn
+            label="Salvar"
+            color="primary"
+            type="submit"
+            :loading="loading"
+          />
+        </q-card-actions>
+      </q-form>
+    </q-card>
+  </q-dialog>
+</template>
+
+<script setup>
+import { ref, useTemplateRef } from "vue";
+import { useDialogPluginComponent } from "quasar";
+import { useInputRules } from "src/composables/useInputRules";
+import { useSubmitHandler } from "src/composables/useSubmitHandler";
+import { createUserType } from "src/api/user_type";
+import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
+import DefaultInput from "src/components/defaults/DefaultInput.vue";
+
+defineEmits([...useDialogPluginComponent.emits]);
+
+const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
+  useDialogPluginComponent();
+
+const { inputRules } = useInputRules();
+const formRef = useTemplateRef("formRef");
+
+const form = ref({ label: "" });
+
+const { loading, execute } = useSubmitHandler({
+  formRef,
+  onSuccess: () => onDialogOK(),
+});
+
+const onSave = async () => {
+  await execute(() => createUserType({ label: form.value.label }));
+};
+</script>

+ 3 - 7
src/pages/users/UserActionPage.vue

@@ -35,7 +35,6 @@
               outlined
               label="Tipo de Usuário"
               :rules="[inputRules.required]"
-              :type="editUserTypeValue"
             />
 
             <DefaultInput
@@ -169,7 +168,6 @@ const avatarFile = ref(null);
 const showPassword = ref(false);
 const showPasswordConfirm = ref(false);
 
-const editUserTypeValue = ref(null);
 const editStateId = ref(null);
 const editUnitId = ref(null);
 
@@ -200,7 +198,7 @@ onMounted(async () => {
     form.value.name = user.name;
     form.value.email = user.email;
     form.value.cpf = user.cpf;
-    editUserTypeValue.value = user.user_type;
+    form.value.user_type = user.user_type;
     editStateId.value = user.state_id ?? null;
     editUnitId.value = user.unit_id ?? null;
     if (user.avatar_url) {
@@ -217,8 +215,7 @@ function buildFormData() {
   if (avatarFile.value) fd.append("avatar", avatarFile.value);
   if (form.value.state?.value) fd.append("state_id", form.value.state.value);
   if (form.value.unit?.value) fd.append("unit_id", form.value.unit.value);
-  if (form.value.user_type?.value)
-    fd.append("user_type", form.value.user_type.value);
+  if (form.value.user_type) fd.append("user_type", form.value.user_type);
   if (form.value.cpf) fd.append("cpf", form.value.cpf);
 
   fd.append("name", form.value.name ?? "");
@@ -235,8 +232,7 @@ function buildUpdateData() {
   if (avatarFile.value) fd.append("avatar", avatarFile.value);
   if (form.value.state?.value) fd.append("state_id", form.value.state.value);
   if (form.value.unit?.value) fd.append("unit_id", form.value.unit.value);
-  if (form.value.user_type?.value)
-    fd.append("user_type", form.value.user_type.value);
+  if (form.value.user_type) fd.append("user_type", form.value.user_type);
   if (form.value.cpf) fd.append("cpf", form.value.cpf);
   if (form.value.name) fd.append("name", form.value.name);
   if (form.value.email) fd.append("email", form.value.email);

+ 14 - 0
src/router/routes/user_type.route.js

@@ -0,0 +1,14 @@
+export default [
+  {
+    path: "/user-types",
+    name: "UserTypesPage",
+    component: () => import("pages/user_types/UserTypesPage.vue"),
+    meta: {
+      title: "Tipos de Usuários do Sistema",
+      requireAuth: true,
+      breadcrumbs: [
+        { name: "UserTypesPage", title: "Tipos de Usuários do Sistema" },
+      ],
+    },
+  },
+];

+ 9 - 1
src/stores/navigation.js

@@ -48,7 +48,15 @@ export const navigationStore = defineStore("navigation", () => {
       disable: false,
       permission: false,
       permissionScope: "dashboard"
-    }
+    },
+    {
+      type: "single",
+      title: "Tipos de Usuários",
+      name: "UserTypesPage",
+      icon: "mdi-account-cog-outline",
+      disable: false,
+      permission: true,
+    },
   ]);
 
   const getNavigationAccess = () => {