diff --git a/server/server.js b/server/server.js index 2e968ff..68441c2 100644 --- a/server/server.js +++ b/server/server.js @@ -271,7 +271,7 @@ app.get("/api/auth/me", auth, async (req, res, next) => { app.get("/api/products", async (req, res, next) => { try { const [rows] = await pool.query( - "SELECT id, user_id, title, category, price, `condition`, location, seller, whatsapp, image, description, status, created_at FROM products ORDER BY created_at DESC, id DESC" + "SELECT id, user_id, title, category, price, `condition`, location, seller, whatsapp, image, description, status, created_at FROM products WHERE status IS NULL OR status = 'Tersedia' ORDER BY created_at DESC, id DESC" ) res.json({ products: await attachProductImages(rows) }) } catch (error) { @@ -365,6 +365,54 @@ app.patch("/api/products/:id/status", auth, async (req, res, next) => { } }) +app.put( + "/api/products/:id", + auth, + upload.fields([ + { name: "images", maxCount: maxImageCount }, + { name: "image", maxCount: maxImageCount }, + { name: "photos", maxCount: maxImageCount } + ]), + async (req, res, next) => { + try { + const product = await findProductById(req.params.id) + if (!product || product.user_id !== req.user.id) { + res.status(404).json({ message: "Produk milik kamu tidak ditemukan." }) + return + } + + const { title, category, price, condition, location, seller, whatsapp, description } = req.body + + if (!title || !category || !price || !condition || !location || !seller || !whatsapp || !description) { + res.status(400).json({ message: "Semua field produk wajib diisi." }) + return + } + + await pool.query( + "UPDATE products SET title = ?, category = ?, price = ?, `condition` = ?, location = ?, seller = ?, whatsapp = ?, description = ? WHERE id = ? AND user_id = ?", + [title, category, Number(price), condition, location, seller, whatsapp, description, req.params.id, req.user.id] + ) + + const uploadedImages = getUploadedImages(req) + if (uploadedImages.length) { + await deleteUploadedImages(product) + await pool.query("DELETE FROM product_images WHERE product_id = ?", [req.params.id]) + + const mainImagePath = `/uploads/${uploadedImages[0].filename}` + await pool.query("UPDATE products SET image = ? WHERE id = ?", [mainImagePath, req.params.id]) + + const imageRows = uploadedImages.map((file, index) => [req.params.id, `/uploads/${file.filename}`, index]) + await pool.query("INSERT INTO product_images (product_id, image, sort_order) VALUES ?", [imageRows]) + } + + const updatedProduct = await findProductById(req.params.id) + res.json({ product: updatedProduct }) + } catch (error) { + next(error) + } + } +) + app.delete("/api/products/:id", auth, async (req, res, next) => { try { const product = await findProductById(req.params.id) diff --git a/server/uploads/1780319234485-521217012.jpeg b/server/uploads/1780319234485-521217012.jpeg new file mode 100644 index 0000000..582837b Binary files /dev/null and b/server/uploads/1780319234485-521217012.jpeg differ diff --git a/src/router/index.js b/src/router/index.js index 3ac9ca7..01f01a5 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -9,6 +9,7 @@ import SavedView from "../views/SavedView.vue" import LoginView from "../views/LoginView.vue" import RegisterView from "../views/RegisterView.vue" import AdminProductsView from "../views/AdminProductsView.vue" +import EditProductView from "../views/EditProductView.vue" import { getCurrentUser, getToken } from "../utils" const routes = [ @@ -17,6 +18,7 @@ const routes = [ { path: "/produk/:id", name: "product-detail", component: ProductDetailView }, { path: "/jual", name: "sell-product", component: SellProductView, meta: { requiresAuth: true } }, { path: "/dashboard", name: "dashboard", component: DashboardView, meta: { requiresAuth: true } }, + { path: "/edit/:id", name: "edit-product", component: EditProductView, meta: { requiresAuth: true } }, { path: "/tersimpan", name: "saved", component: SavedView, meta: { requiresAuth: true } }, { path: "/admin/products", diff --git a/src/utils.js b/src/utils.js index 725ba6a..ec9ed62 100644 --- a/src/utils.js +++ b/src/utils.js @@ -109,6 +109,13 @@ export function createProduct(formData) { }) } +export function updateProduct(id, formData) { + return requestJson(`/api/products/${id}`, { + method: "PUT", + body: formData + }) +} + export function updateProductStatus(id, status) { return requestJson(`/api/products/${id}/status`, { method: "PATCH", diff --git a/src/views/DashboardView.vue b/src/views/DashboardView.vue index 132da19..49bc0ae 100644 --- a/src/views/DashboardView.vue +++ b/src/views/DashboardView.vue @@ -51,7 +51,10 @@
Detail - + Edit +
@@ -93,9 +96,10 @@ async function loadProducts() { } } -async function markSold(id) { +async function toggleSold(id, currentStatus) { try { - const data = await updateProductStatus(id, "Terjual") + const newStatus = currentStatus === "Terjual" ? "Tersedia" : "Terjual" + const data = await updateProductStatus(id, newStatus) userProducts.value = userProducts.value.map((product) => { if (product.id === id) return data.product return product diff --git a/src/views/EditProductView.vue b/src/views/EditProductView.vue new file mode 100644 index 0000000..7f99373 --- /dev/null +++ b/src/views/EditProductView.vue @@ -0,0 +1,248 @@ + + + diff --git a/src/views/MarketplaceView.vue b/src/views/MarketplaceView.vue index eab026d..a20dd47 100644 --- a/src/views/MarketplaceView.vue +++ b/src/views/MarketplaceView.vue @@ -95,7 +95,9 @@ const filteredProducts = computed(() => { const matchCondition = selectedCondition.value === "Semua" || product.condition === selectedCondition.value - return matchSearch && matchCategory && matchCondition + const matchStatus = product.status === "Tersedia" || !product.status + + return matchSearch && matchCategory && matchCondition && matchStatus }) })