mirror of
https://github.com/ApfelTeeSaft/product-listing.git
synced 2026-08-26 19:33:36 +00:00
Base
This commit is contained in:
@@ -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/<int:product_id>', 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/<path:path>')
|
||||
def send_static(path):
|
||||
return send_from_directory('static', path)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 = `
|
||||
<img src="${product.image}" alt="${product.product_name}">
|
||||
<h3>${product.product_name}</h3>
|
||||
<p>Type: ${product.product_type}</p>
|
||||
<p>Date Bought: ${product.date_bought}</p>
|
||||
<p>Price Bought: €${product.price_bought}</p>
|
||||
<p>Condition: ${product.condition}</p>
|
||||
<p>Sold: ${product.is_sold ? 'Yes' : 'No'}</p>
|
||||
${product.is_sold ? `<p>Date Sold: ${product.date_sold}</p><p>Price Sold: $${product.price_sold}</p>` : ''}
|
||||
<button class="edit-button" data-id="${product.id}">Edit</button>
|
||||
`;
|
||||
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();
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Product Inventory</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>Product Inventory</h1>
|
||||
<button id="add-product-btn" class="add-button">+</button>
|
||||
</header>
|
||||
|
||||
<div id="product-list" class="product-list"></div>
|
||||
|
||||
<div id="pagination" class="pagination"></div>
|
||||
|
||||
<div id="modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close-btn">×</span>
|
||||
<h2 id="modal-title">Add New Product</h2>
|
||||
<form id="product-form" enctype="multipart/form-data">
|
||||
<label for="product_name">Product Name:</label>
|
||||
<input type="text" id="product_name" name="product_name" required>
|
||||
|
||||
<label for="product_type">Type:</label>
|
||||
<input type="text" id="product_type" name="product_type" required>
|
||||
|
||||
<label for="date_bought">Date Bought:</label>
|
||||
<input type="date" id="date_bought" name="date_bought" required>
|
||||
|
||||
<label for="price_bought">Price Bought:</label>
|
||||
<input type="number" id="price_bought" name="price_bought" step="0.01" required>
|
||||
|
||||
<label for="condition">Condition:</label>
|
||||
<input type="text" id="condition" name="condition" required>
|
||||
|
||||
<label for="image">Image:</label>
|
||||
<input type="file" id="image" name="image" accept="image/*">
|
||||
|
||||
<label for="is_sold">Mark as Sold:</label>
|
||||
<input type="checkbox" id="is_sold" name="is_sold">
|
||||
|
||||
<div id="sold-fields" style="display: none;">
|
||||
<label for="date_sold">Date Sold:</label>
|
||||
<input type="date" id="date_sold" name="date_sold">
|
||||
|
||||
<label for="price_sold">Price Sold:</label>
|
||||
<input type="number" id="price_sold" name="price_sold" step="0.01">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="submit-button">Save Product</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user