Browse Source

feat(packages): componente PackageCard e grid de listagem na PackagesPage

- PackageCard exibe nome, quantidade de aulas, contrato, material e matrícula
- PackagesPage busca pacotes da API no mount com estado de loading (skeleton)
- Grid responsivo 3 colunas com filtros de unidade e status
- Estado vazio quando sem pacotes cadastrados
- openEditDialog passando props ao AddEditPackageDialog

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ebagabee 1 month ago
parent
commit
afbb6f7351
2 changed files with 132 additions and 9 deletions
  1. 87 9
      src/pages/packages/PackagesPage.vue
  2. 45 0
      src/pages/packages/components/PackageCard.vue

+ 87 - 9
src/pages/packages/PackagesPage.vue

@@ -2,26 +2,104 @@
   <div>
     <DefaultHeaderPage title="Pacotes" />
 
-    <div class="flex justify-end q-px-sm q-mb-md">
-      <q-btn
-        color="primary"
-        icon="mdi-plus"
-        unelevated
-        style="border-radius: 8px; height: 40px; width: 40px"
-        @click="openAddDialog"
-      />
+    <div class="q-px-sm">
+      <!-- Filters + Add button -->
+      <div class="row items-center justify-between q-mb-md">
+        <div class="row q-gutter-x-sm">
+          <DefaultSelect
+            v-model="filterUnit"
+            label="Selecione a Unidade"
+            :options="[]"
+            style="width: 210px"
+          />
+          <DefaultSelect
+            v-model="filterStatus"
+            label="Selecione o Status"
+            :options="[]"
+            style="width: 210px"
+          />
+        </div>
+
+        <q-btn
+          color="primary"
+          icon="mdi-plus"
+          unelevated
+          style="border-radius: 8px; height: 40px; width: 40px"
+          @click="openAddDialog"
+        />
+      </div>
+
+      <!-- Loading -->
+      <div v-if="loading" class="row q-col-gutter-sm">
+        <div v-for="n in 6" :key="n" class="col-4">
+          <q-card flat bordered style="border-radius: 12px">
+            <q-card-section>
+              <q-skeleton type="text" width="60%" />
+              <q-skeleton type="text" width="30%" class="q-mb-sm" />
+              <q-skeleton type="text" width="80%" />
+              <q-skeleton type="text" width="70%" />
+              <q-skeleton type="text" width="50%" />
+            </q-card-section>
+          </q-card>
+        </div>
+      </div>
+
+      <!-- Packages grid -->
+      <div v-else class="row q-col-gutter-sm">
+        <div
+          v-for="pkg in packages"
+          :key="pkg.id"
+          class="col-12 col-sm-6 col-md-4"
+        >
+          <PackageCard :pkg="pkg" @click="openEditDialog(pkg)" />
+        </div>
+
+        <div v-if="packages.length === 0" class="col-12 text-center text-grey-5 q-py-xl">
+          Nenhum pacote cadastrado
+        </div>
+      </div>
     </div>
   </div>
 </template>
 
 <script setup>
+import { ref, onMounted } from "vue";
 import { useQuasar } from "quasar";
+
 import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
+import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
+import PackageCard from "./components/PackageCard.vue";
 import AddEditPackageDialog from "./components/AddEditPackageDialog.vue";
 
+import { getPackages } from "src/api/package";
+
 const $q = useQuasar();
 
+const loading = ref(false);
+const packages = ref([]);
+const filterUnit = ref(null);
+const filterStatus = ref(null);
+
+const fetchPackages = async () => {
+  loading.value = true;
+  try {
+    packages.value = await getPackages();
+  } finally {
+    loading.value = false;
+  }
+};
+
 const openAddDialog = () => {
-  $q.dialog({ component: AddEditPackageDialog });
+  $q.dialog({ component: AddEditPackageDialog }).onOk(() => {
+    fetchPackages();
+  });
 };
+
+const openEditDialog = (pkg) => {
+  $q.dialog({ component: AddEditPackageDialog, componentProps: { package: pkg } }).onOk(() => {
+    fetchPackages();
+  });
+};
+
+onMounted(fetchPackages);
 </script>

+ 45 - 0
src/pages/packages/components/PackageCard.vue

@@ -0,0 +1,45 @@
+<template>
+  <q-card
+    flat
+    bordered
+    class="cursor-pointer package-card"
+    @click="$emit('click')"
+  >
+    <q-card-section class="q-pa-md">
+      <div class="text-subtitle1 text-weight-bold">{{ pkg.name }}</div>
+      <div class="text-body2 text-grey-6 q-mb-sm">{{ pkg.quantity_classes }} Aulas</div>
+
+      <div class="column" style="gap: 2px">
+        <span class="text-body2">Contrato {{ formatToBRLCurrency(pkg.contract_value) }}</span>
+        <span class="text-body2">
+          Material {{ formatToBRLCurrency(pkg.contract_material_value) }} - Incluso
+        </span>
+        <span class="text-body2">Matrícula {{ formatToBRLCurrency(pkg.contract_register_value) }}</span>
+      </div>
+    </q-card-section>
+  </q-card>
+</template>
+
+<script setup>
+import { formatToBRLCurrency } from "src/helpers/utils";
+
+defineProps({
+  pkg: {
+    type: Object,
+    required: true,
+  },
+});
+
+defineEmits(["click"]);
+</script>
+
+<style scoped>
+.package-card {
+  border-radius: 12px;
+  transition: box-shadow 0.2s;
+}
+
+.package-card:hover {
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+}
+</style>