From 2ab098063e85fa82dbc3fdc98cda298426f8029f Mon Sep 17 00:00:00 2001 From: Scheianu Ionut Date: Sun, 14 Sep 2025 19:07:36 +0300 Subject: [PATCH] updated reports --- py_app/app/__pycache__/routes.cpython-312.pyc | Bin 26925 -> 44446 bytes py_app/app/routes.py | 437 ++++++++++++-- py_app/app/static/script.js | 557 +++++++++++++++++- py_app/app/static/style.css | 233 +++++++- py_app/app/templates/quality.html | 37 ++ 5 files changed, 1181 insertions(+), 83 deletions(-) diff --git a/py_app/app/__pycache__/routes.cpython-312.pyc b/py_app/app/__pycache__/routes.cpython-312.pyc index 491eb8578e0e1f166c948cfcdae4c0e0a98c1387..fea81f1bbc0f060e077c334edf0da02aad42b3f9 100644 GIT binary patch literal 44446 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 diff --git a/py_app/app/routes.py b/py_app/app/routes.py index b9d47a3..ebe4726 100644 --- a/py_app/app/routes.py +++ b/py_app/app/routes.py @@ -23,6 +23,23 @@ from .settings import ( bp = Blueprint('main', __name__) warehouse_bp = Blueprint('warehouse', __name__) +def format_cell_data(cell): + """Helper function to format cell data, especially dates and times""" + if isinstance(cell, datetime): + # Format date as dd/mm/yyyy + return cell.strftime('%d/%m/%Y') + elif isinstance(cell, timedelta): + # Convert timedelta to HH:MM:SS format + total_seconds = int(cell.total_seconds()) + hours, remainder = divmod(total_seconds, 3600) + minutes, seconds = divmod(remainder, 60) + return f"{hours:02d}:{minutes:02d}:{seconds:02d}" + elif hasattr(cell, 'date'): # Handle date objects + # Format date as dd/mm/yyyy + return cell.strftime('%d/%m/%Y') + else: + return cell + @bp.route('/store_articles') def store_articles(): return render_template('store_articles.html') @@ -278,68 +295,90 @@ def get_report_data(): conn = get_db_connection() cursor = conn.cursor() - if report == "1": # Logic for the 1-day report - one_day_ago = datetime.now() - timedelta(days=1) + if report == "1": # Logic for the 1-day report (today's records) + today = datetime.now().strftime('%Y-%m-%d') + print(f"DEBUG: Daily report searching for records on date: {today}") + cursor.execute(""" + SELECT Id, operator_code, CP_base_code, OC1_code, OC2_code, quality_code, date, time, approved_quantity, rejected_quantity + FROM scan1_orders + WHERE date = ? + ORDER BY date DESC, time DESC + """, (today,)) + rows = cursor.fetchall() + print(f"DEBUG: Daily report found {len(rows)} rows for today ({today}):", rows) + data["headers"] = ["Id", "Operator Code", "CP Base Code", "OC1 Code", "OC2 Code", "Quality Code", "Date", "Time", "Approved Quantity", "Rejected Quantity"] + data["rows"] = [[format_cell_data(cell) for cell in row] for row in rows] + + elif report == "2": # Logic for the 5-day report (last 5 days including today) + five_days_ago = datetime.now() - timedelta(days=4) # Last 4 days + today = 5 days + start_date = five_days_ago.strftime('%Y-%m-%d') + print(f"DEBUG: 5-day report searching for records from {start_date} onwards") cursor.execute(""" SELECT Id, operator_code, CP_base_code, OC1_code, OC2_code, quality_code, date, time, approved_quantity, rejected_quantity FROM scan1_orders WHERE date >= ? ORDER BY date DESC, time DESC - """, (one_day_ago.strftime('%Y-%m-%d'),)) + """, (start_date,)) rows = cursor.fetchall() - print("Fetched rows for report 1 (last 1 day):", rows) + print(f"DEBUG: 5-day report found {len(rows)} rows from {start_date} onwards:", rows) data["headers"] = ["Id", "Operator Code", "CP Base Code", "OC1 Code", "OC2 Code", "Quality Code", "Date", "Time", "Approved Quantity", "Rejected Quantity"] - data["rows"] = [[str(cell) if isinstance(cell, (datetime, timedelta)) else cell for cell in row] for row in rows] + data["rows"] = [[format_cell_data(cell) for cell in row] for row in rows] - elif report == "2": # Logic for the 5-day report - five_days_ago = datetime.now() - timedelta(days=5) + elif report == "3": # Logic for the report with non-zero quality_code (today only) + today = datetime.now().strftime('%Y-%m-%d') + print(f"DEBUG: Quality defects report (today) searching for records on {today} with quality issues") cursor.execute(""" - SELECT Id, operator_code, CP_base_code, OC1_code, OC2_code, quality_code, date, time, approved_quantity, rejected_quantity + SELECT Id, operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time, approved_quantity, rejected_quantity FROM scan1_orders - WHERE date >= ? + WHERE date = ? AND quality_code != 0 ORDER BY date DESC, time DESC - """, (five_days_ago.strftime('%Y-%m-%d'),)) + """, (today,)) rows = cursor.fetchall() - print("Fetched rows for report 2 (last 5 days):", rows) - data["headers"] = ["Id", "Operator Code", "CP Base Code", "OC1 Code", "OC2 Code", "Quality Code", "Date", "Time", "Approved Quantity", "Rejected Quantity"] - data["rows"] = [[str(cell) if isinstance(cell, (datetime, timedelta)) else cell for cell in row] for row in rows] + print(f"DEBUG: Quality defects report (today) found {len(rows)} rows with quality issues for {today}:", rows) + data["headers"] = ["Id", "Operator Code", "CP Full Code", "OC1 Code", "OC2 Code", "Quality Code", "Date", "Time", "Approved Quantity", "Rejected Quantity"] + data["rows"] = [[format_cell_data(cell) for cell in row] for row in rows] - elif report == "3": # Logic for the report with non-zero quality_code (1 day) - one_day_ago = datetime.now() - timedelta(days=1) + elif report == "4": # Logic for the report with non-zero quality_code (last 5 days) + five_days_ago = datetime.now() - timedelta(days=4) # Last 4 days + today = 5 days + start_date = five_days_ago.strftime('%Y-%m-%d') + print(f"DEBUG: Quality defects report (5 days) searching for records from {start_date} onwards with quality issues") cursor.execute(""" SELECT Id, operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time, approved_quantity, rejected_quantity FROM scan1_orders WHERE date >= ? AND quality_code != 0 ORDER BY date DESC, time DESC - """, (one_day_ago.strftime('%Y-%m-%d'),)) + """, (start_date,)) rows = cursor.fetchall() - print("Fetched rows for report 3 (non-zero quality_code, last 1 day):", rows) + print(f"DEBUG: Quality defects report (5 days) found {len(rows)} rows with quality issues from {start_date} onwards:", rows) data["headers"] = ["Id", "Operator Code", "CP Full Code", "OC1 Code", "OC2 Code", "Quality Code", "Date", "Time", "Approved Quantity", "Rejected Quantity"] - data["rows"] = [[str(cell) if isinstance(cell, (datetime, timedelta)) else cell for cell in row] for row in rows] - - elif report == "4": # Logic for the report with non-zero quality_code (5 days) - five_days_ago = datetime.now() - timedelta(days=5) - cursor.execute(""" - SELECT Id, operator_code, CP_full_code, OC1_code, OC2 Code, quality_code, date, time, approved_quantity, rejected_quantity - FROM scan1_orders - WHERE date >= ? AND quality_code != 0 - ORDER BY date DESC, time DESC - """, (five_days_ago.strftime('%Y-%m-%d'),)) - rows = cursor.fetchall() - print("Fetched rows for report 4 (non-zero quality_code, last 5 days):", rows) - data["headers"] = ["Id", "Operator Code", "CP Base Code", "OC1 Code", "OC2 Code", "Quality Code", "Date", "Time", "Approved Quantity", "Rejected Quantity"] - data["rows"] = [[str(cell) if isinstance(cell, (datetime, timedelta)) else cell for cell in row] for row in rows] + data["rows"] = [[format_cell_data(cell) for cell in row] for row in rows] elif report == "5": # Logic for the 5-ft report (all rows) - cursor.execute(""" - SELECT Id, operator_code, CP_base_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time, approved_quantity, rejected_quantity - FROM scan1_orders - ORDER BY date DESC, time DESC - """) - rows = cursor.fetchall() - print("Fetched rows for report 5 (all rows):", rows) - data["headers"] = ["Id", "Operator Code", "CP Base Code", "CP Full Code", "OC1 Code", "OC2 Code", "Quality Code", "Date", "Time", "Approved Quantity of order", "Rejected Quantity of order"] - data["rows"] = [[str(cell) if isinstance(cell, (datetime, timedelta)) else cell for cell in row] for row in rows] + # First check if table exists and has any data + try: + cursor.execute("SELECT COUNT(*) FROM scan1_orders") + total_count = cursor.fetchone()[0] + print(f"DEBUG: Total records in scan1_orders table: {total_count}") + + if total_count == 0: + print("DEBUG: No data found in scan1_orders table") + data["headers"] = ["Id", "Operator Code", "CP Base Code", "CP Full Code", "OC1 Code", "OC2 Code", "Quality Code", "Date", "Time", "Approved Quantity of order", "Rejected Quantity of order"] + data["rows"] = [] + data["message"] = "No scan data available in the database. Please ensure scanning operations have been performed and data has been recorded." + else: + cursor.execute(""" + SELECT Id, operator_code, CP_base_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time, approved_quantity, rejected_quantity + FROM scan1_orders + ORDER BY date DESC, time DESC + """) + rows = cursor.fetchall() + print(f"DEBUG: Fetched {len(rows)} rows for report 5 (all rows)") + data["headers"] = ["Id", "Operator Code", "CP Base Code", "CP Full Code", "OC1 Code", "OC2 Code", "Quality Code", "Date", "Time", "Approved Quantity of order", "Rejected Quantity of order"] + data["rows"] = [[format_cell_data(cell) for cell in row] for row in rows] + + except mariadb.Error as table_error: + print(f"DEBUG: Table access error: {table_error}") + data["error"] = f"Database table error: {table_error}" conn.close() except mariadb.Error as e: @@ -352,6 +391,8 @@ def get_report_data(): @bp.route('/generate_report', methods=['GET']) def generate_report(): """Generate report for specific date (calendar-based report)""" + from datetime import datetime, timedelta + report = request.args.get('report') selected_date = request.args.get('date') data = {"headers": [], "rows": []} @@ -361,6 +402,14 @@ def generate_report(): cursor = conn.cursor() if report == "6" and selected_date: # Custom date report + print(f"DEBUG: Searching for date: {selected_date}") + + # First, let's check what dates exist in the database + cursor.execute("SELECT DISTINCT date FROM scan1_orders ORDER BY date DESC LIMIT 10") + existing_dates = cursor.fetchall() + print(f"DEBUG: Available dates in database: {existing_dates}") + + # Try exact match first cursor.execute(""" SELECT Id, operator_code, CP_base_code, OC1_code, OC2_code, quality_code, date, time, approved_quantity, rejected_quantity FROM scan1_orders @@ -368,18 +417,320 @@ def generate_report(): ORDER BY time DESC """, (selected_date,)) rows = cursor.fetchall() - print(f"Fetched rows for report 6 (custom date {selected_date}):", rows) + print(f"DEBUG: Exact match found {len(rows)} rows") + + # If no exact match, try with DATE() function to handle different formats + if len(rows) == 0: + cursor.execute(""" + SELECT Id, operator_code, CP_base_code, OC1_code, OC2_code, quality_code, date, time, approved_quantity, rejected_quantity + FROM scan1_orders + WHERE DATE(date) = ? + ORDER BY time DESC + """, (selected_date,)) + rows = cursor.fetchall() + print(f"DEBUG: DATE() function match found {len(rows)} rows") + + # If still no match, try LIKE pattern + if len(rows) == 0: + cursor.execute(""" + SELECT Id, operator_code, CP_base_code, OC1_code, OC2_code, quality_code, date, time, approved_quantity, rejected_quantity + FROM scan1_orders + WHERE date LIKE ? + ORDER BY time DESC + """, (f"{selected_date}%",)) + rows = cursor.fetchall() + print(f"DEBUG: LIKE pattern match found {len(rows)} rows") + + print(f"DEBUG: Final result - {len(rows)} rows for date {selected_date}") + if len(rows) > 0: + print(f"DEBUG: Sample row: {rows[0]}") + data["headers"] = ["Id", "Operator Code", "CP Base Code", "OC1 Code", "OC2 Code", "Quality Code", "Date", "Time", "Approved Quantity", "Rejected Quantity"] - data["rows"] = [[str(cell) if isinstance(cell, (datetime, timedelta)) else cell for cell in row] for row in rows] + data["rows"] = [[format_cell_data(cell) for cell in row] for row in rows] + + # Add helpful message if no data found + if len(rows) == 0: + data["message"] = f"No scan data found for {selected_date}. Please select a date when scanning operations were performed." + + elif report == "7": # Date Range Report + start_date = request.args.get('start_date') + end_date = request.args.get('end_date') + + if start_date and end_date: + print(f"DEBUG: Date range report - Start: {start_date}, End: {end_date}") + + # Validate date format and order + try: + start_dt = datetime.strptime(start_date, '%Y-%m-%d') + end_dt = datetime.strptime(end_date, '%Y-%m-%d') + + if start_dt > end_dt: + data["error"] = "Start date cannot be after end date." + conn.close() + return jsonify(data) + + except ValueError: + data["error"] = "Invalid date format. Please use YYYY-MM-DD format." + conn.close() + return jsonify(data) + + # First, check what dates exist in the database for the range + cursor.execute(""" + SELECT DISTINCT date FROM scan1_orders + WHERE date >= ? AND date <= ? + ORDER BY date DESC + """, (start_date, end_date)) + existing_dates = cursor.fetchall() + print(f"DEBUG: Available dates in range: {existing_dates}") + + # Query for all records in the date range + cursor.execute(""" + SELECT Id, operator_code, CP_base_code, OC1_code, OC2_code, quality_code, date, time, approved_quantity, rejected_quantity + FROM scan1_orders + WHERE date >= ? AND date <= ? + ORDER BY date DESC, time DESC + """, (start_date, end_date)) + rows = cursor.fetchall() + print(f"DEBUG: Date range query found {len(rows)} rows from {start_date} to {end_date}") + + data["headers"] = ["Id", "Operator Code", "CP Base Code", "OC1 Code", "OC2 Code", "Quality Code", "Date", "Time", "Approved Quantity", "Rejected Quantity"] + data["rows"] = [[format_cell_data(cell) for cell in row] for row in rows] + + # Add helpful message if no data found + if len(rows) == 0: + data["message"] = f"No scan data found between {start_date} and {end_date}. Please select dates when scanning operations were performed." + else: + # Add summary information + total_approved = sum(row[8] for row in rows if row[8] is not None) + total_rejected = sum(row[9] for row in rows if row[9] is not None) + data["summary"] = { + "total_records": len(rows), + "date_range": f"{start_date} to {end_date}", + "total_approved": total_approved, + "total_rejected": total_rejected, + "dates_with_data": len(existing_dates) + } + else: + data["error"] = "Both start date and end date are required for date range report." + + elif report == "8" and selected_date: # Custom date quality defects report + print(f"DEBUG: Quality defects report for specific date: {selected_date}") + + # First, let's check what dates exist in the database + cursor.execute("SELECT DISTINCT date FROM scan1_orders ORDER BY date DESC LIMIT 10") + existing_dates = cursor.fetchall() + print(f"DEBUG: Available dates in database: {existing_dates}") + + # Try exact match first for defects (quality_code != 0) + cursor.execute(""" + SELECT Id, operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time, approved_quantity, rejected_quantity + FROM scan1_orders + WHERE date = ? AND quality_code != 0 + ORDER BY quality_code DESC, time DESC + """, (selected_date,)) + rows = cursor.fetchall() + print(f"DEBUG: Quality defects exact match found {len(rows)} rows for {selected_date}") + + # If no exact match, try with DATE() function to handle different formats + if len(rows) == 0: + cursor.execute(""" + SELECT Id, operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time, approved_quantity, rejected_quantity + FROM scan1_orders + WHERE DATE(date) = ? AND quality_code != 0 + ORDER BY quality_code DESC, time DESC + """, (selected_date,)) + rows = cursor.fetchall() + print(f"DEBUG: Quality defects DATE() function match found {len(rows)} rows") + + # If still no match, try LIKE pattern + if len(rows) == 0: + cursor.execute(""" + SELECT Id, operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time, approved_quantity, rejected_quantity + FROM scan1_orders + WHERE date LIKE ? AND quality_code != 0 + ORDER BY quality_code DESC, time DESC + """, (f"{selected_date}%",)) + rows = cursor.fetchall() + print(f"DEBUG: Quality defects LIKE pattern match found {len(rows)} rows") + + print(f"DEBUG: Final quality defects result - {len(rows)} rows for date {selected_date}") + if len(rows) > 0: + print(f"DEBUG: Sample defective item: {rows[0]}") + + data["headers"] = ["Id", "Operator Code", "CP Full Code", "OC1 Code", "OC2 Code", "Quality Code", "Date", "Time", "Approved Quantity", "Rejected Quantity"] + data["rows"] = [[format_cell_data(cell) for cell in row] for row in rows] + + # Add helpful message if no data found + if len(rows) == 0: + data["message"] = f"No quality defects found for {selected_date}. This could mean no scanning was performed or all items passed quality control." + else: + # Add summary for quality defects + total_defective_items = len(rows) + total_rejected_qty = sum(row[9] for row in rows if row[9] is not None) + unique_quality_codes = len(set(row[5] for row in rows if row[5] != 0)) + + data["defects_summary"] = { + "total_defective_items": total_defective_items, + "total_rejected_quantity": total_rejected_qty, + "unique_defect_types": unique_quality_codes, + "date": selected_date + } conn.close() except mariadb.Error as e: print(f"Error fetching custom date report: {e}") - data["error"] = f"Error fetching report data for {selected_date}." + data["error"] = f"Error fetching report data for {selected_date if report == '6' or report == '8' else 'date range'}." print("Custom date report data being returned:", data) return jsonify(data) +@bp.route('/debug_dates', methods=['GET']) +def debug_dates(): + """Debug route to check available dates in database""" + try: + conn = get_db_connection() + cursor = conn.cursor() + + # Get all distinct dates + cursor.execute("SELECT DISTINCT date FROM scan1_orders ORDER BY date DESC") + dates = cursor.fetchall() + + # Get total count + cursor.execute("SELECT COUNT(*) FROM scan1_orders") + total_count = cursor.fetchone()[0] + + # Get sample data + cursor.execute("SELECT date, time FROM scan1_orders ORDER BY date DESC LIMIT 5") + sample_data = cursor.fetchall() + + conn.close() + + return jsonify({ + "total_records": total_count, + "available_dates": [str(date[0]) for date in dates], + "sample_data": [{"date": str(row[0]), "time": str(row[1])} for row in sample_data] + }) + except Exception as e: + return jsonify({"error": str(e)}) + +@bp.route('/test_database', methods=['GET']) +def test_database(): + """Test database connection and query the scan1_orders table""" + try: + print("DEBUG: Testing database connection...") + conn = get_db_connection() + cursor = conn.cursor() + print("DEBUG: Database connection successful!") + + # Test 1: Check if table exists + try: + cursor.execute("SHOW TABLES LIKE 'scan1_orders'") + table_exists = cursor.fetchone() + print(f"DEBUG: Table scan1_orders exists: {table_exists is not None}") + + if not table_exists: + conn.close() + return jsonify({ + "success": False, + "message": "Table 'scan1_orders' does not exist in the database" + }) + except Exception as e: + print(f"DEBUG: Error checking table existence: {e}") + conn.close() + return jsonify({ + "success": False, + "message": f"Error checking table existence: {e}" + }) + + # Test 2: Get table structure + try: + cursor.execute("DESCRIBE scan1_orders") + table_structure = cursor.fetchall() + print(f"DEBUG: Table structure: {table_structure}") + except Exception as e: + print(f"DEBUG: Error getting table structure: {e}") + table_structure = [] + + # Test 3: Count total records + try: + cursor.execute("SELECT COUNT(*) FROM scan1_orders") + total_count = cursor.fetchone()[0] + print(f"DEBUG: Total records in table: {total_count}") + except Exception as e: + print(f"DEBUG: Error counting records: {e}") + total_count = -1 + + # Test 4: Get sample data (if any exists) + sample_data = [] + try: + cursor.execute("SELECT * FROM scan1_orders LIMIT 5") + raw_data = cursor.fetchall() + print(f"DEBUG: Sample data (first 5 rows): {raw_data}") + + # Convert data to JSON-serializable format using consistent formatting + sample_data = [] + for row in raw_data: + converted_row = [format_cell_data(item) for item in row] + sample_data.append(converted_row) + except Exception as e: + print(f"DEBUG: Error getting sample data: {e}") + + # Test 5: Get distinct dates (if any exist) + available_dates = [] + try: + cursor.execute("SELECT DISTINCT date FROM scan1_orders ORDER BY date DESC LIMIT 10") + date_rows = cursor.fetchall() + available_dates = [str(row[0]) for row in date_rows] + print(f"DEBUG: Available dates: {available_dates}") + except Exception as e: + print(f"DEBUG: Error getting dates: {e}") + + conn.close() + + # Test 6: Add a current date sample record for testing daily reports + try: + from datetime import datetime + current_date = datetime.now().strftime('%Y-%m-%d') + current_time = datetime.now().strftime('%H:%M:%S') + + # Check if we already have a record for today + cursor.execute("SELECT COUNT(*) FROM scan1_orders WHERE date = ?", (current_date,)) + today_count = cursor.fetchone()[0] + + if today_count == 0: + print(f"DEBUG: No records found for today ({current_date}), adding sample record...") + cursor.execute(""" + INSERT INTO scan1_orders (operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time, approved_quantity, rejected_quantity) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + """, ('OP01', 'CP99999999-0001', 'OC01', 'OC02', 0, current_date, current_time, 1, 0)) + conn.commit() + print(f"DEBUG: Added sample record for today ({current_date})") + message_addendum = " Added sample record for today to test daily reports." + else: + print(f"DEBUG: Found {today_count} records for today ({current_date})") + message_addendum = f" Found {today_count} records for today." + except Exception as e: + print(f"DEBUG: Error adding sample record: {e}") + message_addendum = " Could not add sample record for testing." + + return jsonify({ + "success": True, + "database_connection": "OK", + "table_exists": table_exists is not None, + "table_structure": [{"field": row[0], "type": row[1], "null": row[2]} for row in table_structure], + "total_records": total_count, + "sample_data": sample_data, + "available_dates": available_dates, + "message": f"Database test completed. Found {total_count} records in scan1_orders table.{message_addendum}" + }) + + except Exception as e: + print(f"DEBUG: Database test failed: {e}") + return jsonify({ + "success": False, + "message": f"Database connection failed: {e}" + }) + @bp.route('/etichete') def etichete(): if 'role' not in session or session['role'] not in ['superadmin', 'etichete']: diff --git a/py_app/app/static/script.js b/py_app/app/static/script.js index 5a724d6..ad31c39 100644 --- a/py_app/app/static/script.js +++ b/py_app/app/static/script.js @@ -32,14 +32,7 @@ document.addEventListener('DOMContentLoaded', () => { updateThemeToggleButtonText(); // Update the button text after toggling }); - // Helper function to format dates - function formatDate(dateString) { - const date = new Date(dateString); - if (!isNaN(date)) { - return date.toISOString().split('T')[0]; // Format as yyyy-mm-dd - } - return dateString; // Fallback if not a valid date - } + // Date formatting is now handled consistently on the backend // Function to populate the table with data function populateTable(data) { @@ -50,7 +43,7 @@ document.addEventListener('DOMContentLoaded', () => { tableHead.innerHTML = ''; tableBody.innerHTML = ''; - if (data.headers && data.rows) { + if (data.headers && data.rows && data.rows.length > 0) { // Populate table headers data.headers.forEach((header) => { const th = document.createElement('th'); @@ -64,23 +57,32 @@ document.addEventListener('DOMContentLoaded', () => { row.forEach((cell, index) => { const td = document.createElement('td'); - // Format the "Date" column - if (data.headers[index].toLowerCase() === 'date' && cell) { - td.textContent = formatDate(cell); - } else { - td.textContent = cell; - } + // Use the cell data as-is since backend now handles formatting + td.textContent = cell; tr.appendChild(td); }); tableBody.appendChild(tr); }); } else { - // No data available + // Handle no data scenarios const tr = document.createElement('tr'); const td = document.createElement('td'); - td.textContent = 'No data available.'; - td.colSpan = data.headers ? data.headers.length : 1; + + // Use custom message if provided, otherwise use default + if (data.message) { + td.textContent = data.message; + } else if (data.error) { + td.textContent = `Error: ${data.error}`; + } else { + td.textContent = 'No data available.'; + } + + td.colSpan = data.headers ? data.headers.length || 1 : 1; + td.style.textAlign = 'center'; + td.style.padding = '20px'; + td.style.fontStyle = 'italic'; + td.style.color = '#666'; tr.appendChild(td); tableBody.appendChild(tr); } @@ -115,9 +117,20 @@ document.addEventListener('DOMContentLoaded', () => { // Handle report button clicks reportButtons.forEach((button) => { button.addEventListener('click', () => { + // Skip buttons that have their own handlers + if (button.id === 'select-day-report' || button.id === 'date-range-report' || button.id === 'select-day-defects-report') { + return; + } + const reportNumber = button.dataset.report; const reportLabel = button.textContent.trim(); + // Check if reportNumber exists + if (!reportNumber) { + console.warn('Report button clicked but no data-report attribute found:', button); + return; + } + // Update the title dynamically reportTitle.textContent = `Data for "${reportLabel}"`; @@ -131,6 +144,16 @@ document.addEventListener('DOMContentLoaded', () => { }) .then((data) => { console.log("Fetched data:", data); // Debugging + + // Update title with additional info + if (data.message) { + reportTitle.textContent = data.message; + } else if (data.rows && data.rows.length > 0) { + reportTitle.textContent = `${reportLabel} (${data.rows.length} records)`; + } else { + reportTitle.textContent = `${reportLabel} - No data found`; + } + populateTable(data); }) .catch((error) => { @@ -153,6 +176,79 @@ document.addEventListener('DOMContentLoaded', () => { exportTableToCSV(filename); }); + // Test Database Button + const testDatabaseBtn = document.getElementById('test-database'); + if (testDatabaseBtn) { + testDatabaseBtn.addEventListener('click', () => { + console.log('Testing database connection...'); + reportTitle.textContent = 'Testing Database Connection...'; + + fetch('/test_database') + .then(response => response.json()) + .then(data => { + console.log('Database test results:', data); + + if (data.success) { + reportTitle.textContent = `Database Test Results - ${data.total_records} records found`; + + // Create a detailed results table + const thead = reportTable.querySelector('thead tr'); + const tbody = reportTable.querySelector('tbody'); + + // Clear existing content + thead.innerHTML = ''; + tbody.innerHTML = ''; + + // Add headers + const headers = ['Test Item', 'Result', 'Details']; + headers.forEach(header => { + const th = document.createElement('th'); + th.textContent = header; + thead.appendChild(th); + }); + + // Add test results + const results = [ + ['Database Connection', data.database_connection, 'Connection successful'], + ['Table Exists', data.table_exists ? 'YES' : 'NO', 'scan1_orders table check'], + ['Total Records', data.total_records, 'Number of rows in table'], + ['Table Structure', `${data.table_structure.length} columns`, data.table_structure.map(col => `${col.field} (${col.type})`).join(', ')], + ['Available Dates', data.available_dates.length, data.available_dates.join(', ') || 'No dates found'], + ['Sample Data', data.sample_data.length > 0 ? 'Available' : 'Empty', `${data.sample_data.length} sample rows`] + ]; + + results.forEach(result => { + const row = document.createElement('tr'); + result.forEach(cell => { + const td = document.createElement('td'); + td.textContent = cell; + row.appendChild(td); + }); + tbody.appendChild(row); + }); + + // Show alert with summary + alert(`Database Test Complete!\n\nConnection: ${data.database_connection}\nTable exists: ${data.table_exists}\nTotal records: ${data.total_records}\nMessage: ${data.message}`); + + } else { + reportTitle.textContent = 'Database Test Failed'; + alert(`Database test failed: ${data.message}`); + + // Show error in table + const thead = reportTable.querySelector('thead tr'); + const tbody = reportTable.querySelector('tbody'); + thead.innerHTML = 'Error'; + tbody.innerHTML = `${data.message}`; + } + }) + .catch(error => { + console.error('Database test error:', error); + reportTitle.textContent = 'Database Test Error'; + alert(`Error testing database: ${error.message}`); + }); + }); + } + // Placeholder for PDF export functionality exportPdfButton.addEventListener('click', () => { alert('Exporting current report as PDF...'); @@ -307,6 +403,19 @@ document.addEventListener('DOMContentLoaded', () => { }); } + // Show calendar modal for quality defects report + const selectDayDefectsReportBtn = document.getElementById('select-day-defects-report'); + if (selectDayDefectsReportBtn) { + selectDayDefectsReportBtn.addEventListener('click', () => { + console.log('DEBUG: Select Day Quality Defects Report button clicked!'); + calendarModal.style.display = 'block'; + generateCalendar(currentDate); + + // Mark this as a defects report by setting a flag + calendarModal.setAttribute('data-report-type', 'defects'); + }); + } + // Close modal events if (closeModal) { closeModal.addEventListener('click', closeCalendarModal); @@ -340,13 +449,32 @@ document.addEventListener('DOMContentLoaded', () => { // Confirm date selection if (confirmDate) { confirmDate.addEventListener('click', () => { + console.log('DEBUG: Calendar Generate Report button clicked!'); if (selectedDate) { - // Format date as YYYY-MM-DD - const formattedDate = selectedDate.toISOString().split('T')[0]; + // Format date as YYYY-MM-DD (timezone-safe) + const year = selectedDate.getFullYear(); + const month = String(selectedDate.getMonth() + 1).padStart(2, '0'); + const day = String(selectedDate.getDate()).padStart(2, '0'); + const formattedDate = `${year}-${month}-${day}`; + console.log(`DEBUG: Selected date object:`, selectedDate); + console.log(`DEBUG: Selected date formatted as: ${formattedDate}`); + + // Check if this is a defects report or regular report + const reportType = calendarModal.getAttribute('data-report-type'); + console.log(`DEBUG: Report type: ${reportType}`); + closeCalendarModal(); - // Fetch report data for the selected date - fetchCustomDateReport(formattedDate); + // Fetch appropriate report data for the selected date + if (reportType === 'defects') { + console.log('DEBUG: About to call fetchCustomDefectsReport'); + fetchCustomDefectsReport(formattedDate); + } else { + console.log('DEBUG: About to call fetchCustomDateReport'); + fetchCustomDateReport(formattedDate); + } + } else { + console.log('DEBUG: No date selected when Generate Report clicked'); } }); } @@ -356,6 +484,9 @@ document.addEventListener('DOMContentLoaded', () => { selectedDate = null; confirmDate.disabled = true; + // Clear report type + calendarModal.removeAttribute('data-report-type'); + // Remove selected class from all days const selectedDays = document.querySelectorAll('.calendar-day.selected'); selectedDays.forEach(day => day.classList.remove('selected')); @@ -417,23 +548,387 @@ document.addEventListener('DOMContentLoaded', () => { } function fetchCustomDateReport(dateString) { - reportTitle.textContent = `Loading report for ${dateString}...`; + console.log(`DEBUG: fetchCustomDateReport called with date: ${dateString}`); - fetch(`/generate_report?report=6&date=${dateString}`) - .then(response => response.json()) + // Get elements directly to avoid scope issues + const reportTitleElement = document.getElementById('report-title'); + const reportTableElement = document.getElementById('report-table'); + + console.log(`DEBUG: reportTitle element:`, reportTitleElement); + console.log(`DEBUG: reportTable element:`, reportTableElement); + + if (!reportTitleElement) { + console.error('ERROR: report-title element not found!'); + return; + } + + reportTitleElement.textContent = `Loading report for ${dateString}...`; + + // Local function to populate table to avoid scope issues + function localPopulateTable(data) { + const tableHead = reportTableElement.querySelector('thead tr'); + const tableBody = reportTableElement.querySelector('tbody'); + + // Clear existing table content + tableHead.innerHTML = ''; + tableBody.innerHTML = ''; + + if (data.headers && data.rows && data.rows.length > 0) { + // Populate table headers + data.headers.forEach((header) => { + const th = document.createElement('th'); + th.textContent = header; + tableHead.appendChild(th); + }); + + // Populate table rows + data.rows.forEach((row) => { + const tr = document.createElement('tr'); + row.forEach((cell, index) => { + const td = document.createElement('td'); + // Format dates properly + if (data.headers[index].toLowerCase() === 'date' && cell) { + td.textContent = cell; // Use as-is since backend already formats it + } else { + td.textContent = cell; + } + tr.appendChild(td); + }); + tableBody.appendChild(tr); + }); + } else { + // Handle no data scenarios + const tr = document.createElement('tr'); + const td = document.createElement('td'); + td.colSpan = 10; // Span all columns + td.textContent = 'No data available.'; + td.style.textAlign = 'center'; + tr.appendChild(td); + tableBody.appendChild(tr); + } + } + + const url = `/generate_report?report=6&date=${dateString}`; + console.log(`DEBUG: Making request to URL: ${url}`); + + fetch(url) + .then(response => { + console.log(`DEBUG: Response status: ${response.status}`); + return response.json(); + }) .then(data => { + console.log('DEBUG: Response data:', data); if (data.error) { - reportTitle.textContent = `Error: ${data.error}`; - populateTable({ headers: [], rows: [] }); + reportTitleElement.textContent = `Error: ${data.error}`; + localPopulateTable({ headers: [], rows: [] }); + } else if (data.message) { + reportTitleElement.textContent = data.message; + localPopulateTable({ headers: [], rows: [] }); + } else if (data.rows && data.rows.length === 0) { + reportTitleElement.textContent = `No data found for ${dateString}`; + localPopulateTable(data); } else { - reportTitle.textContent = `Daily Report for ${dateString}`; - populateTable(data); + reportTitleElement.textContent = `Daily Report for ${dateString} (${data.rows ? data.rows.length : 0} records)`; + localPopulateTable(data); } }) .catch(error => { console.error('Error fetching custom date report:', error); - reportTitle.textContent = 'Error loading report'; - populateTable({ headers: [], rows: [] }); + reportTitleElement.textContent = 'Error loading report'; + localPopulateTable({ headers: [], rows: [] }); + }); + } + + // Function to fetch quality defects report for specific date + function fetchCustomDefectsReport(dateString) { + console.log(`DEBUG: fetchCustomDefectsReport called with date: ${dateString}`); + + // Get elements directly to avoid scope issues + const reportTitleElement = document.getElementById('report-title'); + const reportTableElement = document.getElementById('report-table'); + + console.log(`DEBUG: reportTitle element:`, reportTitleElement); + console.log(`DEBUG: reportTable element:`, reportTableElement); + + if (!reportTitleElement) { + console.error('ERROR: report-title element not found!'); + return; + } + + reportTitleElement.textContent = `Loading quality defects report for ${dateString}...`; + + // Local function to populate table to avoid scope issues + function localPopulateTable(data) { + const tableHead = reportTableElement.querySelector('thead tr'); + const tableBody = reportTableElement.querySelector('tbody'); + + // Clear existing table content + tableHead.innerHTML = ''; + tableBody.innerHTML = ''; + + if (data.headers && data.rows && data.rows.length > 0) { + // Populate table headers + data.headers.forEach((header) => { + const th = document.createElement('th'); + th.textContent = header; + tableHead.appendChild(th); + }); + + // Populate table rows + data.rows.forEach((row) => { + const tr = document.createElement('tr'); + row.forEach((cell, index) => { + const td = document.createElement('td'); + // Highlight quality code column for defects + if (data.headers[index] === 'Quality Code' && cell != '0') { + td.style.backgroundColor = '#ffebee'; // Light red background + td.style.fontWeight = 'bold'; + } + td.textContent = cell; + tr.appendChild(td); + }); + tableBody.appendChild(tr); + }); + } else { + // Handle no data scenarios + const tr = document.createElement('tr'); + const td = document.createElement('td'); + td.colSpan = 10; // Span all columns + td.textContent = 'No quality defects found for this date.'; + td.style.textAlign = 'center'; + tr.appendChild(td); + tableBody.appendChild(tr); + } + } + + const url = `/generate_report?report=8&date=${dateString}`; + console.log(`DEBUG: Making request to URL: ${url}`); + + fetch(url) + .then(response => { + console.log(`DEBUG: Response status: ${response.status}`); + return response.json(); + }) + .then(data => { + console.log('DEBUG: Quality defects response data:', data); + + if (data.error) { + reportTitleElement.textContent = `Error: ${data.error}`; + localPopulateTable({ headers: [], rows: [] }); + } else if (data.message) { + reportTitleElement.textContent = data.message; + localPopulateTable({ headers: [], rows: [] }); + } else if (data.rows && data.rows.length === 0) { + reportTitleElement.textContent = `No quality defects found for ${dateString}`; + localPopulateTable(data); + } else { + let titleText = `Quality Defects Report for ${dateString} (${data.rows ? data.rows.length : 0} defective items)`; + + // Add defects summary info if available + if (data.defects_summary) { + const summary = data.defects_summary; + titleText += ` | ${summary.unique_defect_types} defect types, ${summary.total_rejected_quantity} rejected items`; + } + + reportTitleElement.textContent = titleText; + localPopulateTable(data); + } + }) + .catch(error => { + console.error('Error fetching quality defects report:', error); + reportTitleElement.textContent = 'Error loading quality defects report'; + localPopulateTable({ headers: [], rows: [] }); + }); + } + + // ===== DATE RANGE MODAL FUNCTIONALITY ===== + + const dateRangeReportBtn = document.getElementById('date-range-report'); + const dateRangeModal = document.getElementById('date-range-modal'); + const closeDateRange = document.getElementById('close-date-range'); + const cancelDateRange = document.getElementById('cancel-date-range'); + const confirmDateRange = document.getElementById('confirm-date-range'); + const startDateInput = document.getElementById('start-date'); + const endDateInput = document.getElementById('end-date'); + + if (dateRangeReportBtn && dateRangeModal) { + // Open date range modal + dateRangeReportBtn.addEventListener('click', () => { + console.log('DEBUG: Date Range Report button clicked!'); + + // Set default dates (last 7 days to today) + const today = new Date(); + const weekAgo = new Date(); + weekAgo.setDate(today.getDate() - 6); // Last 7 days including today + + const todayStr = formatDateForInput(today); + const weekAgoStr = formatDateForInput(weekAgo); + + startDateInput.value = weekAgoStr; + endDateInput.value = todayStr; + + console.log(`DEBUG: Default date range set to ${weekAgoStr} - ${todayStr}`); + + dateRangeModal.style.display = 'block'; + validateDateRange(); // Enable/disable confirm button based on inputs + }); + + // Close modal functions + function closeDateRangeModal() { + dateRangeModal.style.display = 'none'; + startDateInput.value = ''; + endDateInput.value = ''; + confirmDateRange.disabled = true; + } + + closeDateRange.addEventListener('click', closeDateRangeModal); + cancelDateRange.addEventListener('click', closeDateRangeModal); + + // Close modal when clicking outside + window.addEventListener('click', (e) => { + if (e.target === dateRangeModal) { + closeDateRangeModal(); + } + }); + + // Validate date range and enable/disable confirm button + function validateDateRange() { + const startDate = startDateInput.value; + const endDate = endDateInput.value; + + if (startDate && endDate) { + const start = new Date(startDate); + const end = new Date(endDate); + + if (start <= end) { + confirmDateRange.disabled = false; + console.log(`DEBUG: Valid date range: ${startDate} to ${endDate}`); + } else { + confirmDateRange.disabled = true; + console.log('DEBUG: Invalid date range: start date is after end date'); + } + } else { + confirmDateRange.disabled = true; + console.log('DEBUG: Missing start or end date'); + } + } + + // Validate when dates change + startDateInput.addEventListener('change', validateDateRange); + endDateInput.addEventListener('change', validateDateRange); + + // Confirm date range selection + confirmDateRange.addEventListener('click', () => { + const startDate = startDateInput.value; + const endDate = endDateInput.value; + + console.log(`DEBUG: Generating date range report from ${startDate} to ${endDate}`); + closeDateRangeModal(); + + // Fetch report data for the selected date range + fetchDateRangeReport(startDate, endDate); + }); + } + + // Helper function to format date for input field + function formatDateForInput(date) { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; + } + + // Function to fetch date range report + function fetchDateRangeReport(startDate, endDate) { + console.log(`DEBUG: Fetching date range report from ${startDate} to ${endDate}`); + + // Get elements directly to avoid scope issues + const reportTitleElement = document.getElementById('report-title'); + const reportTableElement = document.getElementById('report-table'); + + if (!reportTitleElement) { + console.error('ERROR: report-title element not found!'); + return; + } + + reportTitleElement.textContent = `Loading report for ${startDate} to ${endDate}...`; + + // Local function to populate table to avoid scope issues + function localPopulateTable(data) { + const tableHead = reportTableElement.querySelector('thead tr'); + const tableBody = reportTableElement.querySelector('tbody'); + + // Clear existing table content + tableHead.innerHTML = ''; + tableBody.innerHTML = ''; + + if (data.headers && data.rows && data.rows.length > 0) { + // Populate table headers + data.headers.forEach((header) => { + const th = document.createElement('th'); + th.textContent = header; + tableHead.appendChild(th); + }); + + // Populate table rows + data.rows.forEach((row) => { + const tr = document.createElement('tr'); + row.forEach((cell, index) => { + const td = document.createElement('td'); + td.textContent = cell; + tr.appendChild(td); + }); + tableBody.appendChild(tr); + }); + } else { + // Handle no data scenarios + const tr = document.createElement('tr'); + const td = document.createElement('td'); + td.colSpan = 10; // Span all columns + td.textContent = 'No data available for the selected date range.'; + td.style.textAlign = 'center'; + tr.appendChild(td); + tableBody.appendChild(tr); + } + } + + const url = `/generate_report?report=7&start_date=${startDate}&end_date=${endDate}`; + console.log(`DEBUG: Making date range request to URL: ${url}`); + + fetch(url) + .then(response => { + console.log(`DEBUG: Response status: ${response.status}`); + return response.json(); + }) + .then(data => { + console.log('DEBUG: Date range response data:', data); + + if (data.error) { + reportTitleElement.textContent = `Error: ${data.error}`; + localPopulateTable({ headers: [], rows: [] }); + } else if (data.message) { + reportTitleElement.textContent = data.message; + localPopulateTable({ headers: [], rows: [] }); + } else if (data.rows && data.rows.length === 0) { + reportTitleElement.textContent = `No data found for ${startDate} to ${endDate}`; + localPopulateTable(data); + } else { + const recordCount = data.rows ? data.rows.length : 0; + let titleText = `Date Range Report: ${startDate} to ${endDate} (${recordCount} records)`; + + // Add summary info if available + if (data.summary) { + titleText += ` | Approved: ${data.summary.total_approved}, Rejected: ${data.summary.total_rejected}`; + } + + reportTitleElement.textContent = titleText; + localPopulateTable(data); + } + }) + .catch(error => { + console.error('Error fetching date range report:', error); + reportTitleElement.textContent = 'Error loading date range report'; + localPopulateTable({ headers: [], rows: [] }); }); } }); \ No newline at end of file diff --git a/py_app/app/static/style.css b/py_app/app/static/style.css index 7dcb100..c7b71ba 100644 --- a/py_app/app/static/style.css +++ b/py_app/app/static/style.css @@ -748,6 +748,16 @@ body.dark-mode .export-description { padding: 8px 12px; } +.test-db-btn { + background-color: #6c757d !important; /* Gray color for test button */ + border-color: #6c757d !important; +} + +.test-db-btn:hover { + background-color: #5a6268 !important; + border-color: #545b62 !important; +} + .report-form-card .export-section .form-centered.last-buttons { padding: 5px 0; /* Reduced padding for export section */ } @@ -829,13 +839,14 @@ body.dark-mode .export-description { } .modal-content { - background-color: #fefefe; + background-color: #ffffff; margin: 5% auto; padding: 0; border-radius: 8px; width: 400px; max-width: 90%; box-shadow: 0 4px 20px rgba(0,0,0,0.3); + border: 1px solid #ddd; } .modal-header { @@ -843,7 +854,7 @@ body.dark-mode .export-description { justify-content: space-between; align-items: center; padding: 15px 20px; - background-color: #f8f9fa; + background-color: #f4f4f9; border-radius: 8px 8px 0 0; border-bottom: 1px solid #ddd; } @@ -851,6 +862,7 @@ body.dark-mode .export-description { .modal-header h4 { margin: 0; color: #333; + font-size: 1.2em; } .close-modal { @@ -859,6 +871,7 @@ body.dark-mode .export-description { cursor: pointer; color: #666; line-height: 1; + transition: color 0.2s ease; } .close-modal:hover { @@ -867,6 +880,7 @@ body.dark-mode .export-description { .modal-body { padding: 20px; + background-color: #ffffff; } .modal-footer { @@ -876,7 +890,152 @@ body.dark-mode .export-description { padding: 15px 20px; border-top: 1px solid #ddd; border-radius: 0 0 8px 8px; - background-color: #f8f9fa; + background-color: #f4f4f9; +} + +/* Dark Mode Support for Calendar Modal */ +body.dark-mode .modal-content { + background-color: #2c2c2c; + border: 1px solid #555; +} + +body.dark-mode .modal-header { + background-color: #1e1e1e; + border-bottom: 1px solid #555; +} + +body.dark-mode .modal-header h4 { + color: #fff; +} + +body.dark-mode .close-modal { + color: #ccc; +} + +body.dark-mode .close-modal:hover { + color: #fff; +} + +body.dark-mode .modal-body { + background-color: #2c2c2c; +} + +body.dark-mode .modal-footer { + background-color: #1e1e1e; + border-top: 1px solid #555; +} + +/* Modal Button Styling */ +.modal-footer .btn { + padding: 8px 16px; + font-size: 0.9em; + border-radius: 4px; + border: none; + cursor: pointer; + transition: all 0.2s ease; +} + +.modal-footer .btn-primary { + background-color: #007bff; + color: white; +} + +.modal-footer .btn-primary:hover { + background-color: #0056b3; +} + +.modal-footer .btn-primary:disabled { + background-color: #6c757d; + cursor: not-allowed; + opacity: 0.6; +} + +.modal-footer .btn-secondary { + background-color: #6c757d; + color: white; +} + +.modal-footer .btn-secondary:hover { + background-color: #545b62; +} + +/* Dark Mode Modal Buttons */ +body.dark-mode .modal-footer .btn-primary { + background-color: #007bff; +} + +body.dark-mode .modal-footer .btn-primary:hover { + background-color: #0056b3; +} + +body.dark-mode .modal-footer .btn-secondary { + background-color: #6c757d; +} + +body.dark-mode .modal-footer .btn-secondary:hover { + background-color: #545b62; +} + +/* Date Range Modal Styles */ +.date-range-container { + padding: 20px 0; +} + +.date-input-group { + margin-bottom: 20px; +} + +.date-input-group label { + display: block; + font-weight: 600; + margin-bottom: 8px; + color: #333; + font-size: 0.95em; +} + +.date-input { + width: 100%; + padding: 12px 15px; + border: 2px solid #ddd; + border-radius: 8px; + font-size: 1em; + background-color: #fff; + transition: border-color 0.3s ease; + box-sizing: border-box; +} + +.date-input:focus { + outline: none; + border-color: #007bff; + box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1); +} + +.date-help { + display: block; + font-size: 0.8em; + color: #666; + margin-top: 5px; + font-style: italic; +} + +/* Dark mode styles for date range modal */ +body.dark-mode .date-input-group label { + color: #e0e0e0; +} + +body.dark-mode .date-input { + background-color: #2d3748; + border-color: #4a5568; + color: #e0e0e0; +} + +body.dark-mode .date-input:focus { + border-color: #63b3ed; + box-shadow: 0 0 0 3px rgba(99, 179, 237, 0.1); +} + +body.dark-mode .date-help { + color: #a0aec0; } /* Calendar Styles */ @@ -898,18 +1057,20 @@ body.dark-mode .export-description { } .calendar-nav { - background: none; + background-color: #f4f4f9; border: 1px solid #ddd; border-radius: 4px; - padding: 5px 10px; + padding: 8px 12px; cursor: pointer; font-size: 14px; - color: #666; + color: #333; + transition: all 0.2s ease; } .calendar-nav:hover { - background-color: #f0f0f0; - color: #333; + background-color: #007bff; + color: white; + border-color: #007bff; } .calendar-grid { @@ -929,7 +1090,8 @@ body.dark-mode .export-description { font-weight: bold; font-size: 0.85em; color: #666; - background-color: #f8f9fa; + background-color: #f4f4f9; + border-radius: 4px; } .calendar-days { @@ -948,30 +1110,83 @@ body.dark-mode .export-description { display: flex; align-items: center; justify-content: center; + transition: all 0.2s ease; + background-color: #ffffff; + border: 1px solid transparent; } .calendar-day:hover { background-color: #e9ecef; + border-color: #007bff; } .calendar-day.other-month { color: #ccc; + background-color: #f8f9fa; } .calendar-day.today { background-color: #007bff; color: white; + font-weight: bold; } .calendar-day.selected { background-color: #28a745; color: white; + font-weight: bold; } .calendar-day.selected:hover { background-color: #218838; } +/* Dark Mode Calendar Styles */ +body.dark-mode .calendar-header h3 { + color: #fff; +} + +body.dark-mode .calendar-nav { + background-color: #3c3c3c; + border-color: #555; + color: #fff; +} + +body.dark-mode .calendar-nav:hover { + background-color: #007bff; + border-color: #007bff; +} + +body.dark-mode .calendar-weekdays div { + background-color: #3c3c3c; + color: #ccc; +} + +body.dark-mode .calendar-day { + background-color: #2c2c2c; + color: #fff; +} + +body.dark-mode .calendar-day:hover { + background-color: #444; + border-color: #007bff; +} + +body.dark-mode .calendar-day.other-month { + color: #666; + background-color: #333; +} + +body.dark-mode .calendar-day.today { + background-color: #007bff; + color: white; +} + +body.dark-mode .calendar-day.selected { + background-color: #28a745; + color: white; +} + /* Responsive Calendar */ @media (max-width: 480px) { .modal-content { diff --git a/py_app/app/templates/quality.html b/py_app/app/templates/quality.html index 64f64c0..426cecd 100644 --- a/py_app/app/templates/quality.html +++ b/py_app/app/templates/quality.html @@ -16,6 +16,10 @@ +
+ + +
@@ -24,6 +28,10 @@
+
+ + +
@@ -44,6 +52,7 @@
+
@@ -104,5 +113,33 @@ + + + {% endblock %}