|
|
@@ -1,44 +1,83 @@
|
|
|
<template>
|
|
|
<div class="column" style="gap: 8px">
|
|
|
- <ProductCard
|
|
|
- v-for="product in mockProducts"
|
|
|
- :key="product.id"
|
|
|
- :title="product.title"
|
|
|
- :description="product.description"
|
|
|
- :unit-price="product.unitPrice"
|
|
|
- :current-stock="product.currentStock"
|
|
|
- :total-value="product.totalValue"
|
|
|
- />
|
|
|
+ <div v-if="loading" class="row justify-center q-pa-lg">
|
|
|
+ <q-spinner color="primary" size="32px" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template v-else>
|
|
|
+ <ProductCard
|
|
|
+ v-for="product in products"
|
|
|
+ :key="product.id"
|
|
|
+ :title="product.name"
|
|
|
+ :description="product.description"
|
|
|
+ :unit-price="product.price_sale"
|
|
|
+ :current-stock="product.quantity"
|
|
|
+ :total-value="product.price_sale * product.quantity"
|
|
|
+ @edit="handleEdit(product)"
|
|
|
+ @delete="handleDelete(product)"
|
|
|
+ />
|
|
|
+
|
|
|
+ <div v-if="products.length === 0" class="text-center text-grey q-pa-lg">
|
|
|
+ Nenhum produto cadastrado.
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
+import { ref, onMounted } from "vue";
|
|
|
+import { useQuasar } from "quasar";
|
|
|
import ProductCard from "../components/ProductCard.vue";
|
|
|
+import AddEditProductDialog from "../components/AddEditProductDialog.vue";
|
|
|
+import { getProducts, deleteProduct } from "src/api/product";
|
|
|
+
|
|
|
+const $q = useQuasar();
|
|
|
+
|
|
|
+const products = ref([]);
|
|
|
+const loading = ref(false);
|
|
|
+
|
|
|
+const fetchProducts = async () => {
|
|
|
+ loading.value = true;
|
|
|
+ try {
|
|
|
+ products.value = await getProducts();
|
|
|
+ } catch {
|
|
|
+ $q.notify({ type: "negative", message: "Erro ao carregar produtos." });
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+onMounted(fetchProducts);
|
|
|
+
|
|
|
+defineExpose({ fetchProducts });
|
|
|
+
|
|
|
+const handleEdit = (product) => {
|
|
|
+ $q.dialog({
|
|
|
+ component: AddEditProductDialog,
|
|
|
+ componentProps: { product },
|
|
|
+ }).onOk((updated) => {
|
|
|
+ const index = products.value.findIndex((p) => p.id === product.id);
|
|
|
+ if (index !== -1) {
|
|
|
+ products.value[index] = updated;
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
|
|
|
-const mockProducts = [
|
|
|
- {
|
|
|
- id: 1,
|
|
|
- title: "Apostila Ginástica Cerebro Vol. 1",
|
|
|
- description: "Material didático completo com conteúdo para os primeiros 3 meses de prática. Inclui exercícios cognitivos, fichas de avaliação e guia do professor.",
|
|
|
- unitPrice: 89.9,
|
|
|
- currentStock: 142,
|
|
|
- totalValue: 12761.8,
|
|
|
- },
|
|
|
- {
|
|
|
- id: 2,
|
|
|
- title: "Kit Iniciante",
|
|
|
- description: "Kit completo para novos alunos contendo apostila, camiseta e carteirinha de identificação.",
|
|
|
- unitPrice: 159.9,
|
|
|
- currentStock: 0,
|
|
|
- totalValue: 0,
|
|
|
- },
|
|
|
- {
|
|
|
- id: 3,
|
|
|
- title: "Camiseta Oficial GC",
|
|
|
- description: "Camiseta oficial da Ginástica Cerebro em malha 100% algodão. Disponível nos tamanhos P, M, G e GG.",
|
|
|
- unitPrice: 49.9,
|
|
|
- currentStock: 37,
|
|
|
- totalValue: 1846.3,
|
|
|
- },
|
|
|
-];
|
|
|
+const handleDelete = (product) => {
|
|
|
+ $q.dialog({
|
|
|
+ title: "Excluir Produto",
|
|
|
+ message: `Deseja excluir o produto "${product.name}"?`,
|
|
|
+ cancel: { label: "Cancelar", flat: true, color: "primary" },
|
|
|
+ ok: { label: "Excluir", color: "negative" },
|
|
|
+ persistent: true,
|
|
|
+ }).onOk(async () => {
|
|
|
+ try {
|
|
|
+ await deleteProduct(product.id);
|
|
|
+ products.value = products.value.filter((p) => p.id !== product.id);
|
|
|
+ $q.notify({ type: "positive", message: "Produto excluído com sucesso." });
|
|
|
+ } catch {
|
|
|
+ $q.notify({ type: "negative", message: "Erro ao excluir produto." });
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
</script>
|