Skip to main content

จัดการ CRUD product

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

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

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

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

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

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

<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

<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>