diff --git a/app.js b/app.js index 15ca0457a0f82610888f529eca2cae41e7a0015d..316a7477e7b6bc9770b38fac9a0504ee7e5d5fbb 100644 --- a/app.js +++ b/app.js @@ -20,14 +20,10 @@ app.use(express.static(path.join(__dirname, 'public'))); // การตั้งค่า body-parser สำหรับรับข้อมูลจากแบบฟอร์ม app.use(express.urlencoded({ extended: true })); app.use(express.json()); -app.use((req, res, next) => { - res.locals.session = req.session; // ส่ง session ไปที่ทุกไฟล์ EJS - next(); -}); // กำหนดการใช้งาน session app.use(session({ - secret: 'your_secret_key', + secret: 'RUK', // ใช้ key ที่คุณต้องการ resave: false, saveUninitialized: false })); @@ -35,6 +31,12 @@ app.use(session({ // เพิ่มการตั้งค่าเสิร์ฟไฟล์จากโฟลเดอร์ 'uploads' app.use('/uploads', express.static(path.join(__dirname, 'public/uploads'))); +// ส่งค่า session ไปที่ทุกไฟล์ EJS +app.use((req, res, next) => { + res.locals.session = req.session; // ส่ง session ไปที่ทุกไฟล์ EJS + next(); +}); + // กำหนด Routes สำหรับหน้าอื่นๆ import authRoutes from './routes/auth.js'; import productRoutes from './routes/products.js'; diff --git a/db.js b/db.js index bbe4131a26efb7e19a3daf55bccba76302c45451..9bc7e148e3038358a285938b665c1524928b2f12 100644 --- a/db.js +++ b/db.js @@ -1,7 +1,6 @@ import mysql from 'mysql2'; import dotenv from 'dotenv'; - dotenv.config(); const db = mysql.createConnection({ diff --git a/package-lock.json b/package-lock.json index ce0da577afb52a46595fc7984f153bd408d4c599..6b44279b7aacf5bd37dbf1880f123fc8dca19bbf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "bcrypt": "^5.1.1", + "bcryptjs": "^3.0.2", "body-parser": "^1.20.3", "dotenv": "^16.4.7", "ejs": "^3.1.10", @@ -198,6 +199,15 @@ "node": ">= 10.0.0" } }, + "node_modules/bcryptjs": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz", + "integrity": "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", diff --git a/package.json b/package.json index 40c891b3c762b4015ff25bc767f135034b656815..43772cbb52880adc5b49656275d4b1da0c03379e 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "description": "", "dependencies": { "bcrypt": "^5.1.1", + "bcryptjs": "^3.0.2", "body-parser": "^1.20.3", "dotenv": "^16.4.7", "ejs": "^3.1.10", diff --git a/public/css/style.css b/public/css/style.css index 165c4969d7fb39974226c10b3a1b48f246404342..145e027ce9271e4de9259c0f25017225c9ab737e 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -1,139 +1,207 @@ -/* ตั้งค่าพื้นฐาน */ +/* กำหนดค่าเริ่มต้น */ * { margin: 0; padding: 0; box-sizing: border-box; - font-family: Arial, sans-serif; -} - -body { - background-color: #f4f7fa; - color: #333; - font-size: 16px; + font-family: 'Arial', sans-serif; line-height: 1.6; } -h2 { - color: #333; - font-size: 24px; - margin-bottom: 20px; +/* ส่วนหัว (Header) */ +header { + background-color: #2c3e50; /* สีเข้มสำหรับหัว */ + color: #ecf0f1; /* สีอ่อนสำหรับตัวอักษร */ + padding: 10px 0; text-align: center; } -/* ส่วนฟอร์มเพิ่มสินค้า */ -form { - background-color: #fff; - padding: 20px; - border-radius: 8px; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); - max-width: 500px; - margin: 0 auto; - margin-top: 40px; +header h1 { + font-size: 28px; } -form div { - margin-bottom: 20px; +nav ul { + display: flex; + justify-content: center; + list-style: none; + padding: 0; + margin: 0; +} + +nav li { + margin: 0 15px; } -form label { - font-weight: bold; +nav a { + color: #ecf0f1; + text-decoration: none; + font-size: 16px; + padding: 10px 15px; display: block; - margin-bottom: 8px; - color: #555; + border-radius: 5px; } -form input[type="text"], -form input[type="number"], -form input[type="file"] { +nav a:hover { + background-color: #3498db; /* สีพื้นหลังเมื่อ hover */ + color: white; + transition: background-color 0.3s ease; +} + +/* ฟอร์มการเพิ่มสินค้า */ +form { + background-color: #f9f9f9; /* สีพื้นหลังที่นุ่มนวล */ + padding: 20px; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + max-width: 600px; + margin: 20px auto; +} + +form input, +form select, +form button { width: 100%; padding: 10px; + margin-bottom: 15px; + border-radius: 5px; border: 1px solid #ccc; - border-radius: 4px; - font-size: 16px; } -form input[type="text"]:focus, -form input[type="number"]:focus, -form input[type="file"]:focus { - border-color: #007bff; - outline: none; +form input[type="text"], +form input[type="number"], +form select { + background-color: #fff; } form button { - width: 100%; - padding: 10px; - background-color: #28a745; + background-color: #3498db; /* สีพื้นหลังปุ่ม */ + color: white; + font-size: 16px; border: none; - border-radius: 4px; - color: #fff; - font-size: 18px; cursor: pointer; - transition: background-color 0.3s; } form button:hover { - background-color: #218838; + background-color: #2980b9; /* สีพื้นหลังปุ่มเมื่อ hover */ } -/* ส่วนแสดงสินค้าทั้งหมด */ +/* ตารางสินค้า */ +table { + width: 100%; + border-collapse: collapse; + margin-top: 20px; +} + +table th, +table td { + padding: 15px; + text-align: center; + border: 1px solid #ddd; +} + +table th { + background-color: #2c3e50; /* สีพื้นหลังหัวตาราง */ + color: #fff; +} + +table tr:nth-child(even) { + background-color: #ecf0f1; /* สีพื้นหลังแถวคู่ */ +} + +table tr:hover { + background-color: #bdc3c7; /* สีพื้นหลังเมื่อ hover แถว */ +} + +table a { + color: #3498db; + text-decoration: none; + padding: 5px 10px; + border-radius: 5px; +} + +table a:hover { + background-color: #3498db; + color: white; +} + +/* หน้าแสดงหมวดหมู่สินค้า */ ul { - list-style-type: none; + list-style: none; padding: 0; - margin-top: 20px; - display: grid; - grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); - gap: 20px; } ul li { - background-color: #fff; + border: 1px solid #ddd; + padding: 15px; + margin-bottom: 20px; border-radius: 8px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - padding: 20px; - text-align: center; - transition: transform 0.3s ease, box-shadow 0.3s ease; + background-color: #f9f9f9; + display: flex; + justify-content: space-between; + align-items: center; +} + +ul li h3 { + font-size: 18px; } -ul li:hover { - transform: translateY(-5px); - box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); +ul li p { + margin: 5px 0; } ul li img { - max-width: 100%; - height: auto; + max-width: 100px; border-radius: 8px; - margin-bottom: 10px; } -ul li h3 { +ul li a { + text-decoration: none; + padding: 10px 15px; + background-color: #3498db; + color: white; + border-radius: 5px; +} + +ul li a:hover { + background-color: #2980b9; +} + +/* การแสดงผลเมื่อไม่มีข้อมูล */ +.no-data { + text-align: center; font-size: 18px; - color: #333; - margin-bottom: 10px; + color: #7f8c8d; + margin-top: 20px; } -ul li p { - font-size: 16px; - color: #555; +/* ปุ่มที่ใช้บ่อย */ +button { + background-color: #3498db; + color: white; + padding: 10px 20px; + border-radius: 5px; + border: none; + cursor: pointer; } -/* ปรับแต่งการแสดงผลสำหรับหน้าจอมือถือ */ -@media (max-width: 768px) { - ul { - grid-template-columns: 1fr 1fr; - } +button:hover { + background-color: #2980b9; + transition: background-color 0.3s ease; +} - form { - width: 90%; - } +/* ส่วนเท้า (footer) */ +footer { + background-color: #2c3e50; + color: #ecf0f1; + text-align: center; + padding: 20px; + margin-top: 20px; } -@media (max-width: 480px) { - ul { - grid-template-columns: 1fr; - } +footer a { + color: #ecf0f1; + text-decoration: none; +} - form { - width: 95%; - } +footer a:hover { + color: #3498db; } diff --git a/public/uploads/1742773725716.jpg b/public/uploads/1742773725716.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e99f115030ea94eadad6143355aae1ccca74563e Binary files /dev/null and b/public/uploads/1742773725716.jpg differ diff --git a/public/uploads/1742773853188.avif b/public/uploads/1742773853188.avif new file mode 100644 index 0000000000000000000000000000000000000000..40b08cf20681dab76bf8fba6984f4ad94a9285fc Binary files /dev/null and b/public/uploads/1742773853188.avif differ diff --git a/public/uploads/1742773923536.jpg b/public/uploads/1742773923536.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8ca86b366bf5bf7026fc9535d449ba04cd5bff5c Binary files /dev/null and b/public/uploads/1742773923536.jpg differ diff --git a/routes/auth.js b/routes/auth.js index 619f06852fc5495c80b7e3fc2e96b6f7a30bdb14..6f7bc27292d4034a1c056d74591cb74fae6d3a3c 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -4,13 +4,20 @@ import bcrypt from 'bcryptjs'; // ใช้ bcryptjs แทน bcrypt const router = express.Router(); -// หน้า login +// ตรวจสอบ session ก่อนที่จะแสดงหน้า login router.get('/login', (req, res) => { + if (req.session.username) { + return res.redirect('/'); // ถ้าผู้ใช้ล็อกอินแล้วให้ไปที่หน้า dashboard + } res.render('login'); }); + // หน้า register router.get('/register', (req, res) => { + if (req.session.username) { + return res.redirect('/'); // ถ้าผู้ใช้ล็อกอินแล้วให้ไปที่หน้า dashboard + } res.render('register'); }); @@ -57,6 +64,7 @@ router.post('/login', (req, res) => { }); }); + // ออกจากระบบ router.get('/logout', (req, res) => { req.session.destroy((err) => { @@ -67,4 +75,5 @@ router.get('/logout', (req, res) => { }); }); + export default router; diff --git a/routes/categories.js b/routes/categories.js index 5a383af93a366793f27e7fccff68d1a0d7f515d2..f5fb7ae66fc24fbcafae99e346d1f7f2e899067a 100644 --- a/routes/categories.js +++ b/routes/categories.js @@ -6,7 +6,6 @@ const router = express.Router(); // หน้าแสดงหมวดหมู่ router.get('/', (req, res) => { const query = 'SELECT * FROM categories'; - db.query(query, (err, results) => { if (err) { return res.send('เกิดข้อผิดพลาด'); @@ -15,12 +14,16 @@ router.get('/', (req, res) => { }); }); +// 👉 หน้าเพิ่มหมวดหมู่ (ที่หายไป) +router.get('/add', (req, res) => { + res.render('addCategory'); +}); + // เพิ่มหมวดหมู่ใหม่ router.post('/add', (req, res) => { const { name } = req.body; const query = 'INSERT INTO categories (name) VALUES (?)'; - - db.query(query, [name], (err, result) => { + db.query(query, [name], (err) => { if (err) { return res.send('เกิดข้อผิดพลาด'); } @@ -28,4 +31,14 @@ router.post('/add', (req, res) => { }); }); +// 👉 เส้นทางลบหมวดหมู่ +router.get('/delete/:id', (req, res) => { + const categoryId = req.params.id; + const query = 'DELETE FROM categories WHERE category_id = ?'; + db.query(query, [categoryId], (err) => { + if (err) return res.send('เกิดข้อผิดพลาดในการลบหมวดหมู่'); + res.redirect('/categories'); + }); +}); + export default router; diff --git a/routes/products.js b/routes/products.js index e4dc8206b309d3dc29cd65239e5ae2fc862d64e9..4c77749e96e29f84acf7146a5c4d0b8230b478dc 100644 --- a/routes/products.js +++ b/routes/products.js @@ -17,59 +17,99 @@ const storage = multer.diskStorage({ }); const upload = multer({ storage: storage }); -// แสดงสินค้าทั้งหมด +// แสดงสินค้าทั้งหมดพร้อมชื่อหมวดหมู่ router.get('/', (req, res) => { - const query = 'SELECT * FROM products'; + if (!req.session.username) { + return res.redirect('/auth/login'); + } + + const query = ` + SELECT p.*, c.name AS category_name + FROM products p + LEFT JOIN categories c ON p.category_id = c.category_id + `; db.query(query, (err, result) => { if (err) { return res.send('เกิดข้อผิดพลาดในการดึงข้อมูล'); } - res.render('products', { products: result, searchQuery: '' }); + res.render('products', { products: result, searchQuery: '', session: req.session }); + }); +}); + +// แสดงสินค้าตามหมวดหมู่ +router.get('/category/:category_id', (req, res) => { + const categoryId = req.params.category_id; + + const query = 'SELECT * FROM products WHERE category_id = ?'; + + db.query(query, [categoryId], (err, result) => { + if (err) { + return res.send('เกิดข้อผิดพลาดในการดึงข้อมูลสินค้า'); + } + + // ส่งข้อมูลสินค้าและหมวดหมู่ไปยังหน้าแสดงสินค้า + res.render('products', { products: result, searchQuery: '', session: req.session }); }); }); + + // ค้นหาสินค้า router.get('/search', (req, res) => { const searchQuery = req.query.search || ''; - const query = 'SELECT * FROM products WHERE name LIKE ?'; + const query = ` + SELECT p.*, c.name AS category_name + FROM products p + LEFT JOIN categories c ON p.category_id = c.category_id + WHERE p.name LIKE ? + `; + db.query(query, [`%${searchQuery}%`], (err, result) => { if (err) return res.send('เกิดข้อผิดพลาดในการค้นหา'); - res.render('products', { products: result, searchQuery }); + res.render('products', { products: result, searchQuery, session: req.session }); }); }); -// หน้าเพิ่มสินค้า +// หน้าเพิ่มสินค้า พร้อมดึง categories ไปแสดง router.get('/add', (req, res) => { - res.render('addProduct'); + if (!req.session.username) { + return res.redirect('/auth/login'); + } + const categoryQuery = 'SELECT * FROM categories'; + db.query(categoryQuery, (err, categories) => { + if (err) return res.send('เกิดข้อผิดพลาดในการดึงหมวดหมู่'); + res.render('addProduct', { session: req.session, categories }); + }); }); -// เส้นทางสำหรับบันทึกสินค้าที่เพิ่มใหม่ +// บันทึกสินค้าที่เพิ่มใหม่พร้อม category router.post('/add', upload.single('image'), (req, res) => { - const { name, price, stock } = req.body; + const { name, price, stock, category_id } = req.body; const image = req.file ? '/uploads/' + req.file.filename : null; - - const query = 'INSERT INTO products (name, price, stock, image) VALUES (?, ?, ?, ?)'; - - db.query(query, [name, price, stock, image], (err, result) => { + + const query = 'INSERT INTO products (name, price, stock, image, category_id) VALUES (?, ?, ?, ?, ?)'; + db.query(query, [name, price, stock, image, category_id], (err) => { if (err) { return res.send('เกิดข้อผิดพลาดในการบันทึกข้อมูลสินค้า: ' + err); } - res.redirect('/products'); // ไปที่หน้าแสดงสินค้าทั้งหมดหลังจากบันทึก + res.redirect('/products'); }); }); - -// หน้าแก้ไขสินค้า +// หน้าแก้ไขสินค้า (ถ้าต้องการสามารถเพิ่มเติมได้ให้เลือก category ด้วย) router.get('/edit/:id', (req, res) => { + if (!req.session.username) { + return res.redirect('/auth/login'); + } const productId = req.params.id; const query = 'SELECT * FROM products WHERE product_id = ?'; db.query(query, [productId], (err, result) => { if (err || result.length === 0) return res.send('ไม่พบสินค้านี้'); - res.render('editProduct', { product: result[0] }); + res.render('editProduct', { product: result[0], session: req.session }); }); }); -// บันทึกข้อมูลสินค้าที่แก้ไข +// แก้ไขสินค้า router.post('/edit/:id', upload.single('image'), (req, res) => { const productId = req.params.id; const { name, price } = req.body; diff --git a/views/addCategory.ejs b/views/addCategory.ejs new file mode 100644 index 0000000000000000000000000000000000000000..d3de39ead385b1ab3f72e2ee74254361ea0913b5 --- /dev/null +++ b/views/addCategory.ejs @@ -0,0 +1,16 @@ +<%- include('partials/header') %> + +<h2>เพิ่มหมวดหมู่ใหม่</h2> + +<form action="/categories/add" method="POST"> + <div> + <label for="name">ชื่อหมวดหมู่:</label> + <input type="text" id="name" name="name" required> + </div> + <br> + <button type="submit">บันทึก</button> +</form> + +<a href="/categories" style="margin-top: 20px; display:inline-block;">กลับไปหน้าหมวดหมู่</a> + +<%- include('partials/footer') %> diff --git a/views/addProduct.ejs b/views/addProduct.ejs index 0935385b665901737d09c8b5c046b71885afa3c6..08450f13ecfb4602cac52a9b29a8194f17aa0732 100644 --- a/views/addProduct.ejs +++ b/views/addProduct.ejs @@ -15,6 +15,15 @@ <label>จำนวนสินค้าในคลัง:</label> <input type="number" name="stock" min="0" required> </div> + <div> + <label>หมวดหมู่สินค้า:</label> + <select name="category_id" required> + <option value="">-- กรุณาเลือกหมวดหมู่ --</option> + <% categories.forEach(category => { %> + <option value="<%= category.category_id %>"><%= category.name %></option> + <% }) %> + </select> + </div> <div> <label>รูปสินค้า:</label> <input type="file" name="image" accept="image/*"> diff --git a/views/categories.ejs b/views/categories.ejs index 6bb9029683c4993aab8fed392536f5aa4726c132..42f28fc0d880806afadc467a755bb7f8c474626c 100644 --- a/views/categories.ejs +++ b/views/categories.ejs @@ -2,27 +2,21 @@ <h2>หมวดหมู่สินค้า</h2> -<table> - <thead> - <tr> - <th>ชื่อหมวดหมู่</th> - </tr> - </thead> - <tbody> - <% if (categories && categories.length > 0) { %> - <% categories.forEach(category => { %> - <tr> - <td><%= category.name %></td> - </tr> - <% }) %> - <% } else { %> - <tr> - <td colspan="1">ไม่มีหมวดหมู่สินค้า</td> - </tr> - <% } %> - </tbody> -</table> +<!-- แสดงหมวดหมู่ --> +<% if (categories && categories.length > 0) { %> + <ul style="list-style: none; padding: 0;"> + <% categories.forEach(category => { %> + <li> + <!-- เพิ่มลิงก์ไปยังสินค้าของหมวดหมู่นั้น --> + <a href="/products/category/<%= category.category_id %>"><%= category.name %></a> + <a href="/categories/delete/<%= category.category_id %>" onclick="return confirm('คุณแน่ใจหรือไม่ที่จะลบหมวดหมู่นี้?');" style="color: red; margin-left: 20px;">ลบ</a> + </li> + <% }) %> + </ul> +<% } else { %> + <p>ไม่มีหมวดหมู่สินค้า</p> +<% } %> -<a href="/categories/add">เพิ่มหมวดหมู่ใหม่</a> +<a href="/categories/add" style="display:inline-block; margin-top: 20px; padding: 10px 20px; background-color: #28a745; color: white; border-radius: 5px; text-decoration: none;">เพิ่มหมวดหมู่ใหม่</a> <%- include('partials/footer') %> diff --git a/views/products.ejs b/views/products.ejs index 99dffe0ac8755c145c956587e6d88be9a4987d58..5780d4dce067985fac58e37b23f8f814f98b611d 100644 --- a/views/products.ejs +++ b/views/products.ejs @@ -2,6 +2,12 @@ <h2>สินค้าทั้งหมด</h2> +<% if (session && session.username) { %> + <p>ยินดีต้อนรับ, <%= session.username %></p> +<% } else { %> + <p>ผู้ใช้ไม่ได้เข้าสู่ระบบ</p> +<% } %> + <form action="/products/search" method="GET"> <input type="text" name="search" placeholder="ค้นหาสินค้า..." value="<%= searchQuery || '' %>"> <button type="submit">ค้นหา</button> @@ -12,6 +18,8 @@ <li> <h3><%= product.name %></h3> <p>ราคา: <%= product.price %> บาท</p> + <p>หมวดหมู่: <%= product.category_id %> <!-- สามารถปรับให้แสดงชื่อหมวดหมู่ได้ ถ้าต้องการ --></p> + <p>จำนวนในสต๊อก: <%= product.stock %> ชิ้น</p> <% if (product.image) { %> <img src="<%= product.image %>" alt="<%= product.name %>" width="100"> <% } else { %>