diff --git a/web/app.py b/web/app.py index 8ddcc89..453c59a 100644 --- a/web/app.py +++ b/web/app.py @@ -1,10 +1,9 @@ from flask import Flask, render_template, request, redirect, url_for, flash, session, jsonify import logging from datetime import datetime -from werkzeug.security import check_password_hash, generate_password_hash from db import get_db_connection, fetch_users, fetch_orders, fetch_roles, fetch_repairs, fetch_employees, fetch_products, update_product, add_product_stock from auth import encrypt_password, check_password -import random +import sqlite3 app = Flask(__name__) app.secret_key = 'e462a50b0401f495cfb5c67c3871fe4b' @@ -20,14 +19,20 @@ werkzeug_logger.setLevel(logging.ERROR) # Log client IP before each request @app.before_request def log_client_ip(): - client_ip = request.headers.get('X-Forwarded-For', request.remote_addr) + if request.remote_addr == '127.0.0.1': + client_ip = request.headers.get('CF-Connecting-IP', request.headers.get('X-Forwarded-For', request.remote_addr)) + else: + client_ip = request.remote_addr client_ip = client_ip.split(',')[0] # Get the first IP if it's a forwarded request request.client_ip = client_ip # Store the client IP in the request context # Override werkzeug's default logging to show client IP in the access log @app.after_request def log_request(response): - client_ip = request.headers.get('X-Forwarded-For', request.remote_addr) + if request.remote_addr == '127.0.0.1': + client_ip = request.headers.get('CF-Connecting-IP', request.headers.get('X-Forwarded-For', request.remote_addr)) + else: + client_ip = request.remote_addr client_ip = client_ip.split(',')[0] # Get the first IP if it's a forwarded request app.logger.info(f"{client_ip} - - [{request.date}] \"{request.method} {request.full_path} {request.environ.get('SERVER_PROTOCOL')}\" {response.status_code}") return response @@ -39,6 +44,7 @@ def logout(): session.pop('logged_in', None) session.pop('role_id', None) session.pop('username', None) + session.pop('user_id', None) return ''' + ''' else: session['logged_in'] = True session['role_id'] = user['Role_ID'] - session['username'] = user['Username'] # Store username in session + session['username'] = user['Username'] + session['user_id'] = user['ID_Uzivatele'] flash('Úspěšně přihlášen.', 'success') if user['Role_ID'] == 1: return redirect(url_for('administrator')) @@ -104,7 +120,7 @@ def administrator(): @app.route('/managers') def managers(): - if not session.get('logged_in') or session.get('role_id') != 2: + if not session.get('logged_in') or session.get('role_id') not in [1, 2]: flash('Nemáte oprávnění k přístupu na tuto stránku.', 'error') return redirect(url_for('login')) users = fetch_users(session.get('role_id')) @@ -114,7 +130,7 @@ def managers(): @app.route('/create_user', methods=['GET', 'POST']) def create_user(): - if not session.get('logged_in') or session.get('role_id') != 1: + if not session.get('logged_in') or session.get('role_id') not in [1, 2]: flash('Nemáte oprávnění k přístupu na tuto stránku.', 'error') return redirect(url_for('login')) @@ -148,7 +164,7 @@ def create_user(): @app.route('/edit_user/', methods=['GET', 'POST']) def edit_user(user_id): - if not session.get('logged_in') or session.get('role_id') != 1: + if not session.get('logged_in') or session.get('role_id') not in [1, 2]: flash('Nemáte oprávnění k přístupu na tuto stránku.', 'error') return redirect(url_for('login')) @@ -182,7 +198,7 @@ def edit_user(user_id): @app.route('/delete_user/', methods=['POST']) def delete_user(user_id): - if not session.get('logged_in') or session.get('role_id') != 1: + if not session.get('logged_in') or session.get('role_id') not in [1, 2]: flash('Nemáte oprávnění k přístupu na tuto stránku.', 'error') return redirect(url_for('login')) @@ -201,7 +217,7 @@ def delete_user(user_id): @app.route('/edit_order/', methods=['GET', 'POST']) def edit_order(order_id): - if not session.get('logged_in') or session.get('role_id') != 1: + if not session.get('logged_in') or session.get('role_id') not in [1, 2, 3]: flash('Nemáte oprávnění k přístupu na tuto stránku.', 'error') return redirect(url_for('login')) @@ -229,7 +245,7 @@ def edit_order(order_id): @app.route('/delete_order/', methods=['POST']) def delete_order(order_id): - if not session.get('logged_in') or session.get('role_id') != 1: + if not session.get('logged_in') or session.get('role_id') not in [1, 2, 3]: flash('Nemáte oprávnění k přístupu na tuto stránku.', 'error') return redirect(url_for('login')) @@ -243,7 +259,7 @@ def delete_order(order_id): @app.route('/repairs') def repairs(): - if not session.get('logged_in'): + if not session.get('logged_in') or session.get('role_id') not in [1, 3]: flash('Nemáte oprávnění k přístupu na tuto stránku.', 'error') return redirect(url_for('login')) repairs = fetch_repairs() @@ -253,7 +269,7 @@ def repairs(): @app.route('/create_repair', methods=['GET', 'POST']) def create_repair(): - if not session.get('logged_in') or session.get('role_id') != 1: + if not session.get('logged_in') and session.get('role_id') != 1 or session.get('role_id') not in [1, 3]: flash('Nemáte oprávnění k přístupu na tuto stránku.', 'error') return redirect(url_for('login')) @@ -261,51 +277,102 @@ def create_repair(): id_zamestnance = request.form['id_zamestnance'] nazev = request.form['nazev'] popis = request.form['popis'] + products = [{'id': pid, 'quantity': qty} for pid, qty in zip(request.form.getlist('products[0][id]'), request.form.getlist('products[0][quantity]'))] + + app.logger.debug(f"Received id_zamestnance: {id_zamestnance}") + app.logger.debug(f"Received nazev: {nazev}") + app.logger.debug(f"Received popis: {popis}") + app.logger.debug(f"Received products: {products}") conn = get_db_connection() try: - conn.execute('INSERT INTO Opravy (ID_Zamestnance, Nazev, Popis) VALUES (?, ?, ?)', - (id_zamestnance, nazev, popis)) + conn.execute('BEGIN') + sql_insert_repair = 'INSERT INTO Opravy (ID_Zamestnance, Nazev, Popis) VALUES (?, ?, ?)' + app.logger.debug(f"Executing SQL: {sql_insert_repair} with values ({id_zamestnance}, {nazev}, {popis})") + conn.execute(sql_insert_repair, (id_zamestnance, nazev, popis)) + repair_id = conn.execute('SELECT last_insert_rowid()').fetchone()[0] + + debug_info = f"Inserted Repair ID: {repair_id}\n" + + for product in products: + product_id = product['id'] + quantity = product['quantity'] + app.logger.debug(f"Inserting product {product_id} with quantity {quantity} for repair {repair_id}") + if quantity and int(quantity) > 0: + conn.execute('INSERT INTO Pouzite_Produkty (ID_Opravy, ID_Produktu, Pocet_Produktu) VALUES (?, ?, ?)', + (repair_id, product_id, quantity)) + debug_info += f"Inserted Product ID: {product_id}, Quantity: {quantity}\n" + conn.commit() + session['debug'] = debug_info flash('Nová oprava byla úspěšně vytvořena.', 'success') return redirect(url_for('repairs')) except sqlite3.Error as e: + conn.rollback() + app.logger.error(f"Error creating repair: {e}") flash(f'Chyba při vytváření opravy: {e}', 'error') finally: conn.close() employees = fetch_employees() - return render_template('create_repair.html', employees=employees) + products = fetch_products() + return render_template('create_repair.html', employees=employees, products=products) @app.route('/edit_repair/', methods=['GET', 'POST']) def edit_repair(repair_id): - if not session.get('logged_in') or session.get('role_id') != 1: + if not session.get('logged_in') or session.get('role_id') not in [1, 3]: flash('Nemáte oprávnění k přístupu na tuto stránku.', 'error') return redirect(url_for('login')) conn = get_db_connection() repair = conn.execute('SELECT * FROM Opravy WHERE ID_Opravy = ?', (repair_id,)).fetchone() + repair = dict(repair) if request.method == 'POST': id_zamestnance = request.form['id_zamestnance'] nazev = request.form['nazev'] popis = request.form['popis'] + products = [{'id': pid, 'quantity': qty} for pid, qty in zip(request.form.getlist('products[][id]'), request.form.getlist('products[][quantity]'))] - conn.execute('UPDATE Opravy SET ID_Zamestnance = ?, Nazev = ?, Popis = ? WHERE ID_Opravy = ?', - (id_zamestnance, nazev, popis, repair_id)) - conn.commit() - conn.close() + conn.execute('BEGIN') + try: + conn.execute('UPDATE Opravy SET ID_Zamestnance = ?, Nazev = ?, Popis = ? WHERE ID_Opravy = ?', + (id_zamestnance, nazev, popis, repair_id)) + + conn.execute('DELETE FROM Pouzite_Produkty WHERE ID_Opravy = ?', (repair_id,)) + + for product in products: + product_id = product['id'] + quantity = product['quantity'] + if quantity and int(quantity) > 0: + conn.execute('INSERT INTO Pouzite_Produkty (ID_Opravy, ID_Produktu, Pocet_Produktu) VALUES (?, ?, ?)', + (repair_id, product_id, quantity)) + + conn.commit() + flash('Oprava byla úspěšně aktualizována.') + except sqlite3.Error as e: + conn.rollback() + app.logger.error(f"Error updating repair: {e}") + flash(f'Chyba při aktualizaci opravy: {e}', 'error') + finally: + conn.close() - flash('Oprava byla úspěšně aktualizována.') return redirect(url_for('repairs')) employees = fetch_employees() + products = fetch_products() + repair['products'] = [dict(row) for row in conn.execute(''' + SELECT Produkty.ID_Produktu, Produkty.Nazev, Pouzite_Produkty.Pocet_Produktu + FROM Pouzite_Produkty + JOIN Produkty ON Pouzite_Produkty.ID_Produktu = Produkty.ID_Produktu + WHERE Pouzite_Produkty.ID_Opravy = ? + ''', (repair_id,)).fetchall()] conn.close() - return render_template('edit_repair.html', repair=repair, employees=employees) + return render_template('edit_repair.html', repair=repair, employees=employees, products=products) @app.route('/delete_repair/', methods=['POST']) def delete_repair(repair_id): - if not session.get('logged_in') or session.get('role_id') != 1: + if not session.get('logged_in') or session.get('role_id') not in [1, 2, 3]: flash('Nemáte oprávnění k přístupu na tuto stránku.', 'error') return redirect(url_for('login')) @@ -326,12 +393,12 @@ def create_reservation(): conn = get_db_connection() try: - # Fetch a random user with role_id 2 - user = conn.execute('SELECT ID_Uzivatele FROM Zamestnanci WHERE Role_ID = 2 ORDER BY RANDOM() LIMIT 1').fetchone() + # Fetch a random user with role_id 3 + user = conn.execute('SELECT ID_Uzivatele FROM Zamestnanci WHERE Role_ID = 3 ORDER BY RANDOM() LIMIT 1').fetchone() if user: user_id = user['ID_Uzivatele'] else: - user_id = 1 # Fallback to a default user ID if no user with role_id 2 is found + user_id = 1 # Fallback to a default user ID if no user with role_id 3 is found conn.execute('INSERT INTO Objednavky (Stav, ID_Zamestnance, Popis, ID_Vozidla, Datum_Zacatku, Datum_Konce, Cena) VALUES (?, ?, ?, ?, ?, ?, ?)', ('Nová', user_id, description, 1, datetime.now().strftime('%Y-%m-%d'), datum_konce, 0.0)) # Use the current date for Datum_Zacatku @@ -342,11 +409,16 @@ def create_reservation(): finally: conn.close() - return redirect(url_for('home')) + return ''' + + ''' @app.route('/edit_product/', methods=['GET', 'POST']) def edit_product(product_id): - if not session.get('logged_in') or session.get('role_id') != 1: + if not session.get('logged_in') or session.get('role_id') not in [1, 2]: flash('Nemáte oprávnění k přístupu na tuto stránku.', 'error') return redirect(url_for('login')) @@ -369,7 +441,7 @@ def edit_product(product_id): @app.route('/add_product_stock/', methods=['GET', 'POST']) def add_product_stock(product_id): - if not session.get('logged_in') or session.get('role_id') != 1: + if not session.get('logged_in') or session.get('role_id') not in [1, 2]: flash('Nemáte oprávnění k přístupu na tuto stránku.', 'error') return redirect(url_for('login')) @@ -389,7 +461,7 @@ def add_product_stock(product_id): @app.route('/create_product', methods=['GET', 'POST']) def create_product(): - if not session.get('logged_in') or session.get('role_id') != 1: + if not session.get('logged_in') or session.get('role_id') not in [1, 2]: flash('Nemáte oprávnění k přístupu na tuto stránku.', 'error') return redirect(url_for('login')) @@ -411,10 +483,16 @@ def create_product(): finally: conn.close() + + return render_template('create_product.html') @app.route('/statistics') def statistics(): + if not session.get('logged_in') or session.get('role_id') not in [1, 2]: + flash('Nemáte oprávnění k přístupu na tuto stránku.', 'error') + return redirect(url_for('login')) + conn = get_db_connection() repairs_data = conn.execute(''' SELECT Zamestnanci.Jmeno || ' ' || Zamestnanci.Prijmeni AS employee, COUNT(Opravy.ID_Opravy) AS count diff --git a/web/db.py b/web/db.py index 5b10cc5..65f94f1 100644 --- a/web/db.py +++ b/web/db.py @@ -1,5 +1,6 @@ import sqlite3 from flask import current_app as app +import time def get_db_connection(): conn = sqlite3.connect('./static/db/db.sqlite') @@ -9,6 +10,21 @@ def get_db_connection(): def dict_from_row(row): return {key: row[key] for key in row.keys()} +def execute_with_retry(conn, query, params=(), retries=5, delay=0.1): + for attempt in range(retries): + try: + conn.execute(query, params) + conn.commit() + return + except sqlite3.OperationalError as e: + if "database is locked" in str(e): + if attempt < retries - 1: + time.sleep(delay) + else: + raise + else: + raise + def fetch_users(role_id): conn = get_db_connection() users = conn.execute('SELECT * FROM Zamestnanci WHERE Role_ID >= ?', (role_id,)).fetchall() @@ -73,29 +89,39 @@ def fetch_products(): def update_product(product_id, nazev, popis, momentalni_zasoba, minimalni_zasoba): conn = get_db_connection() - conn.execute(''' + execute_with_retry(conn, ''' UPDATE Produkty SET Nazev = ?, Popis = ?, Momentalni_Zasoba = ?, Minimalni_Zasoba = ? WHERE ID_Produktu = ? ''', (nazev, popis, momentalni_zasoba, minimalni_zasoba, product_id)) - conn.commit() conn.close() def add_product_stock(product_id, quantity): conn = get_db_connection() - conn.execute(''' + execute_with_retry(conn, ''' UPDATE Produkty SET Momentalni_Zasoba = Momentalni_Zasoba + ? WHERE ID_Produktu = ? ''', (quantity, product_id)) - conn.commit() conn.close() def create_product(nazev, popis, momentalni_zasoba, minimalni_zasoba): conn = get_db_connection() - conn.execute(''' + execute_with_retry(conn, ''' INSERT INTO Produkty (Nazev, Popis, Momentalni_Zasoba, Minimalni_Zasoba) VALUES (?, ?, ?, ?) ''', (nazev, popis, momentalni_zasoba, minimalni_zasoba)) - conn.commit() - conn.close() \ No newline at end of file + conn.close() + +def insert_used_products(repair_id, products): + conn = get_db_connection() + try: + for product in products: + product_id = product['id'] + quantity = product['quantity'] + app.logger.debug(f"Inserting product {product_id} with quantity {quantity} for repair {repair_id}") + if quantity and int(quantity) > 0: + execute_with_retry(conn, 'INSERT INTO Pouzite_Produkty (ID_Opravy, ID_Produktu, Pocet_Produktu) VALUES (?, ?, ?)', + (repair_id, product_id, quantity)) + finally: + conn.close() \ No newline at end of file diff --git a/web/static/css/style.css b/web/static/css/style.css index ca3d509..dae10c1 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -241,20 +241,43 @@ footer { /* Reviews section */ .reviews { + margin: 20px 0; + padding: 15px; + background-color: #222; + border: 1px solid #555; + color: white; + border-radius: 1em; + text-align: center; +} + +.reviews-header { + margin-bottom: 20px; +} + +.review-list { display: flex; justify-content: space-around; - margin: 20px; + flex-wrap: wrap; } .review-box { + flex: 1 1 200px; + background-color: #333; width: 200px; min-height: 100px; - background-color: #444; - border: 1px solid #555; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + transition: transform 0.3s, box-shadow 0.3s; + margin: 10px; padding: 10px; text-align: center; - transition: transform 0.3s, box-shadow 0.3s; - color: white; +} + +.review-box:hover { + transform: scale(1.05); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); } /* Text alignment for Directions */ @@ -413,19 +436,29 @@ table tr:hover { /* Media queries for mobile responsiveness */ @media (max-width: 768px) { - .navbar, .container, .about, .map, .service-info, .photos, .directions, .services, .service-item, .review-box { + header, footer, .navbar, .container, .about, .map, .service-info, .photos, .directions, .services, .service-item, .review-box { + width: 100%; border-radius: 0.5em; } + .header, .buttons { + flex-direction: column; + align-items: center; + } + + .header .contact-info, .header .nazev, .header .buttons { + text-align: center; + margin: 10px 0; + } + .navbar { flex-direction: column; align-items: center; } - .navbar a { - padding: 10px; + .navbar button { width: 100%; - text-align: center; + margin: 5px 0; } .content { @@ -474,6 +507,19 @@ table tr:hover { .form-container { width: 90%; } + + .user-info { + width: 100%; + text-align: center; + } + + table { + font-size: 14px; + } + + table th, table td { + padding: 8px 10px; + } } /* Button link styles */ @@ -619,3 +665,52 @@ canvas { transform: scale(1.05); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); } + +.partners { + margin: 20px 0; + padding: 15px; + background-color: #222; + border: 1px solid #555; + color: white; + border-radius: 1em; +} + +.partner-list { + display: flex; + justify-content: space-around; + flex-wrap: wrap; +} + +.partner-item { + flex: 1 1 200px; + width: 200px; + height: 200px; + display: flex; + justify-content: center; + align-items: center; + transition: transform 0.3s, box-shadow 0.3s; + margin: 10px; +} + +.partner-item img { + width: 10em; + height: auto; + border-radius: 10em; +} + +.partner-item:hover { + transform: scale(1.5); +} + +.partner-logos { + display: flex; + justify-content: space-around; + align-items: center; + margin: 20px 0; +} + +.partner-logos img { + width: var(--partner-logo-width, 100px); + height: var(--partner-logo-height, auto); + margin: 0 10px; +} diff --git a/web/static/db/db.sqlite b/web/static/db/db.sqlite index f148d36..ad7fc22 100644 Binary files a/web/static/db/db.sqlite and b/web/static/db/db.sqlite differ diff --git a/web/static/img/partner1.png b/web/static/img/partner1.png new file mode 100644 index 0000000..e9019e7 Binary files /dev/null and b/web/static/img/partner1.png differ diff --git a/web/static/img/partner2.png b/web/static/img/partner2.png new file mode 100644 index 0000000..fd0911b Binary files /dev/null and b/web/static/img/partner2.png differ diff --git a/web/static/img/partner3.png b/web/static/img/partner3.png new file mode 100644 index 0000000..815c78a Binary files /dev/null and b/web/static/img/partner3.png differ diff --git a/web/static/img/partner4.png b/web/static/img/partner4.png new file mode 100644 index 0000000..a6e79fa Binary files /dev/null and b/web/static/img/partner4.png differ diff --git a/web/static/scripts/products.js b/web/static/scripts/products.js new file mode 100644 index 0000000..f9b47e0 --- /dev/null +++ b/web/static/scripts/products.js @@ -0,0 +1,29 @@ +let productCount = document.querySelectorAll('.product-item').length; + +function addProduct() { + const container = document.getElementById('product-container'); + const newProduct = document.createElement('div'); + newProduct.classList.add('product-item'); + newProduct.id = `product-item-${productCount}`; + newProduct.innerHTML = ` + + + + + + `; + container.appendChild(newProduct); + productCount++; +} + +function removeProduct(index) { + const productItem = document.getElementById(`product-item-${index}`); + if (productItem) { + productItem.remove(); + } +} \ No newline at end of file diff --git a/web/templates/administrator.html b/web/templates/administrator.html index de1b17f..e559f52 100644 --- a/web/templates/administrator.html +++ b/web/templates/administrator.html @@ -30,15 +30,8 @@ - -
- {% if not session.get('logged_in') %} -

Musíte se přihlásit, abyste mohli zobrazit tuto stránku.

- {% elif session.get('role_id') != 1 %} -

Nemáte oprávnění zobrazit tuto stránku.

- {% else %}
- {% endif %} -
+
Made by Hrachovina
diff --git a/web/templates/create_repair.html b/web/templates/create_repair.html index cac402d..f62b2f2 100644 --- a/web/templates/create_repair.html +++ b/web/templates/create_repair.html @@ -30,6 +30,23 @@ +

Použité Produkty

+
+
+ + + + + +
+
+ + @@ -38,5 +55,7 @@
Made by Hrachovina
+ + \ No newline at end of file diff --git a/web/templates/create_user.html b/web/templates/create_user.html index 9d205c1..9659bac 100644 --- a/web/templates/create_user.html +++ b/web/templates/create_user.html @@ -29,9 +29,6 @@
- {% if not session.get('logged_in') or session.get('role_id') != 1 %} -

Nemáte oprávnění k přístupu na tuto stránku.

- {% else %}
@@ -57,7 +54,6 @@
- {% endif %}
diff --git a/web/templates/edit_repair.html b/web/templates/edit_repair.html index d655c04..6f15a58 100644 --- a/web/templates/edit_repair.html +++ b/web/templates/edit_repair.html @@ -42,14 +42,35 @@ +

Použité Produkty

+
+ {% for product in repair['products'] %} +
+ + + + + +
+ {% endfor %} +
+ +
-
Made by Hrachovina
+
Made by Hrachovina and Kirsch
+ + \ No newline at end of file diff --git a/web/templates/home.html b/web/templates/home.html index c2faeec..2fe781b 100644 --- a/web/templates/home.html +++ b/web/templates/home.html @@ -51,6 +51,7 @@ +
@@ -105,27 +106,49 @@
-
-

Skvělý servis! Rychlá a profesionální oprava.

-

⭐⭐⭐⭐

-

- Jan Novák

-
-
-

Velmi přátelský personál a kvalitní služby.

-

⭐⭐⭐⭐⭐

-

- Petra Svobodová

-
-
-

Oprava byla hotová dříve, než jsem očekával.

-

⭐⭐⭐

-

- Martin Dvořák

-
-
-

Výborná komunikace a skvělé ceny. Určitě se vrátím.

-

⭐⭐⭐⭐⭐

-

- Eva Černá

+

Recenze

+
+
+

Skvělý servis! Rychlá a profesionální oprava.

+

⭐⭐⭐⭐

+

- Jan Novák

+
+
+

Velmi přátelský personál a kvalitní služby.

+

⭐⭐⭐⭐⭐

+

- Petra Svobodová

+
+
+

Oprava byla hotová dříve, než jsem očekával.

+

⭐⭐⭐

+

- Martin Dvořák

+
+
+

Výborná komunikace a skvělé ceny. Určitě se vrátím.

+

⭐⭐⭐⭐⭐

+

- Eva Černá

+
+ +
+

Naši partneři

+
+
+ Partner 1 +
+
+ Partner 2 +
+
+ Partner 3 +
+
+ Partner 4 +
+
+
+