diff --git a/web/app.py b/web/app.py index 66e98d2..1db36ae 100644 --- a/web/app.py +++ b/web/app.py @@ -1,8 +1,8 @@ -from flask import Flask, render_template, request, redirect, url_for, flash, session +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 +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 @@ -72,6 +72,10 @@ def login(): flash('Úspěšně přihlášen.', 'success') if user['Role_ID'] == 1: return redirect(url_for('administrator')) + elif user['Role_ID'] == 2: + return redirect(url_for('managers')) + elif user['Role_ID'] == 3: + return redirect(url_for('repairs')) else: return redirect(url_for('home')) @@ -95,7 +99,18 @@ def administrator(): users = fetch_users(session.get('role_id')) orders = fetch_orders() roles = fetch_roles() - return render_template('administrator.html', users=users, orders=orders, roles=roles) + products = fetch_products() + return render_template('administrator.html', users=users, orders=orders, roles=roles, products=products) + +@app.route('/managers') +def managers(): + if not session.get('logged_in') or session.get('role_id') != 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')) + orders = fetch_orders() + roles = fetch_roles() + return render_template('managers.html', users=users, orders=orders, roles=roles) @app.route('/create_user', methods=['GET', 'POST']) def create_user(): @@ -128,7 +143,8 @@ def create_user(): finally: conn.close() - return render_template('create_user.html') + roles = fetch_roles() + return render_template('create_user.html', roles=roles) @app.route('/edit_user/', methods=['GET', 'POST']) def edit_user(user_id): @@ -191,6 +207,7 @@ def edit_order(order_id): conn = get_db_connection() order = conn.execute('SELECT * FROM Objednavky WHERE ID_Objednavky = ?', (order_id,)).fetchone() + users = fetch_users(session.get('role_id')) if request.method == 'POST': stav = request.form['stav'] @@ -208,7 +225,21 @@ def edit_order(order_id): return redirect(url_for('administrator')) conn.close() - return render_template('edit_order.html', order=order) + return render_template('edit_order.html', order=order, users=users) + +@app.route('/delete_order/', methods=['POST']) +def delete_order(order_id): + if not session.get('logged_in') or session.get('role_id') != 1: + flash('Nemáte oprávnění k přístupu na tuto stránku.', 'error') + return redirect(url_for('login')) + + conn = get_db_connection() + conn.execute('DELETE FROM Objednavky WHERE ID_Objednavky = ?', (order_id,)) + conn.commit() + conn.close() + + flash('Objednávka byla úspěšně smazána.') + return redirect(url_for('administrator')) @app.route('/repairs') def repairs(): @@ -216,7 +247,9 @@ def repairs(): flash('Nemáte oprávnění k přístupu na tuto stránku.', 'error') return redirect(url_for('login')) repairs = fetch_repairs() - return render_template('repairs.html', repairs=repairs) + orders = fetch_orders() + users = fetch_users(session.get('role_id')) + return render_template('repairs.html', repairs=repairs, orders=orders, users=users) @app.route('/create_repair', methods=['GET', 'POST']) def create_repair(): @@ -288,12 +321,9 @@ def delete_repair(repair_id): def create_reservation(): full_name = request.form['fullName'] email = request.form['email'] - date = request.form['date'] + datum_konce = request.form['datum_konce'] description = request.form['description'] - # Convert date to DD.MM.YYYY format - formatted_date = datetime.strptime(date, '%Y-%m-%d').strftime('%d.%m.%Y') - conn = get_db_connection() try: # Fetch a random user with role_id 2 @@ -303,8 +333,8 @@ def create_reservation(): else: user_id = 1 # Fallback to a default user ID if no user with role_id 2 is found - conn.execute('INSERT INTO Objednavky (Stav, ID_Zamestnance, Popis, ID_Vozidla, Datum_Zacatku, Cena) VALUES (?, ?, ?, ?, ?, ?)', - ('Nová', user_id, description, 1, formatted_date, 0.0)) # Example values for ID_Vozidla + 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 conn.commit() flash('Rezervace byla úspěšně vytvořena.', 'success') except sqlite3.Error as e: @@ -314,6 +344,105 @@ def create_reservation(): return redirect(url_for('home')) +@app.route('/edit_product/', methods=['GET', 'POST']) +def edit_product(product_id): + if not session.get('logged_in') or session.get('role_id') != 1: + flash('Nemáte oprávnění k přístupu na tuto stránku.', 'error') + return redirect(url_for('login')) + + conn = get_db_connection() + product = conn.execute('SELECT * FROM Produkty WHERE ID_Produktu = ?', (product_id,)).fetchone() + + if request.method == 'POST': + nazev = request.form['nazev'] + popis = request.form['popis'] + momentalni_zasoba = request.form['momentalni_zasoba'] + minimalni_zasoba = request.form['minimalni_zasoba'] + + update_product(product_id, nazev, popis, momentalni_zasoba, minimalni_zasoba) + + flash('Produkt byl úspěšně aktualizován.') + return redirect(url_for('administrator')) + + conn.close() + return render_template('edit_product.html', product=product) + +@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: + flash('Nemáte oprávnění k přístupu na tuto stránku.', 'error') + return redirect(url_for('login')) + + conn = get_db_connection() + product = conn.execute('SELECT * FROM Produkty WHERE ID_Produktu = ?', (product_id,)).fetchone() + + if request.method == 'POST': + quantity = request.form['quantity'] + + add_product_stock(product_id, quantity) + + flash('Zásoba byla úspěšně přidána.') + return redirect(url_for('administrator')) + + conn.close() + return render_template('add_product_stock.html', product=product) + +@app.route('/create_product', methods=['GET', 'POST']) +def create_product(): + if not session.get('logged_in') or session.get('role_id') != 1: + flash('Nemáte oprávnění k přístupu na tuto stránku.', 'error') + return redirect(url_for('login')) + + if request.method == 'POST': + nazev = request.form['nazev'] + popis = request.form['popis'] + momentalni_zasoba = request.form['momentalni_zasoba'] + minimalni_zasoba = request.form['minimalni_zasoba'] + + conn = get_db_connection() + try: + conn.execute('INSERT INTO Produkty (Nazev, Popis, Momentalni_Zasoba, Minimalni_Zasoba) VALUES (?, ?, ?, ?)', + (nazev, popis, momentalni_zasoba, minimalni_zasoba)) + conn.commit() + flash('Nový produkt byl úspěšně přidán.', 'success') + return redirect(url_for('administrator')) + except sqlite3.Error as e: + flash(f'Chyba při přidávání produktu: {e}', 'error') + finally: + conn.close() + + return render_template('create_product.html') + +@app.route('/statistics') +def statistics(): + conn = get_db_connection() + repairs_data = conn.execute(''' + SELECT Zamestnanci.Jmeno || ' ' || Zamestnanci.Prijmeni AS employee, COUNT(Opravy.ID_Opravy) AS count + FROM Opravy + JOIN Zamestnanci ON Opravy.ID_Zamestnance = Zamestnanci.ID_Uzivatele + GROUP BY Zamestnanci.ID_Uzivatele + ''').fetchall() + repairs_data = [dict(employee=row['employee'], count=row['count']) for row in repairs_data] + conn.close() + return render_template('statistics.html', repairs_data=repairs_data) + +@app.route('/repairs_by_date') +def repairs_by_date(): + start_date = request.args.get('start') + end_date = request.args.get('end') + + conn = get_db_connection() + repairs_data = conn.execute(''' + SELECT DATE(Datum_Zacatku) AS date, COUNT(*) AS count + FROM Objednavky + WHERE Datum_Zacatku BETWEEN ? AND ? + GROUP BY DATE(Datum_Zacatku) + ''', (start_date, end_date)).fetchall() + repairs_data = [dict(date=row['date'], count=row['count']) for row in repairs_data] + conn.close() + + return jsonify(repairs_data) + # Always redirect back home @app.errorhandler(404) def default_page(e): diff --git a/web/db.py b/web/db.py index 0e7d09a..5b10cc5 100644 --- a/web/db.py +++ b/web/db.py @@ -1,4 +1,3 @@ - import sqlite3 from flask import current_app as app @@ -62,4 +61,41 @@ def fetch_employees(): employees = [dict_from_row(employee) for employee in employees] conn.close() app.logger.debug(f"Fetched employees: {employees}") - return employees \ No newline at end of file + return employees + +def fetch_products(): + conn = get_db_connection() + products = conn.execute('SELECT * FROM Produkty').fetchall() + products = [dict_from_row(product) for product in products] + conn.close() + app.logger.debug(f"Fetched products: {products}") + return products + +def update_product(product_id, nazev, popis, momentalni_zasoba, minimalni_zasoba): + conn = get_db_connection() + conn.execute(''' + 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(''' + 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(''' + 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 diff --git a/web/static/css/style.css b/web/static/css/style.css index bc289a9..ca3d509 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -4,10 +4,6 @@ box-sizing: border-box; } -html { - scroll-behavior: smooth; -} - /* Body styling */ body { font-family: Arial, sans-serif; @@ -75,17 +71,7 @@ footer { right: 0; } -.navbar-users-table { - display: flex; - justify-content: space-around; - background-color: #444; - padding: 10px; - margin-top: 1.5em; - border-top-left-radius: 0.5em; - border-top-right-radius: 0.5em; -} - -.navbar-orders-table { +.table-header { display: flex; justify-content: space-around; background-color: #444; @@ -564,3 +550,72 @@ option { color: white; padding: 10px; } + +/* Graph container styling */ +.graph-container { + background-color: #222; + border: 1px solid #555; + border-radius: 1em; + padding: 20px; + margin-top: 20px; + color: white; + text-align: center; +} + +/* Canvas styling */ +canvas { + width: 100% !important; /* Make the canvas span the whole container */ + height: auto; + margin: 0 auto; + background-color: #333; /* Make the background of the graph darker */ +} + +/* Statistics page styling */ +.statistics-container { + background-color: #222; + border: 1px solid #555; + border-radius: 1em; + padding: 20px; + margin-top: 20px; + color: white; + text-align: center; +} + +.statistics-header { + margin-bottom: 20px; +} + +.statistics-form { + display: flex; + justify-content: center; + align-items: center; + margin-bottom: 20px; +} + +.statistics-form label { + margin: 0 10px; +} + +.statistics-form input { + padding: 10px; + margin: 0 10px; + border: none; + border-radius: 5px; + background-color: #555; + color: white; +} + +.statistics-form button { + background-color: #808080; + color: white; + padding: 10px 20px; + border: none; + border-radius: 5px; + cursor: pointer; + transition: transform 0.3s, box-shadow 0.3s; +} + +.statistics-form button:hover { + transform: scale(1.05); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); +} diff --git a/web/static/db/db.sqlite b/web/static/db/db.sqlite index 7e617d0..f148d36 100644 Binary files a/web/static/db/db.sqlite and b/web/static/db/db.sqlite differ diff --git a/web/static/scripts/reservation.js b/web/static/scripts/reservation.js index bd3581a..1239b2b 100644 --- a/web/static/scripts/reservation.js +++ b/web/static/scripts/reservation.js @@ -1,6 +1,6 @@ function openReservationForm() { const now = new Date(); - const formattedDate = now.toLocaleDateString('cs-CZ', { day: '2-digit', month: '2-digit', year: 'numeric' }); + const formattedDate = now.toISOString().split('T')[0]; // Format date as YYYY-MM-DD document.getElementById('date').value = formattedDate; document.getElementById('reservationForm').style.display = 'block'; } diff --git a/web/static/scripts/statistics.js b/web/static/scripts/statistics.js new file mode 100644 index 0000000..1c41afa --- /dev/null +++ b/web/static/scripts/statistics.js @@ -0,0 +1,61 @@ +document.addEventListener('DOMContentLoaded', function() { + const ctx = document.getElementById('repairsChart').getContext('2d'); + const repairsData = JSON.parse(document.getElementById('repairsData').textContent); + const labels = repairsData.map(data => data.employee); + const data = repairsData.map(data => data.count); + + new Chart(ctx, { + type: 'bar', + data: { + labels: labels, + datasets: [{ + label: 'Počet Oprav', + data: data, + backgroundColor: 'rgba(255, 99, 132, 0.2)', + borderColor: 'rgba(255, 99, 132, 1)', + borderWidth: 1 + }] + }, + options: { + scales: { + y: { + beginAtZero: true + } + } + } + }); +}); + +function fetchRepairsByDate() { + const startDate = document.getElementById('startDate').value; + const endDate = document.getElementById('endDate').value; + + fetch(`/repairs_by_date?start=${startDate}&end=${endDate}`) + .then(response => response.json()) + .then(data => { + const ctx = document.getElementById('timeRepairsChart').getContext('2d'); + const labels = data.map(item => item.date); + const counts = data.map(item => item.count); + + new Chart(ctx, { + type: 'line', + data: { + labels: labels, + datasets: [{ + label: 'Počet Oprav', + data: counts, + backgroundColor: 'rgba(54, 162, 235, 0.2)', + borderColor: 'rgba(54, 162, 235, 1)', + borderWidth: 1 + }] + }, + options: { + scales: { + y: { + beginAtZero: true + } + } + } + }); + }); +} \ No newline at end of file diff --git a/web/templates/add_product_stock.html b/web/templates/add_product_stock.html new file mode 100644 index 0000000..872a05a --- /dev/null +++ b/web/templates/add_product_stock.html @@ -0,0 +1,47 @@ + + + + + + + Přidat Zásobu + + + + + +
+
+
+ Kontakt: 123 123 123 | Otevírací doba: 9-18 hod | auto@servis.cz +
+

+ + + + Přidat Zásobu +

+ +
+
+ +
+
+

Přidat Zásobu

+
+ + + + +
+
+
+ +
+
Made by Hrachovina and Kirsch
+
+ + + \ No newline at end of file diff --git a/web/templates/administrator.html b/web/templates/administrator.html index 7a9abbc..de1b17f 100644 --- a/web/templates/administrator.html +++ b/web/templates/administrator.html @@ -4,6 +4,7 @@ Administrativní pracovníci + @@ -23,7 +24,8 @@ @@ -38,7 +40,7 @@

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

{% else %}
-
-
+
+ + {% if products %} + + + + + + + + + + + + + {% for product in products %} + + + + + + + + + {% endfor %} + +
IDNázevPopisMomentální ZásobaMinimální ZásobaAkce
{{ product['ID_Produktu'] }}{{ product['Nazev'] }}{{ product['Popis'] }}{{ product['Momentalni_Zasoba'] }}{{ product['Minimalni_Zasoba'] }} + Edit / + Add +
+ {% else %} + + + + +
Nenalezeny žádné produkty k zobrazení.
+ {% endif %} +
{% endif %} -