keyboard_tabSkip to content
listVue Firebase Masterclass

จัดการ CRUD product

สารบัญ

มาทำ Product page กันต่อ

ทำให้ Product สามารถทำตาม Standard ของ CRUD ได้คือ ต้องสามารถ

  • สร้าง product ได้ (CREATE)
  • list product ทั้งหมดและอ่าน product แต่ละตัวได้ (READ)
  • แก้ไขข้อมูล product ได้ (UPDATE)
  • ลบข้อมูล product ได้ (DELETE)

ผลลัพธ์ผ่าน UI มันจะออกมาเหมือนเดิม แต่เพิ่มเติมคือเราจะเห็นผลลัพธ์ออกมาผ่าน Firestore แทน

ปรับ store stores/admin/product.js

javascript
import { defineStore } from 'pinia'

import {
  collection,
  getDocs,
  doc,
  addDoc,
  getDoc,
  setDoc,
  deleteDoc,
  query
} from 'firebase/firestore'

import { db } from '@/firebase'

export const useProductStore = defineStore('product', {
  state: () => ({
    docList: [], // ปรับเป็น docList แทนเพื่อใช้สำหรับ Reference ในอนาคต
    total: 0
  }),
  getters: {
    list (state) {
      return state.docList.map(doc => {
        let convertedData = doc.data()
        convertedData.updatedAt = convertedData.updatedAt.toDate()
        convertedData.uid = doc.id
        return convertedData
      })
    }
  },
  actions: {
    async loadProduct () {
      try {
        let productsCol = query(collection(db, 'products'))
        const productSnapshot = await getDocs(productsCol)
        this.docList = productSnapshot.docs || []
      } catch (error) {
        console.log('error', error)
      }
    },
    async getProduct (productUid) {
      try {
        const docRef = doc(db, 'products', productUid)
        const docSnap = await getDoc(docRef)
        return docSnap.data()
      } catch (error) {
        console.log('error', error)
      }
    },
    async addProduct (productData) {
      productData.remainQuantity = productData.quantity
      productData.updatedAt = new Date()
      console.log('productData', productData)
      try {
        await addDoc(collection(db, 'products'), productData)
      } catch (error) {
        console.log('error', error)
      }
    },
    async updateProduct (productUid, productData) {
      try {
        const updatedProduct = {
          name: productData.name,
          imageUrl: productData.imageUrl,
          quantity: productData.quantity,
          price: productData.price,
          remainQuantity: productData.quantity,
          status: productData.status,
          about: productData.about,
          updatedAt: new Date()
        }
        const docRef = doc(db, 'products', productUid)
        await setDoc(docRef, updatedProduct)
      } catch (error) {
        console.log('error', error)
      }
    },
    async removeProduct (productUid) {
      try {
        await deleteDoc(doc(db, 'products', productUid))
      } catch (error) {
        console.log('error', error)
      }
    }
  }
})

list, delete product

html
<script setup>
import { ref, computed, onMounted } from 'vue'
import { RouterLink } from 'vue-router'

import { useProductStore } from '@/stores/admin/product'
import { useEventStore } from '@/stores/event'

const productStore = useProductStore()
const eventStore = useEventStore()

onMounted(async () => {
  // เพิ่ม await สำหรับ loadProduct จาก Firestore ก่อน
  await productStore.loadProduct()
})

const removeProduct = async (index) => {
  try {
    // เปลี่ยนมาใช้ uid แทน index
    await productStore.removeProduct(productStore.list[index].uid)
    await productStore.loadProduct()
    eventStore.popupMessage('success', 'DELETE Successful!')
  } catch (error) {
    console.log('error', error)
  }
}
</script>
<template>
  <AdminLayout>
    <div class="flex-1  pt-8 px-6 bg-base-100">
      <div class="card w-full p-6 mt-2">
        <div class="text-xl font-semibold inline-block">
          Product
          <div class="inline-block float-right">
            <div class="inline-block float-right">
              <RouterLink
                to="/admin/products/create"
                class="btn px-6 btn-sm normal-case btn-primary"
              >
                Add New
              </RouterLink>
            </div>
          </div>
        </div>
        <div class="divider mt-2"></div>
        <div class="h-full w-full pb-6 bg-base-100 mt-2">
          <div class="overflow-x-auto w-full">
            <table class="table w-full">
              <thead>
                <tr>
                  <th>Name</th>
                  <th>Image</th>
                  <th>Price</th>
                  <th>Quantity</th>
                  <th>Status</th>
                  <th>Updated At</th>
                  <th></th>
                </tr>
              </thead>
              <tbody>
                <tr v-for="(product, index) in productStore.list" :key="index">
                  <td>
                    <div class="font-bold">{{ product.name }}</div>
                  </td>
                  <td>
                    <div class="mask mask-squircle w-12 h-12">
                      <img :src="product.imageUrl" />
                    </div>
                  </td>
                  <td>{{ product.price }}</td>
                  <td>{{ product.remainQuantity }} / {{ product.quantity }}</td>
                  <td>
                    <div class="badge" :class="product.status === 'open' ? 'badge-success' : 'badge-error'">
                      {{ product.status }}
                    </div>
                  </td>
                  <td>{{ product.updatedAt }}</td>
                  <td>
                    <!-- เปลี่ยนมาใช้ uid แทน-->
                    <RouterLink :to="{ name: 'admin-products-update', params: { id: product.uid } }">
                      <button class="btn btn-square btn-ghost">
                        <EditIcon></EditIcon>
                      </button>
                    </RouterLink>
                    <button @click="removeProduct(index)" class="btn btn-square btn-ghost">
                      <TrashIcon></TrashIcon>
                    </button>
                  </td>
                </tr>
              </tbody>
            </table>
          </div>
        </div>
      </div>
    </div>
  </AdminLayout>
</template>

add, edit product

html
<script setup>
import { onMounted, ref, reactive, computed } from 'vue'
import { useProductStore } from '@/stores/admin/product'
import { useEventStore } from '@/stores/event'
import { RouterLink, useRoute, useRouter } from 'vue-router'

import AdminLayout from '@/layouts/AdminLayout.vue'

const productStore = useProductStore()
const eventStore = useEventStore()

const route = useRoute()
const router = useRouter()

const productId = ref(-1)
let selectedProduct = ref({
  name: '',
  imageUrl: '',
  quantity: 0,
  about: '',
  status: 'open'
})

const mode = computed(() => {
  return productId.value !== -1 ? 'Edit' : 'Add'
})

onMounted(async () => {
  if (route.params.id) {
    productId.value = route.params.id
    // เพิ่ม await เนื่องจากเปลี่ยนจาก load ผ่าน array เป็น index
    selectedProduct.value = await productStore.getProduct(productId.value)
  }
})

const updateProduct = async () => {
  try {
    if (productId.value !== -1) {
      // เพิ่ม await แค่นั้น ที่เหลือเหมือนเดิม เนื่องจากใช้ uid ตั้งแต่ด้านบน
      await productStore.updateProduct(productId.value, selectedProduct.value)
      eventStore.popupMessage('success', 'Update Product successful!')
    } else {
      // เพิ่ม await แค่นั้น ที่เหลือเหมือนเดิม เนื่องจากใช้ uid ตั้งแต่ด้านบน
      await productStore.addProduct(selectedProduct.value)
      eventStore.popupMessage('success', 'Create Product successful!')
      router.push({ name: 'admin-products' })
    }
  } catch (error) {
    console.log('error', error)
  }
}
</script>

<template>
  <!-- code เหมือนเดิม -->
</template>