فهرست منبع

feat: add inhabitant_classifications nas tabs da brt com crud funcional

Gustavo Mantovani 1 ماه پیش
والد
کامیت
ad5b63484b

+ 21 - 0
src/api/inhabitant_classification.js

@@ -4,3 +4,24 @@ export const getInhabitantClassificationsForSelect = async () => {
   const { data } = await api.get("/inhabitant-classification/all/select");
   return data.payload;
 };
+
+//
+
+export const getInhabitantClassifications = async () => {
+  const { data } = await api.get("/inhabitant-classification");
+  return data.payload;
+};
+
+export const createInhabitantClassification = async (payload) => {
+  const { data } = await api.post("/inhabitant-classification", payload);
+  return data.payload;
+};
+
+export const updateInhabitantClassification = async (id, payload) => {
+  const { data } = await api.put(`/inhabitant-classification/${id}`, payload);
+  return data.payload;
+};
+
+export const deleteInhabitantClassification = async (id) => {
+  await api.delete(`/inhabitant-classification/${id}`);
+};

+ 38 - 27
src/components/defaults/DefaultInput.vue

@@ -6,16 +6,16 @@
         v-model="model"
         v-bind="inputAttrs"
         hide-bottom-space
-        :label-color
+        :bg-color="bgColor"
+        :class="inputClass"
         :color
-        :label
-        :error="!!error"
+        :error="!!error || !!errorMessageProp"
         :error-message="errorMessage"
+        :input-class="nativeInputClass"
+        :label
+        :label-color
         :rules
         :outlined
-        :bg-color
-        :class="inputClass"
-        :input-class="nativeInputClass"
         @update:model-value="error = null"
       >
         <template v-for="(_, slotName) in $slots" #[slotName]>
@@ -34,55 +34,59 @@
 
 <script setup>
 import {
-  ref,
+  computed,
   onBeforeMount,
+  ref,
   useAttrs,
-  computed,
-  watch,
   useTemplateRef,
+  watch,
 } from "vue";
 
 defineOptions({
   inheritAttrs: false,
 });
 
-const { label, nativeInputClass, inputClass, rules, icon, bgColor, outlined } =
+const { bgColor, errorMessage: errorMessageProp, icon, inputClass, label, labelColor, nativeInputClass, rules, outlined } =
   defineProps({
-    label: {
+    bgColor: {
       type: String,
-      default: "",
+      default: "white",
     },
-    icon: {
+    color: {
       type: String,
-      default: "",
-    },
-    rules: {
-      type: Array,
-      default: () => [],
+      default: "secondary",
     },
-    nativeInputClass: {
+    errorMessage: {
       type: String,
       default: null,
     },
+    icon: {
+      type: String,
+      default: "",
+    },
     inputClass: {
       type: String,
       default: null,
     },
-    bgColor: {
+    label: {
       type: String,
-      default: "white",
-    },
-    outlined: {
-      type: Boolean,
-      default: true,
+      default: "",
     },
     labelColor: {
       type: String,
       default: "secondary",
     },
-    color: {
+    nativeInputClass: {
       type: String,
-      default: "secondary",
+      default: null,
+    },
+    outlined: {
+      type: Boolean,
+      default: true,
+    },
+    rules: {
+      type: Array,
+      default: () => [],
     },
   });
 
@@ -91,6 +95,7 @@ const attrs = useAttrs();
 const inputRef = useTemplateRef("inputRef");
 
 const model = defineModel({ type: [String, Object, Array, Boolean, null] });
+
 const error = defineModel("error", {
   type: [String, Object, Array, Boolean, null],
 });
@@ -104,12 +109,18 @@ const inputAttrs = computed(() => {
 });
 
 const errorMessage = computed(() => {
+  if (errorMessageProp != null) {
+    return errorMessageProp;
+  }
+
   if (error.value == null) {
     return void 0;
   }
+
   if (typeof error.value === "boolean") {
     return void 0;
   }
+
   return String(error.value);
 });
 

+ 14 - 6
src/pages/tbr/TbrPage.vue

@@ -1,18 +1,22 @@
 <script setup>
-import { ref, defineAsyncComponent } from "vue";
-import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
+import { defineAsyncComponent, ref } from "vue";
+
 import CustomTabComponent from "src/components/shared/CustomTabComponent.vue";
+import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
 
-const SettingsTab = defineAsyncComponent(() => import("./tabs/SettingsTab.vue"));
+const SettingsTab  = defineAsyncComponent(() => import("./tabs/SettingsTab.vue"));
 const ContractsTab = defineAsyncComponent(() => import("./tabs/ContractsTab.vue"));
-const BillingsTab = defineAsyncComponent(() => import("./tabs/BillingsTab.vue"));
+const BillingsTab  = defineAsyncComponent(() => import("./tabs/BillingsTab.vue"));
+
+const InhabitantClassificationsTab = defineAsyncComponent(() => import("./tabs/InhabitantClassificationsTab.vue"));
 
 const currentTab = ref("settings");
 
 const tabs = [
-  { name: "settings", label: "Configurações" },
+  { name: "settings",  label: "Configurações" },
   { name: "contracts", label: "Contratos" },
-  { name: "billings", label: "Cobranças" },
+  { name: "billings",  label: "Cobranças" },
+  { name: "inhabitant_classifications", label: "Faixa de Habitantes" },
 ];
 </script>
 
@@ -34,6 +38,10 @@ const tabs = [
       <div v-show="currentTab === 'billings'">
         <BillingsTab />
       </div>
+
+      <div v-show="currentTab === 'inhabitant_classifications'">
+        <InhabitantClassificationsTab />
+      </div>
     </div>
   </div>
 </template>

+ 105 - 0
src/pages/tbr/components/AddEditInhabitantClassificationDialog.vue

@@ -0,0 +1,105 @@
+<template>
+  <q-dialog ref="dialogRef" @hide="onDialogHide">
+    <q-card class="q-dialog-plugin overflow-hidden" style="width: 100%; max-width: 500px">
+      <DefaultDialogHeader
+        :title="() => (item ? 'Editar Faixa de Habitante' : 'Nova Faixa de Habitante')"
+        @close="onDialogCancel"
+      />
+
+      <q-form ref="formRef" @submit="onOKClick">
+        <q-card-section class="row q-col-gutter-sm q-pt-none">
+          <DefaultInput
+            v-model="form.description"
+            v-model:error="validationErrors.description"
+            class="col-12"
+            label="Descrição"
+            :error-message="validationErrors.description"
+            :rules="[inputRules.required]"
+          />
+
+          <DefaultInput
+            v-model="form.acronym"
+            v-model:error="validationErrors.acronym"
+            class="col-12"
+            label="Sigla"
+            maxlength="2"
+            :error-message="validationErrors.acronym"
+            :rules="[inputRules.required]"
+          />
+        </q-card-section>
+
+        <q-card-actions>
+          <q-space />
+
+          <q-btn
+            color="negative"
+            label="Cancelar"
+            outline
+            @click="onDialogCancel"
+        />
+
+          <q-btn
+            color="primary-2"
+            label="Salvar"
+            type="submit"
+            :loading="loading"
+          />
+        </q-card-actions>
+      </q-form>
+    </q-card>
+  </q-dialog>
+</template>
+
+<script setup>
+import {
+  createInhabitantClassification,
+  updateInhabitantClassification,
+} from "src/api/inhabitant_classification";
+
+import { useDialogPluginComponent } from "quasar";
+import { useFormUpdateTracker } from "src/composables/useFormUpdateTracker";
+import { useInputRules } from "src/composables/useInputRules";
+import { useSubmitHandler } from "src/composables/useSubmitHandler";
+import { useTemplateRef } from "vue";
+
+import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
+import DefaultInput from "src/components/defaults/DefaultInput.vue";
+
+defineEmits([...useDialogPluginComponent.emits]);
+
+const { item } = defineProps({
+  item: {
+    type: Object, default: null,
+  },
+});
+
+const { inputRules } = useInputRules();
+
+const {
+  dialogRef,
+  onDialogCancel,
+  onDialogHide,
+  onDialogOK,
+} = useDialogPluginComponent();
+
+const formRef = useTemplateRef("formRef");
+
+const { form, getUpdatedFields } = useFormUpdateTracker({
+  description: item?.description ?? "", acronym: item?.acronym ?? "",
+});
+
+const { loading, validationErrors, execute } = useSubmitHandler({
+  formRef,
+  onSuccess: () => onDialogOK(true),
+});
+
+const onOKClick = async () => {
+  if (item) {
+    console.log(getUpdatedFields.value);
+
+    await execute(() => updateInhabitantClassification(item.id, getUpdatedFields.value));
+  } else {
+    await execute(() => createInhabitantClassification(form));
+  }
+}
+</script>

+ 53 - 0
src/pages/tbr/components/ViewInhabitantClassificationDialog.vue

@@ -0,0 +1,53 @@
+<template>
+  <q-dialog ref="dialogRef" @hide="onDialogHide">
+    <q-card class="q-dialog-plugin overflow-hidden" style="width: 100%; max-width: 500px">
+      <DefaultDialogHeader
+        :title="() => 'Visualizar Faixa de Habitante'"
+        @close="onDialogCancel"
+      />
+
+      <q-card-section class="row q-col-gutter-sm q-pt-none">
+        <DefaultInput
+          :model-value="item.description"
+          class="col-12"
+          disable
+          label="Descrição"
+        />
+
+        <DefaultInput
+          :model-value="item.acronym"
+          class="col-12"
+          disable
+          label="Sigla"
+        />
+      </q-card-section>
+
+      <q-card-actions>
+        <q-space />
+        <q-btn
+          color="primary-2"
+          label="Fechar"
+          @click="onDialogCancel"
+        />
+      </q-card-actions>
+    </q-card>
+  </q-dialog>
+</template>
+
+<script setup>
+import { useDialogPluginComponent } from "quasar";
+
+import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
+import DefaultInput from "src/components/defaults/DefaultInput.vue";
+
+defineEmits([...useDialogPluginComponent.emits]);
+
+defineProps({
+  item: {
+    type: Object,
+    required: true,
+  },
+});
+
+const { dialogRef, onDialogHide, onDialogCancel } = useDialogPluginComponent();
+</script>

+ 80 - 0
src/pages/tbr/tabs/InhabitantClassificationsTab.vue

@@ -0,0 +1,80 @@
+<template>
+  <DefaultTable
+    v-model:rows="rows"
+    description="faixas"
+    title="Faixa de Habitantes"
+    :add-item="true"
+    :columns="columns"
+    :female="true"
+    :no-api-call="true"
+    :show-search-field="true"
+    @on-add-item="onAdd"
+  >
+    <template #body-cell-actions="{ row }">
+      <q-td align="center">
+        <div class="row no-wrap" style="gap: 4px">
+          <q-btn
+            dense
+            flat
+            icon="mdi-eye-outline"
+            round
+            size="sm"
+            @click="onView(row)"
+          />
+          <q-btn
+            dense
+            flat
+            icon="mdi-file-edit-outline"
+            round
+            size="sm"
+            @click="onEdit(row)"
+          />
+        </div>
+      </q-td>
+    </template>
+  </DefaultTable>
+</template>
+
+<script setup>
+import { getInhabitantClassifications } from "src/api/inhabitant_classification";
+import { onMounted, ref } from "vue";
+import { useQuasar } from "quasar";
+
+import AddEditInhabitantClassificationDialog from "src/pages/tbr/components/AddEditInhabitantClassificationDialog.vue";
+import ViewInhabitantClassificationDialog from "src/pages/tbr/components/ViewInhabitantClassificationDialog.vue";
+
+import DefaultTable from "src/components/defaults/DefaultTable.vue";
+
+const $q = useQuasar();
+
+const rows = ref([]);
+
+const columns = [
+  { name: "id", label: "ID", field: "id", align: "left" },
+  { name: "description", label: "Descrição", field: "description", align: "left" },
+  { name: "acronym", label: "Sigla", field: "acronym", align: "left" },
+  { name: "actions", label: "Ações", field: "actions", align: "center" },
+];
+
+const loadData = async () => {
+  rows.value = await getInhabitantClassifications();
+}
+
+const onAdd = () => {
+  $q.dialog({ component: AddEditInhabitantClassificationDialog }).onOk(() => loadData());
+}
+
+const onEdit = (row) => {
+  $q.dialog({
+    component: AddEditInhabitantClassificationDialog, componentProps: { item: row },
+  }).onOk(() => loadData());
+}
+
+const onView = (row) => {
+  $q.dialog({
+    component: ViewInhabitantClassificationDialog, componentProps: { item: row },
+  });
+}
+
+onMounted(loadData);
+</script>