From 240e89475ab101ef1bee7a11f44bb80418972d26 Mon Sep 17 00:00:00 2001 From: Sirapob Lapanakokiat <65160126@go.buu.ac.th> Date: Wed, 19 Mar 2025 20:39:21 +0700 Subject: [PATCH] Update File --- app.js | 2 + database/db.js | 60 ++++-- routes/auth.js | 10 +- routes/products.js | 223 ++++++++++++++++++++-- routes/stock.js | 94 ++++++++++ views/categories.ejs | 4 +- views/products.ejs | 434 ++++++++++++++++++++++++++++++++++--------- 7 files changed, 698 insertions(+), 129 deletions(-) create mode 100644 routes/stock.js diff --git a/app.js b/app.js index a10f765..8e21428 100644 --- a/app.js +++ b/app.js @@ -33,6 +33,7 @@ const authMiddleware = (req, res, next) => { const authRoutes = require('./routes/auth'); const productRoutes = require('./routes/products'); const categoriesRouter = require('./routes/categories'); +const stockRoutes = require('./routes/stock'); app.use('/auth', authRoutes); @@ -49,6 +50,7 @@ app.get('/', (req, res) => { app.use('/products', authMiddleware, productRoutes); app.use('/categories', authMiddleware, categoriesRouter); +app.use('/stock', stockRoutes); app.listen(3000, () => { console.log('Server running on port 3000'); diff --git a/database/db.js b/database/db.js index e084d68..90cf4a6 100644 --- a/database/db.js +++ b/database/db.js @@ -2,10 +2,10 @@ const mysql = require('mysql2'); const path = require('path'); const db = mysql.createConnection({ - host: 'localhost' || "10.104.22.31", - port: 3306, + host: 'localhost', + port: '3306', user: 'root', - password: '' || "MAXrkt06755", + password: '', database: 'Project' }); @@ -17,7 +17,7 @@ db.connect((err) => { console.log('Connected to MySQL database'); }); -// Create tables + db.query(`CREATE TABLE IF NOT EXISTS users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(255) UNIQUE, @@ -33,19 +33,47 @@ db.query(`CREATE TABLE IF NOT EXISTS categories ( if (err) console.error('Error creating categories table:', err); }); -db.query(`CREATE TABLE IF NOT EXISTS products ( - id INT PRIMARY KEY AUTO_INCREMENT, - name VARCHAR(255), - price DECIMAL(10,2), - category_id INT, - user_id INT, - FOREIGN KEY (category_id) REFERENCES categories(id), - FOREIGN KEY (user_id) REFERENCES users(id) -)`, (err) => { - if (err) console.error('Error creating products table:', err); -}); +db.query(`DROP TABLE IF EXISTS stock_movements`, (err) => { + if (err) console.error('Error dropping stock_movements table:', err); + + + db.query(`DROP TABLE IF EXISTS products`, (err) => { + if (err) console.error('Error dropping products table:', err); + + + db.query(`CREATE TABLE IF NOT EXISTS products ( + id INT PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(255), + price DECIMAL(10,2), + stock_quantity INT DEFAULT 0, + minimum_stock INT DEFAULT 0, + location VARCHAR(255), + last_restock_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + category_id INT, + user_id INT, + FOREIGN KEY (category_id) REFERENCES categories(id), + FOREIGN KEY (user_id) REFERENCES users(id) + )`, (err) => { + if (err) console.error('Error creating products table:', err); + + + db.query(`CREATE TABLE IF NOT EXISTS stock_movements ( + id INT PRIMARY KEY AUTO_INCREMENT, + product_id INT, + quantity INT, + type ENUM('in', 'out'), + date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + user_id INT, + description TEXT, + FOREIGN KEY (product_id) REFERENCES products(id), + FOREIGN KEY (user_id) REFERENCES users(id) + )`, (err) => { + if (err) console.error('Error creating stock_movements table:', err); + }); + }); + }); +}); const promiseDb = db.promise(); - module.exports = promiseDb; \ No newline at end of file diff --git a/routes/auth.js b/routes/auth.js index d6da6db..ba407c3 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -11,7 +11,7 @@ router.get('/register', (req, res) => { router.post('/register', async (req, res) => { try { const { username, password } = req.body; - // Check if username exists + const [existingUsers] = await db.query( 'SELECT * FROM users WHERE username = ?', [username] @@ -35,12 +35,12 @@ router.post('/register', async (req, res) => { } }); -// Login page route + router.get('/login', (req, res) => { res.render('login'); }); -// Login post route + router.post('/login', async (req, res) => { try { const { username, password } = req.body; @@ -58,7 +58,7 @@ router.post('/login', async (req, res) => { const match = await bcrypt.compare(password, user.password); if (match) { - // Create session + req.session.userId = user.id; req.session.username = user.username; res.redirect('/products'); @@ -71,7 +71,7 @@ router.post('/login', async (req, res) => { } }); -// Logout route + router.get('/logout', (req, res) => { req.session.destroy((err) => { if (err) { diff --git a/routes/products.js b/routes/products.js index b8550d4..a11fa05 100644 --- a/routes/products.js +++ b/routes/products.js @@ -15,26 +15,44 @@ const isAuthenticated = (req, res, next) => { router.get('/', isAuthenticated, async (req, res) => { try { const { search } = req.query; + + // Build query with search condition let sql = ` - SELECT p.*, c.name as category_name, u.username as created_by - FROM products p + SELECT p.*, c.name as category_name + FROM products p LEFT JOIN categories c ON p.category_id = c.id - LEFT JOIN users u ON p.user_id = u.id `; let params = []; if (search) { - sql += ` WHERE p.name LIKE ?`; - params.push(`%${search}%`); + sql += ` WHERE p.name LIKE ? OR p.location LIKE ?`; + params = [`%${search}%`, `%${search}%`]; } + // Execute query const [products] = await db.query(sql, params); const [categories] = await db.query('SELECT * FROM categories'); + + // Add helper function to be available in template + const getStockStatus = (current, minimum) => { + current = parseInt(current) || 0; + minimum = parseInt(minimum) || 0; + + if (current <= 0) { + return { text: 'หมด', class: 'status-out' }; + } + if (current <= minimum) { + return { text: 'ต่ำ', class: 'status-low' }; + } + return { text: 'ปกติ', class: 'status-normal' }; + }; + // Pass search value back to template res.render('products', { products, categories, - userId: req.session.userId + searchQuery: search || '', + getStockStatus // Pass the function to the template }); } catch (err) { console.error(err); @@ -45,10 +63,34 @@ router.get('/', isAuthenticated, async (req, res) => { // Add product router.post('/', isAuthenticated, async (req, res) => { try { - const { name, price, category_id } = req.body; + const { + name, + price, + stock_quantity, + minimum_stock, + location, + category_id + } = req.body; + await db.query( - 'INSERT INTO products (name, price, category_id, user_id) VALUES (?, ?, ?, ?)', - [name, price, category_id, req.session.userId] + `INSERT INTO products ( + name, + price, + stock_quantity, + minimum_stock, + location, + category_id, + user_id + ) VALUES (?, ?, ?, ?, ?, ?, ?)`, + [ + name, + price, + stock_quantity || 0, + minimum_stock || 0, + location || '', + category_id, + req.session.userId + ] ); res.redirect('/products'); } catch (err) { @@ -57,18 +99,83 @@ router.post('/', isAuthenticated, async (req, res) => { } }); +// Get edit form +router.get('/edit/:id', isAuthenticated, async (req, res) => { + try { + const [products] = await db.query( + 'SELECT * FROM products WHERE id = ?', + [req.params.id] + ); + const [categories] = await db.query('SELECT * FROM categories'); + + if (products.length === 0) { + return res.status(404).send('Product not found'); + } + + res.render('edit-product', { + product: products[0], + categories: categories + }); + } catch (err) { + console.error(err); + res.status(500).json({ error: 'Database error' }); + } +}); + +// Get product by ID +router.get('/:id', isAuthenticated, async (req, res) => { + try { + const [products] = await db.query( + 'SELECT * FROM products WHERE id = ?', + [req.params.id] + ); + + if (products.length === 0) { + return res.status(404).json({ error: 'Product not found' }); + } + + res.json(products[0]); + } catch (err) { + console.error(err); + res.status(500).json({ error: 'Database error' }); + } +}); + // Update product router.put('/:id', isAuthenticated, async (req, res) => { try { - const { name, price, category_id } = req.body; - await db.query( - 'UPDATE products SET name = ?, price = ?, category_id = ? WHERE id = ?', - [name, price, category_id, req.params.id] - ); + const { + name, + price, + stock_quantity, + minimum_stock, + location, + category_id + } = req.body; + + await db.query(` + UPDATE products + SET name = ?, + price = ?, + stock_quantity = ?, + minimum_stock = ?, + location = ?, + category_id = ? + WHERE id = ? + `, [ + name, + price, + stock_quantity, + minimum_stock, + location, + category_id, + req.params.id + ]); + res.json({ message: 'Product updated successfully' }); } catch (err) { console.error(err); - res.status(400).json({ error: 'Error updating product' }); + res.status(500).json({ error: 'Database error' }); } }); @@ -83,4 +190,90 @@ router.delete('/:id', isAuthenticated, async (req, res) => { } }); +// เพิ่มสต็อก +router.post('/stock/add', isAuthenticated, async (req, res) => { + try { + const { product_id, quantity, description } = req.body; + + await db.query('START TRANSACTION'); + + // บันทึกการเพิ่มสต็อก + await db.query( + 'INSERT INTO stock_movements (product_id, quantity, type, user_id, description) VALUES (?, ?, "in", ?, ?)', + [product_id, quantity, req.session.userId, description] + ); + + // อัพเดตจำนวนสต็อก + await db.query( + 'UPDATE products SET stock_quantity = stock_quantity + ? WHERE id = ?', + [quantity, product_id] + ); + + await db.query('COMMIT'); + res.json({ message: 'Stock added successfully' }); + } catch (err) { + await db.query('ROLLBACK'); + console.error(err); + res.status(400).json({ error: 'Error adding stock' }); + } +}); + +// ตัดสต็อก +router.post('/stock/remove', isAuthenticated, async (req, res) => { + try { + const { product_id, quantity, description } = req.body; + + await db.query('START TRANSACTION'); + + // ตรวจสอบจำนวนสต็อก + const [product] = await db.query( + 'SELECT stock_quantity FROM products WHERE id = ?', + [product_id] + ); + + if (product[0].stock_quantity < quantity) { + await db.query('ROLLBACK'); + return res.status(400).json({ error: 'Insufficient stock' }); + } + + // บันทึกการตัดสต็อก + await db.query( + 'INSERT INTO stock_movements (product_id, quantity, type, user_id, description) VALUES (?, ?, "out", ?, ?)', + [product_id, quantity, req.session.userId, description] + ); + + // อัพเดตจำนวนสต็อก + await db.query( + 'UPDATE products SET stock_quantity = stock_quantity - ? WHERE id = ?', + [quantity, product_id] + ); + + await db.query('COMMIT'); + res.json({ message: 'Stock removed successfully' }); + } catch (err) { + await db.query('ROLLBACK'); + console.error(err); + res.status(400).json({ error: 'Error removing stock' }); + } +}); + +// ดูประวัติการเคลื่อนไหวของสต็อก +router.get('/stock/history/:productId', isAuthenticated, async (req, res) => { + try { + const [movements] = await db.query(` + SELECT sm.*, u.username, p.name as product_name + FROM stock_movements sm + JOIN users u ON sm.user_id = u.id + JOIN products p ON sm.product_id = p.id + WHERE sm.product_id = ? + ORDER BY sm.date DESC + `, [req.params.productId]); + + res.json(movements); + } catch (err) { + console.error(err); + res.status(500).json({ error: 'Error fetching stock history' }); + } +}); + module.exports = router; \ No newline at end of file diff --git a/routes/stock.js b/routes/stock.js new file mode 100644 index 0000000..fe00590 --- /dev/null +++ b/routes/stock.js @@ -0,0 +1,94 @@ +const express = require('express'); +const router = express.Router(); +const db = require('../database/db'); + +// เพิ่มสต็อก (รับสินค้าเข้า) +router.post('/add', async (req, res) => { + try { + const { product_id, quantity, description } = req.body; + + await db.query('START TRANSACTION'); + + // บันทึกการเคลื่อนไหว + await db.query( + 'INSERT INTO stock_movements (product_id, quantity, type, user_id, description) VALUES (?, ?, "in", ?, ?)', + [product_id, quantity, req.session.userId, description] + ); + + // อัพเดตจำนวนสต็อก + await db.query( + 'UPDATE products SET stock_quantity = stock_quantity + ?, last_restock_date = CURRENT_TIMESTAMP WHERE id = ?', + [quantity, product_id] + ); + + await db.query('COMMIT'); + res.json({ success: true }); + } catch (err) { + await db.query('ROLLBACK'); + console.error(err); + res.status(500).json({ error: 'Database error' }); + } +}); + +// ตัดสต็อก (เบิกสินค้าออก) +router.post('/remove', async (req, res) => { + try { + const { product_id, quantity, description } = req.body; + + await db.query('START TRANSACTION'); + + // ตรวจสอบสต็อกคงเหลือ + const [product] = await db.query( + 'SELECT stock_quantity FROM products WHERE id = ?', + [product_id] + ); + + if (product[0].stock_quantity < quantity) { + await db.query('ROLLBACK'); + return res.status(400).json({ error: 'สินค้าในสต็อกไม่เพียงพอ' }); + } + + // บันทึกการเคลื่อนไหว + await db.query( + 'INSERT INTO stock_movements (product_id, quantity, type, user_id, description) VALUES (?, ?, "out", ?, ?)', + [product_id, quantity, req.session.userId, description] + ); + + // อัพเดตจำนวนสต็อก + await db.query( + 'UPDATE products SET stock_quantity = stock_quantity - ? WHERE id = ?', + [quantity, product_id] + ); + + await db.query('COMMIT'); + res.json({ success: true }); + } catch (err) { + await db.query('ROLLBACK'); + console.error(err); + res.status(500).json({ error: 'Database error' }); + } +}); + +// ดูประวัติการเคลื่อนไหวของสินค้า +router.get('/history/:productId', async (req, res) => { + try { + const [movements] = await db.query(` + SELECT + sm.*, + p.name as product_name, + u.username as user_name + FROM stock_movements sm + JOIN products p ON sm.product_id = p.id + JOIN users u ON sm.user_id = u.id + WHERE sm.product_id = ? + ORDER BY sm.date DESC + `, [req.params.productId]); + + res.json(movements); + } catch (err) { + console.error(err); + res.status(500).json({ error: 'Database error' }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/views/categories.ejs b/views/categories.ejs index 97b0d56..7dad304 100644 --- a/views/categories.ejs +++ b/views/categories.ejs @@ -33,8 +33,8 @@ <tr> <td><%= category.name %></td> <td> - <button onclick="editCategory(<%= category.id %>)" class="edit-btn">แก้ไข</button> - <button onclick="deleteCategory(<%= category.id %>)" class="delete-btn">ลบ</button> + <button onclick="editCategory('<%= category.id %>')" class="edit-btn">แก้ไข</button> + <button onclick="deleteCategory('<%= category.id %>')" class="delete-btn">ลบ</button> </td> </tr> <% }); %> diff --git a/views/products.ejs b/views/products.ejs index d54cc1d..fd50d82 100644 --- a/views/products.ejs +++ b/views/products.ejs @@ -3,133 +3,349 @@ <head> <title>จัดการสินค้า</title> <link rel="stylesheet" href="/css/style.css"> + <script> + // Define getStockStatus function first + function getStockStatus(current, minimum) { + current = parseInt(current) || 0; + minimum = parseInt(minimum) || 0; + + if (current <= 0) { + return { text: 'หมด', class: 'status-out' }; + } + if (current <= minimum) { + return { text: 'ต่ำ', class: 'status-low' }; + } + return { text: 'ปกติ', class: 'status-normal' }; + } + </script> </head> <body> <div class="container"> + <!-- ส่วนหัว --> <div class="header"> - <h1>จัดการสินค้า</h1> + <h1>จัดการสต็อกสินค้า</h1> <nav> - <a href="/categories" class="nav-link">จัดการหมวดหมู่</a> + <a href="/categories">จัดการหมวดหมู่</a> <a href="/auth/logout" class="logout-btn">ออกจากระบบ</a> </nav> </div> - <form action="/products" method="GET" class="search-form"> - <input type="text" name="search" placeholder="ค้นหาสินค้า"> - <button type="submit">ค้นหา</button> - </form> + <!-- แก้ไขส่วนค้นหา --> + <div class="search-section"> + <form action="/products" method="GET" class="search-form"> + <input + type="text" + name="search" + placeholder="ค้นหาสินค้า" + value="<%= searchQuery %>" + > + <button type="submit">ค้นหา</button> + <% if (searchQuery) { %> + <a href="/products" class="clear-search">ล้างการค้นหา</a> + <% } %> + </form> + </div> - - <form action="/products" method="POST" class="product-form"> - <input type="text" name="name" placeholder="ชื่อสินค้า" required> - <input type="number" name="price" placeholder="ราคา" required> - <select name="category_id" required> - <option value="">เลือกหมวดหมู่</option> - <% categories.forEach(category => { %> - <option value="<%= category.id %>"><%= category.name %></option> - <% }); %> - </select> - <button type="submit">เพิ่มสินค้า</button> - </form> - - <table> - <thead> - <tr> - <th>ชื่อสินค้า</th> - <th>ราคา</th> - <th>หมวดหมู่</th> - <th>ผู้สร้าง</th> - <th>จัดการ</th> - </tr> - </thead> - <tbody> - <% products.forEach(product => { %> + <!-- เพิ่มสินค้า --> + <div class="add-product-section"> + <form action="/products" method="POST"> + <div class="form-row"> + <input type="text" name="name" placeholder="ชื่อสินค้า" required> + <input type="number" name="price" placeholder="ราคา" required> + </div> + <div class="form-row"> + <input type="number" name="stock_quantity" placeholder="จำนวนในสต็อก" required> + <input type="number" name="minimum_stock" placeholder="จำนวนขั้นต่ำ" required> + </div> + <div class="form-row"> + <input type="text" name="location" placeholder="ตำแหน่งจัดเก็บ"> + <select name="category_id" required> + <option value="">เลือกหมวดหมู่</option> + <% categories.forEach(category => { %> + <option value="<%= category.id %>"><%= category.name %></option> + <% }); %> + </select> + </div> + <button type="submit">เพิ่มสินค้า</button> + </form> + </div> + + + <div class="product-list"> + <table> + <thead> <tr> - <td><%= product.name %></td> - <td><%= product.price %></td> - <td><%= product.category_name %></td> - <td><%= product.created_by %></td> - <td> - <% if (product.user_id === userId) { %> - <button onclick="editProduct(<%= JSON.stringify(product) %>)" class="edit-btn">แก้ไข</button> - <button onclick="deleteProduct(<%= product.id %>)" class="delete-btn">ลบ</button> - <% } %> - </td> + <th>ชื่อสินค้า</th> + <th>ราคา</th> + <th>จำนวน</th> + <th>ขั้นต่ำ</th> + <th>ตำแหน่ง</th> + <th>หมวดหมู่</th> + <th>สถานะ</th> + <th>จัดการ</th> </tr> - <% }); %> - </tbody> - </table> + </thead> + <tbody> + <% if (products && products.length > 0) { %> + <% products.forEach(product => { %> + <tr> + <td><%= product.name %></td> + <td><%= product.price %></td> + <td><%= product.stock_quantity %></td> + <td><%= product.minimum_stock %></td> + <td><%= product.location || '-' %></td> + <td><%= product.category_name %></td> + <td> + <% const status = getStockStatus(product.stock_quantity, product.minimum_stock); %> + <span class="status <%= status.class %>"><%= status.text %></span> + </td> + <td> + <button onclick="editProduct('<%= product.id %>')">แก้ไข</button> + <button onclick="deleteProduct('<%= product.id %>')">ลบ</button> + <button onclick="showStockModal('<%= product.id %>', 'in')">เพิ่มสต็อก</button> + <button onclick="showStockModal('<%= product.id %>', 'out')">เบิกสต็อก</button> + <button onclick="showHistory('<%= product.id %>')">ประวัติ</button> + </td> + </tr> + <% }); %> + <% } else { %> + <tr> + <td colspan="8" class="empty-message">ไม่พบข้อมูลสินค้า</td> + </tr> + <% } %> + </tbody> + </table> + </div> </div> - + <div id="editModal" class="modal"> <div class="modal-content"> <h2>แก้ไขสินค้า</h2> <form id="editForm"> - <input type="text" id="editName" placeholder="ชื่อสินค้า" required> - <input type="number" id="editPrice" placeholder="ราคา" required> - <select id="editCategory" required> - <% categories.forEach(category => { %> - <option value="<%= category.id %>"><%= category.name %></option> - <% }); %> - </select> - <button type="submit">บันทึก</button> - <button type="button" onclick="closeModal()">ยกเลิก</button> + <div class="form-row"> + <input type="text" id="editName" placeholder="ชื่อสินค้า" required> + <input type="number" id="editPrice" placeholder="ราคา" required> + </div> + <div class="form-row"> + <input type="number" id="editStockQuantity" placeholder="จำนวนในสต็อก" required> + <input type="number" id="editMinimumStock" placeholder="จำนวนขั้นต่ำ" required> + </div> + <div class="form-row"> + <input type="text" id="editLocation" placeholder="ตำแหน่งจัดเก็บ"> + <select id="editCategory" required> + <option value="">เลือกหมวดหมู่</option> + <% categories.forEach(category => { %> + <option value="<%= category.id %>"><%= category.name %></option> + <% }); %> + </select> + </div> + <div class="form-actions"> + <button type="submit">บันทึก</button> + <button type="button" onclick="closeEditModal()">ยกเลิก</button> + </div> </form> </div> </div> - + + + <div id="stockModal" class="modal"> + <div class="modal-content"> + <h2 id="modalTitle"></h2> + <form id="stockForm"> + <input type="number" id="stockQuantity" placeholder="จำนวน" required> + <textarea id="stockDescription" placeholder="รายละเอียด"></textarea> + <div class="form-actions"> + <button type="submit">บันทึก</button> + <button type="button" onclick="closeModal()">ยกเลิก</button> + </div> + </form> + </div> + </div> + + + <div id="historyModal" class="modal"> + <div class="modal-content"> + <h2>ประวัติการเคลื่อนไหว</h2> + <table id="historyTable"> + <thead> + <tr> + <th>วันที่</th> + <th>ประเภท</th> + <th>จำนวน</th> + <th>ผู้ดำเนินการ</th> + <th>รายละเอียด</th> + </tr> + </thead> + <tbody></tbody> + </table> + <button onclick="closeHistoryModal()">ปิด</button> + </div> + </div> + <script> - const modal = document.getElementById('editModal'); + // ฟังก์ชันจัดการสินค้า + const editModal = document.getElementById('editModal'); let currentProductId = null; - async function deleteProduct(id) { - if (confirm('ต้องการลบสินค้านี้?')) { - const response = await fetch(`/products/${id}`, { - method: 'DELETE' + function editProduct(id) { + currentProductId = id; + fetch(`/products/${id}`) + .then(response => response.json()) + .then(product => { + document.getElementById('editName').value = product.name; + document.getElementById('editPrice').value = product.price; + document.getElementById('editStockQuantity').value = product.stock_quantity; + document.getElementById('editMinimumStock').value = product.minimum_stock; + document.getElementById('editLocation').value = product.location || ''; + document.getElementById('editCategory').value = product.category_id; + editModal.style.display = 'block'; }); - if (response.ok) { - location.reload(); - } - } - } - - function editProduct(product) { - currentProductId = product.id; - document.getElementById('editName').value = product.name; - document.getElementById('editPrice').value = product.price; - document.getElementById('editCategory').value = product.category_id; - modal.style.display = 'block'; } - function closeModal() { - modal.style.display = 'none'; + function closeEditModal() { + editModal.style.display = 'none'; } - document.getElementById('editForm').onsubmit = async (e) => { + document.getElementById('editForm').onsubmit = function(e) { e.preventDefault(); const data = { name: document.getElementById('editName').value, price: document.getElementById('editPrice').value, + stock_quantity: document.getElementById('editStockQuantity').value, + minimum_stock: document.getElementById('editMinimumStock').value, + location: document.getElementById('editLocation').value, category_id: document.getElementById('editCategory').value }; - - const response = await fetch(`/products/${currentProductId}`, { + + fetch(`/products/${currentProductId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) + }).then(() => { + closeEditModal(); + window.location.reload(); }); + }; - if (response.ok) { - location.reload(); + function deleteProduct(id) { + if (confirm('ต้องการลบสินค้านี้?')) { + fetch(`/products/${id}`, { method: 'DELETE' }) + .then(() => window.location.reload()); + } + } + + let currentType; + + function showStockModal(productId, type) { + currentProductId = productId; + currentType = type; + document.getElementById('modalTitle').textContent = type === 'in' ? 'เพิ่มสต็อก' : 'เบิกสต็อก'; + document.getElementById('stockModal').style.display = 'block'; + } + + function closeModal() { + document.getElementById('stockModal').style.display = 'none'; + document.getElementById('stockForm').reset(); + } + + document.getElementById('stockForm').onsubmit = async function(e) { + e.preventDefault(); + const data = { + product_id: currentProductId, + quantity: document.getElementById('stockQuantity').value, + description: document.getElementById('stockDescription').value + }; + + const url = currentType === 'in' ? '/stock/add' : '/stock/remove'; + + try { + const response = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }); + + if (response.ok) { + closeModal(); + location.reload(); + } else { + const error = await response.json(); + alert(error.error); + } + } catch (err) { + console.error(err); + alert('เกิดข้อผิดพลาด'); } }; + + async function showHistory(productId) { + try { + const response = await fetch(`/stock/history/${productId}`); + const movements = await response.json(); + + const tbody = document.querySelector('#historyTable tbody'); + tbody.innerHTML = movements.map(m => ` + <tr> + <td>${new Date(m.date).toLocaleString()}</td> + <td>${m.type === 'in' ? 'รับเข้า' : 'เบิกออก'}</td> + <td>${m.quantity}</td> + <td>${m.user_name}</td> + <td>${m.description || '-'}</td> + </tr> + `).join(''); + + document.getElementById('historyModal').style.display = 'block'; + } catch (err) { + console.error(err); + alert('เกิดข้อผิดพลาด'); + } + } + + function closeHistoryModal() { + document.getElementById('historyModal').style.display = 'none'; + } </script> <style> + .container { padding: 20px; } + + .form-row { + display: flex; + gap: 10px; + margin-bottom: 10px; + } + + .status { + padding: 5px 10px; + border-radius: 4px; + font-weight: bold; + } + + .status-out { + background: #dc3545; + color: white; + } + + .status-low { + background: #ffc107; + color: black; + } + + .status-normal { + background: #28a745; + color: white; + } + + button { + padding: 5px 10px; + border-radius: 4px; + cursor: pointer; + } + .modal { display: none; position: fixed; @@ -138,38 +354,74 @@ width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); + z-index: 1000; } .modal-content { background-color: white; margin: 15% auto; padding: 20px; + width: 80%; + max-width: 600px; border-radius: 5px; - width: 50%; - max-width: 500px; + position: relative; } - .nav-link { - color: #007bff; - text-decoration: none; - margin-right: 15px; + .form-actions { + display: flex; + gap: 10px; + justify-content: flex-end; + margin-top: 20px; + } + + .form-actions button { + padding: 8px 16px; } - .edit-btn { - background-color: #ffc107; - color: black; + .form-actions button[type="submit"] { + background-color: #28a745; + color: white; + border: none; } - .delete-btn { + .form-actions button[type="button"] { background-color: #dc3545; color: white; + border: none; + } + + .search-section { + margin-bottom: 20px; + } + + .search-form { + display: flex; + gap: 10px; + align-items: center; } - .product-form select { + .search-form input { padding: 8px; border: 1px solid #ddd; border-radius: 4px; - margin-right: 10px; + width: 300px; + } + + .search-form button { + padding: 8px 16px; + background: #007bff; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + } + + .clear-search { + padding: 8px 16px; + background: #6c757d; + color: white; + text-decoration: none; + border-radius: 4px; } </style> </body> -- GitLab