diff --git a/py_app/app/__pycache__/routes.cpython-312.pyc b/py_app/app/__pycache__/routes.cpython-312.pyc index 491eb85..fea81f1 100644 Binary files a/py_app/app/__pycache__/routes.cpython-312.pyc and b/py_app/app/__pycache__/routes.cpython-312.pyc differ 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 %}