diff --git a/server.py b/server.py new file mode 100644 index 0000000..1e01fb1 --- /dev/null +++ b/server.py @@ -0,0 +1,123 @@ +from flask import Flask, request, jsonify, render_template, send_from_directory +from flask_sqlalchemy import SQLAlchemy +from datetime import datetime +import os +from werkzeug.utils import secure_filename + +app = Flask(__name__) + +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///products.db' +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False +app.config['UPLOAD_FOLDER'] = 'static/uploads' +os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) + +db = SQLAlchemy(app) + +class Product(db.Model): + id = db.Column(db.Integer, primary_key=True) + product_name = db.Column(db.String(100), nullable=False) + product_type = db.Column(db.String(50), nullable=False) + date_bought = db.Column(db.Date, nullable=False) + price_bought = db.Column(db.Float, nullable=False) + date_sold = db.Column(db.Date, nullable=True) + price_sold = db.Column(db.Float, nullable=True) + condition = db.Column(db.String(20), nullable=False) + image = db.Column(db.String(255), nullable=True) + is_sold = db.Column(db.Boolean, default=False) + +with app.app_context(): + db.create_all() + +@app.route('/') +def index(): + return render_template('index.html') + +@app.route('/products', methods=['POST']) +def add_product(): + product_name = request.form['product_name'] + product_type = request.form['product_type'] + date_bought = datetime.strptime(request.form['date_bought'], '%d/%m/%Y').date() + price_bought = float(request.form['price_bought']) + condition = request.form['condition'] + + image_file = request.files['image'] + if image_file: + filename = secure_filename(image_file.filename) + image_path = os.path.join(app.config['UPLOAD_FOLDER'], filename) + image_file.save(image_path) + image_url = f"/{image_path}" + else: + image_url = None + + new_product = Product( + product_name=product_name, + product_type=product_type, + date_bought=date_bought, + price_bought=price_bought, + condition=condition, + image=image_url, + is_sold=False + ) + + db.session.add(new_product) + db.session.commit() + + return jsonify({'message': 'Product added successfully!'}), 201 + +@app.route('/products/', methods=['PUT']) +def update_product(product_id): + product = Product.query.get_or_404(product_id) + + product.product_name = request.form['product_name'] + product.product_type = request.form['product_type'] + product.date_bought = datetime.strptime(request.form['date_bought'], '%d/%m/%Y').date() + product.price_bought = float(request.form['price_bought']) + product.condition = request.form['condition'] + + if 'image' in request.files: + image_file = request.files['image'] + if image_file: + filename = secure_filename(image_file.filename) + image_path = os.path.join(app.config['UPLOAD_FOLDER'], filename) + image_file.save(image_path) + product.image = f"/{image_path}" + + product.is_sold = 'is_sold' in request.form and request.form['is_sold'] == 'on' + if product.is_sold: + product.date_sold = datetime.strptime(request.form['date_sold'], '%d/%m/%Y').date() + product.price_sold = float(request.form['price_sold']) + else: + product.date_sold = None + product.price_sold = None + + db.session.commit() + + return jsonify({'message': 'Product updated successfully!'}), 200 + +@app.route('/products', methods=['GET']) +def get_products(): + products = Product.query.all() + result = [] + + for product in products: + result.append({ + 'id': product.id, + 'product_name': product.product_name, + 'product_type': product.product_type, + 'date_bought': product.date_bought.strftime('%d/%m/%Y'), + 'price_bought': product.price_bought, + 'date_sold': product.date_sold.strftime('%d/%m/%Y') if product.date_sold else None, + 'price_sold': product.price_sold, + 'condition': product.condition, + 'image': product.image, + 'is_sold': product.is_sold + }) + + return jsonify(result), 200 + +@app.route('/static/') +def send_static(path): + return send_from_directory('static', path) + +if __name__ == '__main__': + app.run(debug=True) \ No newline at end of file diff --git a/static/css/styles.css b/static/css/styles.css new file mode 100644 index 0000000..83e5118 --- /dev/null +++ b/static/css/styles.css @@ -0,0 +1,117 @@ +body { + font-family: Arial, sans-serif; + background-color: #f0f2f5; + margin: 0; + padding: 0; +} + +.container { + width: 80%; + margin: 0 auto; + padding-top: 30px; +} + +header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} + +h1 { + color: #333; + margin: 0; +} + +.add-button { + background-color: #28a745; + color: white; + border: none; + padding: 10px 15px; + border-radius: 50%; + font-size: 24px; + cursor: pointer; +} + +.add-button:hover { + background-color: #218838; +} + +.product-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 20px; +} + +.product-item { + background-color: white; + padding: 15px; + border-radius: 8px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); +} + +.product-item img { + width: 100%; + height: 150px; + object-fit: cover; + border-radius: 5px; + margin-bottom: 10px; +} + +.pagination { + display: flex; + justify-content: center; + margin-top: 20px; +} + +.pagination button { + margin: 0 5px; + padding: 5px 10px; + background-color: #007bff; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; +} + +.pagination button:hover { + background-color: #0056b3; +} + +.modal { + display: none; + position: fixed; + z-index: 1; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); +} + +.modal-content { + background-color: #fff; + margin: 15% auto; + padding: 20px; + border-radius: 10px; + width: 40%; +} + +.close-btn { + float: right; + font-size: 28px; + cursor: pointer; +} + +.submit-button { + background-color: #007bff; + color: white; + border: none; + padding: 10px; + border-radius: 5px; + cursor: pointer; +} + +.submit-button:hover { + background-color: #0056b3; +} \ No newline at end of file diff --git a/static/js/script.js b/static/js/script.js new file mode 100644 index 0000000..c327fa0 --- /dev/null +++ b/static/js/script.js @@ -0,0 +1,132 @@ +document.addEventListener('DOMContentLoaded', () => { + const productList = document.getElementById('product-list'); + const pagination = document.getElementById('pagination'); + const modal = document.getElementById('modal'); + const closeBtn = document.querySelector('.close-btn'); + const addProductBtn = document.getElementById('add-product-btn'); + const productForm = document.getElementById('product-form'); + const modalTitle = document.getElementById('modal-title'); + const isSoldCheckbox = document.getElementById('is_sold'); + const soldFields = document.getElementById('sold-fields'); + let currentPage = 1; + const itemsPerPage = 10; + let editMode = false; + let editProductId = null; + + function fetchProducts() { + fetch('/products') + .then(response => response.json()) + .then(data => renderProducts(data)) + .catch(error => console.error('Error fetching products:', error)); + } + + function renderProducts(products) { + productList.innerHTML = ''; + pagination.innerHTML = ''; + + const totalPages = Math.ceil(products.length / itemsPerPage); + const start = (currentPage - 1) * itemsPerPage; + const end = start + itemsPerPage; + const currentItems = products.slice(start, end); + + currentItems.forEach(product => { + const productItem = document.createElement('div'); + productItem.classList.add('product-item'); + productItem.innerHTML = ` + ${product.product_name} +

${product.product_name}

+

Type: ${product.product_type}

+

Date Bought: ${product.date_bought}

+

Price Bought: €${product.price_bought}

+

Condition: ${product.condition}

+

Sold: ${product.is_sold ? 'Yes' : 'No'}

+ ${product.is_sold ? `

Date Sold: ${product.date_sold}

Price Sold: $${product.price_sold}

` : ''} + + `; + productList.appendChild(productItem); + }); + + for (let i = 1; i <= totalPages; i++) { + const button = document.createElement('button'); + button.textContent = i; + if (i === currentPage) button.disabled = true; + button.addEventListener('click', () => { + currentPage = i; + renderProducts(products); + }); + pagination.appendChild(button); + } + + document.querySelectorAll('.edit-button').forEach(button => { + button.addEventListener('click', () => { + editMode = true; + editProductId = button.getAttribute('data-id'); + openModalForEdit(editProductId); + }); + }); + } + + addProductBtn.addEventListener('click', () => { + editMode = false; + editProductId = null; + modalTitle.textContent = 'Add New Product'; + productForm.reset(); + soldFields.style.display = 'none'; + modal.style.display = 'block'; + }); + + closeBtn.addEventListener('click', () => { + modal.style.display = 'none'; + }); + + isSoldCheckbox.addEventListener('change', () => { + soldFields.style.display = isSoldCheckbox.checked ? 'block' : 'none'; + }); + + productForm.addEventListener('submit', (event) => { + event.preventDefault(); + const formData = new FormData(productForm); + const url = editMode ? `/products/${editProductId}` : '/products'; + const method = editMode ? 'PUT' : 'POST'; + + fetch(url, { + method: method, + body: formData + }) + .then(response => response.json()) + .then(data => { + fetchProducts(); + modal.style.display = 'none'; + productForm.reset(); + }) + .catch(error => console.error('Error adding/updating product:', error)); + }); + + function openModalForEdit(productId) { + fetch(`/products`) + .then(response => response.json()) + .then(products => { + const product = products.find(p => p.id == productId); + if (product) { + modalTitle.textContent = 'Edit Product'; + document.getElementById('product_name').value = product.product_name; + document.getElementById('product_type').value = product.product_type; + document.getElementById('date_bought').value = product.date_bought; + document.getElementById('price_bought').value = product.price_bought; + document.getElementById('condition').value = product.condition; + isSoldCheckbox.checked = product.is_sold; + if (product.is_sold) { + document.getElementById('date_sold').value = product.date_sold ? product.date_sold : ''; + document.getElementById('price_sold').value = product.price_sold ? product.price_sold : ''; + soldFields.style.display = 'block'; + } else { + soldFields.style.display = 'none'; + } + modal.style.display = 'block'; + } + }) + .catch(error => console.error('Error fetching product details:', error)); + } + + fetchProducts(); +}); \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..c0fcb48 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,62 @@ + + + + + + Product Inventory + + + +
+
+

Product Inventory

+ +
+ +
+ + + + +
+ + + + \ No newline at end of file