diff --git a/.env b/.env deleted file mode 100644 index 8d571af..0000000 --- a/.env +++ /dev/null @@ -1,24 +0,0 @@ -# ----------------------------- -# Flask App Configuration -# ----------------------------- -FLASK_ENV=development -FLASK_DEBUG=True -FLASK_HOST=0.0.0.0 -FLASK_PORT=5010 - -# ----------------------------- -# Security -# ----------------------------- -SECRET_KEY=secret1234 - -# ----------------------------- -# Database Configuration -# ----------------------------- -DB_DIALECT=mysql -# DB_DRIVER=pymysql -DB_HOST=127.0.0.1 -DB_PORT=3306 -DB_NAME=test_income_taxdb -DB_USER=root -DB_PASSWORD=root - diff --git a/.gitignore b/.gitignore index 4a2326d..c823ac0 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,13 @@ logs/ +# Environment variables +.env + +# Python cache +__pycache__/ +*.pyc + +# OS / Editor +.vscode/ +.idea/ diff --git a/AppCode/DocumentHandler.py b/AppCode/DocumentHandler.py index 81df6df..833cd35 100644 --- a/AppCode/DocumentHandler.py +++ b/AppCode/DocumentHandler.py @@ -1,4 +1,6 @@ -from flask import Flask, render_template, request, redirect, url_for, send_from_directory, abort, flash,send_file +from flask import ( + render_template, request, send_file, jsonify +) from werkzeug.utils import secure_filename import pandas as pd import os @@ -17,11 +19,40 @@ class DocumentHandler: self.isSuccess = False self.resultMessage = "" - # VIEW DOCUMENTS + # ========================= + # Utility: Parse Year + # ========================= + def parse_year(self, year_value): + """ + Accepts: + - '2026' + - 'AY 2026-2027' + Returns: + - 2026 (int) + """ + if not year_value: + return None + + year_value = year_value.strip() + + if year_value.isdigit(): + return int(year_value) + + try: + # "AY 2026-2027" → 2026 + return int(year_value.split()[1].split('-')[0]) + except Exception: + return None + + # ========================= + # View Documents + # ========================= def View(self, request): - year = request.args.get('year', '') + year_raw = request.args.get('year', '') stage = request.args.get('stage', '') + year = self.parse_year(year_raw) + dbconfig = DBConfig() connection = dbconfig.get_db_connection() @@ -30,15 +61,13 @@ class DocumentHandler: return cursor = connection.cursor(dictionary=True) - # --- FILTER QUERY --- + cursor.callproc("GetDocuments", [year, stage]) - # fetch first result set for result in cursor.stored_results(): self.documents = result.fetchall() break - # ---- GET YEARS FROM STORED PROCEDURE ---- cursor.callproc("GetYear") for result in cursor.stored_results(): @@ -49,55 +78,72 @@ class DocumentHandler: cursor.close() connection.close() + self.isSuccess = True + # ========================= # Upload Documents + # ========================= def Upload(self, request): dbconfig = DBConfig() connection = dbconfig.get_db_connection() - if connection: - cursor = connection.cursor() - files = request.files.getlist('documents') - year = request.form['year'] - stage = request.form['stage'] + if not connection: + return - for file in files: - extension = file.filename.rsplit('.', 1)[1] - if extension not in FileHandler.ALLOWED_EXTENSIONS: - print("Skip invalid file type : ",extension) - continue + cursor = connection.cursor() - filename = secure_filename(file.filename) - filepath = os.path.join(FileHandler.UPLOAD_FOLDER, filename) - file.save(filepath) + files = request.files.getlist('documents') + year_raw = request.form.get('year') + stage = request.form.get('stage') - cursor.callproc('InsertDocument', [ filename, filepath, extension, year, stage ]) + year = self.parse_year(year_raw) - connection.commit() - cursor.close() - connection.close() - # return redirect(url_for('view_documents')) + if not year: + self.resultMessage = "Invalid year selected." + return - def Summary_report(self, request): - dbconfig = DBConfig() - connection = dbconfig.get_db_connection() + for file in files: + if '.' not in file.filename: + continue - year_str = request.args.get('year') + extension = file.filename.rsplit('.', 1)[1].lower() - # If year not selected - if not year_str or not year_str.isdigit(): - yearGetter = YearGet() - allYears = yearGetter.get_year_by_model("AllYearsInAllModel") - yearGetter.close() - return render_template( - 'summary_reports.html', - years=allYears, - message="Please select a valid year to download." + if extension not in FileHandler.ALLOWED_EXTENSIONS: + print("Skipping invalid file:", extension) + continue + + filename = secure_filename(file.filename) + filepath = os.path.join(FileHandler.UPLOAD_FOLDER, filename) + + file.save(filepath) + + cursor.callproc( + 'InsertDocument', + [filename, filepath, extension, year, stage] ) - # Convert year to int (IMPORTANT FIX) - year = int(year_str) + connection.commit() + cursor.close() + connection.close() + + # ========================= + # Summary Preview (JSON) + # ========================= + def Summary_preview(self, request): + """ + Returns JSON preview of summary report for selected year. + """ + year_raw = request.args.get("year") + year = self.parse_year(year_raw) + + if not year: + return jsonify([]) + + dbconfig = DBConfig() + connection = dbconfig.get_db_connection() + if not connection: + return jsonify([]) try: stages = { @@ -112,11 +158,111 @@ class DocumentHandler: for stage_name, table_name in stages.items(): cursor = connection.cursor(dictionary=True) cursor.callproc("sp_get_stage_data", [table_name, year]) - rows = [] for result in cursor.stored_results(): rows = result.fetchall() + stage_data[stage_name] = pd.DataFrame(rows) if rows else pd.DataFrame() + cursor.close() + columns = [ + 'gross_total_income', 'disallowance_14a', + 'disallowance_37', '-', + 'deduction_80ia_business', + 'deduction_80ia_misc', + 'deduction_80ia_other', + 'deduction_sec37_disallowance', + 'deduction_80g', '-', + 'net_taxable_income', + 'tax_30_percent', + 'tax_book_profit_18_5', + 'tax_payable', 'surcharge', + 'edu_cess', 'total_tax_payable', + 'mat_credit_created', + 'mat_credit_utilized', + 'interest_234c', 'total_tax', + '-', 'advance_tax', 'tds', + 'tcs', 'sat', + 'tax_on_assessment', + 'refund', 'Remarks' + ] + + particulars = [ + "Gross Total Income", "Add: Disallowance u/s 14A", + "Add: Disallowance u/s 37", "GTI as per", + "Less: Deduction u/s 80IA - On Business Income", + "- On Misc Receipts", "- On Other", + "- On Sec 37 Disallowance", + "Less: Deduction u/s 80G", " ", + "Net Taxable Income", "Tax @ 30%", + "Tax @ 18.5% on Book Profit", + "Tax Payable", "Surcharge @ %", + "Education Cess @ %", "Total Tax Payable", + "Add: MAT Credit Created", + "Less: MAT Credit Utilized", + "Add: Interest u/s 234C", + "Total Tax", " ", + "Advance Tax", "TDS", "TCS", "SAT", + "Tax on Regular Assessment", + "Refund", "Remarks" + ] + + def safe_get(df, col): + return df[col].values[0] if col in df.columns and not df.empty else 0 + + preview = [] + for i, part in enumerate(particulars): + preview.append({ + "Particular": part, + "ITR": safe_get(stage_data['ITR'], columns[i]), + "AO": safe_get(stage_data['AO'], columns[i]), + "CIT": safe_get(stage_data['CIT'], columns[i]), + "ITAT": safe_get(stage_data['ITAT'], columns[i]), + }) + + return jsonify(preview) + + finally: + connection.close() + + def Summary_report(self, request): + dbconfig = DBConfig() + connection = dbconfig.get_db_connection() + + year_raw = request.args.get('year') + + # Safely parse year to int + try: + year = int(year_raw) + except (TypeError, ValueError): + year = None + + if not year: + yearGetter = YearGet() + allYears = yearGetter.get_year_by_model("AllYearsInAllModel") + yearGetter.close() + + return render_template( + 'summary_reports.html', + years=allYears, + message="Please select a valid year to download." + ) + + try: + stages = { + "ITR": "itr", + "AO": "ao", + "CIT": "cit", + "ITAT": "itat", + } + + stage_data = {} + + for stage_name, table_name in stages.items(): + cursor = connection.cursor(dictionary=True) + cursor.callproc("sp_get_stage_data", [table_name, year]) + rows = [] + for result in cursor.stored_results(): + rows = result.fetchall() stage_data[stage_name] = pd.DataFrame(rows) if rows else pd.DataFrame() cursor.close() @@ -124,25 +270,45 @@ class DocumentHandler: return df[col].values[0] if col in df.columns and not df.empty else "-" particulars = [ - "Gross Total Income", "Add: Disallowance u/s 14A", "Add: Disallowance u/s 37", - "GTI as per", "Less: Deduction u/s 80IA - On Business Income", "- On Misc Receipts", - "- On Other", "- On Sec 37 Disallowance", "Less: Deduction u/s 80G", " ", - "Net Taxable Income", "Tax @ 30%", "Tax @ 18.5% on Book Profit", - "Tax Payable", "Surcharge @ %", "Education Cess @ %", "Total Tax Payable","Add: MAT Credit Created", - "Less: MAT Credit Utilized", "Add: Interest u/s 234C", "Total Tax", " ", + "Gross Total Income", "Add: Disallowance u/s 14A", + "Add: Disallowance u/s 37", "GTI as per", + "Less: Deduction u/s 80IA - On Business Income", + "- On Misc Receipts", "- On Other", + "- On Sec 37 Disallowance", + "Less: Deduction u/s 80G", " ", + "Net Taxable Income", "Tax @ 30%", + "Tax @ 18.5% on Book Profit", + "Tax Payable", "Surcharge @ %", + "Education Cess @ %", "Total Tax Payable", + "Add: MAT Credit Created", + "Less: MAT Credit Utilized", + "Add: Interest u/s 234C", + "Total Tax", " ", "Advance Tax", "TDS", "TCS", "SAT", - "Tax on Regular Assessment", "Refund" , "Remarks" + "Tax on Regular Assessment", + "Refund", "Remarks" ] columns = [ - 'gross_total_income', 'disallowance_14a', 'disallowance_37', - '-', 'deduction_80ia_business','deduction_80ia_misc', - 'deduction_80ia_other', 'deduction_sec37_disallowance','deduction_80g', '-', - 'net_taxable_income', 'tax_30_percent','tax_book_profit_18_5', - 'tax_payable','surcharge', 'edu_cess', 'total_tax_payable', - 'mat_credit_created','mat_credit_utilized' , 'interest_234c','total_tax', '-', - 'advance_tax', 'tds', 'tcs', 'sat', - 'tax_on_assessment', 'refund', 'Remarks' + 'gross_total_income', 'disallowance_14a', + 'disallowance_37', '-', + 'deduction_80ia_business', + 'deduction_80ia_misc', + 'deduction_80ia_other', + 'deduction_sec37_disallowance', + 'deduction_80g', '-', + 'net_taxable_income', + 'tax_30_percent', + 'tax_book_profit_18_5', + 'tax_payable', 'surcharge', + 'edu_cess', 'total_tax_payable', + 'mat_credit_created', + 'mat_credit_utilized', + 'interest_234c', 'total_tax', + '-', 'advance_tax', 'tds', + 'tcs', 'sat', + 'tax_on_assessment', + 'refund', 'Remarks' ] data = { @@ -155,56 +321,44 @@ class DocumentHandler: df = pd.DataFrame(data) - # ===== Excel Export ===== output = io.BytesIO() + with pd.ExcelWriter(output, engine='xlsxwriter') as writer: - sheet_name = f"AY {year} - {year + 1}" + sheet_name = f"AY {year}-{year + 1}" df.to_excel(writer, index=False, sheet_name=sheet_name, startrow=2) workbook = writer.book worksheet = writer.sheets[sheet_name] - # ===== Company Heading ===== - company_heading = workbook.add_format({ + title = workbook.add_format({ 'bold': True, 'font_size': 14, - 'font_color': 'black', - 'align': 'center', - 'valign': 'middle' + 'align': 'center' }) worksheet.merge_range( 0, 0, 0, len(df.columns) - 1, "Laxmi Civil Engineering Services Pvt Ltd", - company_heading + title ) - # ===== Header Format ===== header = workbook.add_format({ 'bold': True, 'align': 'center', - 'valign': 'middle', 'bg_color': '#007bff', 'font_color': 'white', 'border': 1 }) - cell = workbook.add_format({ - 'border': 1, - 'align': 'left', - 'valign': 'middle' - }) + cell = workbook.add_format({'border': 1}) - # Write headers for col_num, col_name in enumerate(df.columns): worksheet.write(2, col_num, col_name, header) - max_len = max(df[col_name].astype(str).map(len).max(), len(col_name)) + 2 - worksheet.set_column(col_num, col_num, max_len) + worksheet.set_column(col_num, col_num, 25) - # Write data rows - for row in range(3, len(df) + 3): + for row in range(len(df)): for col in range(len(df.columns)): - worksheet.write(row, col, df.iloc[row - 3, col], cell) + worksheet.write(row + 3, col, df.iloc[row, col], cell) worksheet.freeze_panes(3, 1) @@ -219,3 +373,4 @@ class DocumentHandler: finally: connection.close() + diff --git a/AppCode/__pycache__/AOHandler.cpython-311.pyc b/AppCode/__pycache__/AOHandler.cpython-311.pyc deleted file mode 100644 index f37200f..0000000 Binary files a/AppCode/__pycache__/AOHandler.cpython-311.pyc and /dev/null differ diff --git a/AppCode/__pycache__/AOHandler.cpython-312.pyc b/AppCode/__pycache__/AOHandler.cpython-312.pyc deleted file mode 100644 index ef05e4a..0000000 Binary files a/AppCode/__pycache__/AOHandler.cpython-312.pyc and /dev/null differ diff --git a/AppCode/__pycache__/AOHandler.cpython-313.pyc b/AppCode/__pycache__/AOHandler.cpython-313.pyc deleted file mode 100644 index 3f90d31..0000000 Binary files a/AppCode/__pycache__/AOHandler.cpython-313.pyc and /dev/null differ diff --git a/AppCode/__pycache__/CITHandler.cpython-311.pyc b/AppCode/__pycache__/CITHandler.cpython-311.pyc deleted file mode 100644 index 86c8608..0000000 Binary files a/AppCode/__pycache__/CITHandler.cpython-311.pyc and /dev/null differ diff --git a/AppCode/__pycache__/CITHandler.cpython-312.pyc b/AppCode/__pycache__/CITHandler.cpython-312.pyc deleted file mode 100644 index aa7ce45..0000000 Binary files a/AppCode/__pycache__/CITHandler.cpython-312.pyc and /dev/null differ diff --git a/AppCode/__pycache__/CITHandler.cpython-313.pyc b/AppCode/__pycache__/CITHandler.cpython-313.pyc deleted file mode 100644 index 3567079..0000000 Binary files a/AppCode/__pycache__/CITHandler.cpython-313.pyc and /dev/null differ diff --git a/AppCode/__pycache__/Config.cpython-311.pyc b/AppCode/__pycache__/Config.cpython-311.pyc deleted file mode 100644 index 7ac6d23..0000000 Binary files a/AppCode/__pycache__/Config.cpython-311.pyc and /dev/null differ diff --git a/AppCode/__pycache__/Config.cpython-312.pyc b/AppCode/__pycache__/Config.cpython-312.pyc deleted file mode 100644 index 0a9a5fc..0000000 Binary files a/AppCode/__pycache__/Config.cpython-312.pyc and /dev/null differ diff --git a/AppCode/__pycache__/Config.cpython-313.pyc b/AppCode/__pycache__/Config.cpython-313.pyc deleted file mode 100644 index c2eec6e..0000000 Binary files a/AppCode/__pycache__/Config.cpython-313.pyc and /dev/null differ diff --git a/AppCode/__pycache__/DocumentHandler.cpython-311.pyc b/AppCode/__pycache__/DocumentHandler.cpython-311.pyc deleted file mode 100644 index b68a4c0..0000000 Binary files a/AppCode/__pycache__/DocumentHandler.cpython-311.pyc and /dev/null differ diff --git a/AppCode/__pycache__/DocumentHandler.cpython-312.pyc b/AppCode/__pycache__/DocumentHandler.cpython-312.pyc deleted file mode 100644 index ab89779..0000000 Binary files a/AppCode/__pycache__/DocumentHandler.cpython-312.pyc and /dev/null differ diff --git a/AppCode/__pycache__/DocumentHandler.cpython-313.pyc b/AppCode/__pycache__/DocumentHandler.cpython-313.pyc deleted file mode 100644 index 7e86f34..0000000 Binary files a/AppCode/__pycache__/DocumentHandler.cpython-313.pyc and /dev/null differ diff --git a/AppCode/__pycache__/FileHandler.cpython-311.pyc b/AppCode/__pycache__/FileHandler.cpython-311.pyc deleted file mode 100644 index 1a058aa..0000000 Binary files a/AppCode/__pycache__/FileHandler.cpython-311.pyc and /dev/null differ diff --git a/AppCode/__pycache__/FileHandler.cpython-312.pyc b/AppCode/__pycache__/FileHandler.cpython-312.pyc deleted file mode 100644 index 095e62d..0000000 Binary files a/AppCode/__pycache__/FileHandler.cpython-312.pyc and /dev/null differ diff --git a/AppCode/__pycache__/FileHandler.cpython-313.pyc b/AppCode/__pycache__/FileHandler.cpython-313.pyc deleted file mode 100644 index de33b2b..0000000 Binary files a/AppCode/__pycache__/FileHandler.cpython-313.pyc and /dev/null differ diff --git a/AppCode/__pycache__/ITATHandler.cpython-311.pyc b/AppCode/__pycache__/ITATHandler.cpython-311.pyc deleted file mode 100644 index 14a3182..0000000 Binary files a/AppCode/__pycache__/ITATHandler.cpython-311.pyc and /dev/null differ diff --git a/AppCode/__pycache__/ITATHandler.cpython-312.pyc b/AppCode/__pycache__/ITATHandler.cpython-312.pyc deleted file mode 100644 index e750cde..0000000 Binary files a/AppCode/__pycache__/ITATHandler.cpython-312.pyc and /dev/null differ diff --git a/AppCode/__pycache__/ITATHandler.cpython-313.pyc b/AppCode/__pycache__/ITATHandler.cpython-313.pyc deleted file mode 100644 index 9901af2..0000000 Binary files a/AppCode/__pycache__/ITATHandler.cpython-313.pyc and /dev/null differ diff --git a/AppCode/__pycache__/ITRHandler.cpython-311.pyc b/AppCode/__pycache__/ITRHandler.cpython-311.pyc deleted file mode 100644 index 3233e6c..0000000 Binary files a/AppCode/__pycache__/ITRHandler.cpython-311.pyc and /dev/null differ diff --git a/AppCode/__pycache__/ITRHandler.cpython-312.pyc b/AppCode/__pycache__/ITRHandler.cpython-312.pyc deleted file mode 100644 index 43f711d..0000000 Binary files a/AppCode/__pycache__/ITRHandler.cpython-312.pyc and /dev/null differ diff --git a/AppCode/__pycache__/ITRHandler.cpython-313.pyc b/AppCode/__pycache__/ITRHandler.cpython-313.pyc deleted file mode 100644 index 351f0f8..0000000 Binary files a/AppCode/__pycache__/ITRHandler.cpython-313.pyc and /dev/null differ diff --git a/AppCode/__pycache__/LoginAuth.cpython-311.pyc b/AppCode/__pycache__/LoginAuth.cpython-311.pyc deleted file mode 100644 index 9c2d837..0000000 Binary files a/AppCode/__pycache__/LoginAuth.cpython-311.pyc and /dev/null differ diff --git a/AppCode/__pycache__/LoginAuth.cpython-312.pyc b/AppCode/__pycache__/LoginAuth.cpython-312.pyc deleted file mode 100644 index cfc6d1f..0000000 Binary files a/AppCode/__pycache__/LoginAuth.cpython-312.pyc and /dev/null differ diff --git a/AppCode/__pycache__/LoginAuth.cpython-313.pyc b/AppCode/__pycache__/LoginAuth.cpython-313.pyc deleted file mode 100644 index aab7396..0000000 Binary files a/AppCode/__pycache__/LoginAuth.cpython-313.pyc and /dev/null differ diff --git a/AppCode/__pycache__/YearGet.cpython-311.pyc b/AppCode/__pycache__/YearGet.cpython-311.pyc deleted file mode 100644 index b264802..0000000 Binary files a/AppCode/__pycache__/YearGet.cpython-311.pyc and /dev/null differ diff --git a/AppCode/__pycache__/YearGet.cpython-312.pyc b/AppCode/__pycache__/YearGet.cpython-312.pyc deleted file mode 100644 index 4f57296..0000000 Binary files a/AppCode/__pycache__/YearGet.cpython-312.pyc and /dev/null differ diff --git a/AppCode/__pycache__/YearGet.cpython-313.pyc b/AppCode/__pycache__/YearGet.cpython-313.pyc deleted file mode 100644 index 4f6509e..0000000 Binary files a/AppCode/__pycache__/YearGet.cpython-313.pyc and /dev/null differ diff --git a/main.py b/main.py index 57459f8..e39b29b 100644 --- a/main.py +++ b/main.py @@ -443,6 +443,17 @@ def summary_report(): docHandler = DocumentHandler() return docHandler.Summary_report(request=request) +@app.route('/summary/download', methods=['GET']) +@auth.login_required +def download_summary(): + year_raw = request.args.get('year') + if not year_raw: + return "Year parameter is required", 400 + + docHandler = DocumentHandler() + # reuse your existing Summary_report method + return docHandler.Summary_report(request=request) + # check year in table existe or not by using ajax calling. # @app.route('/check_year', methods=['POST']) @@ -513,6 +524,12 @@ def save_mat_row(): finally: mat.close() +@app.route("/summary/preview") +def summary_preview_route(): + handler = DocumentHandler() + return handler.Summary_preview(request) + + # save mat credit bulk data # @app.route("/save_mat_all", methods=["POST"]) # @auth.login_required diff --git a/static/css/summary.css b/static/css/summary.css index 1888f4c..d83932b 100644 --- a/static/css/summary.css +++ b/static/css/summary.css @@ -1,153 +1,278 @@ -/* ================= GLOBAL FORM ELEMENTS ================= */ +/* ================= PAGE WRAPPER ================= */ +.main { + margin-left: 260px; + width: calc(100% - 260px); + margin-top: 80px; + padding: 20px; + transition: all 0.3s ease; +} + +/* ================= CONTAINER ================= */ +.container { + width: 100%; + max-width: none; + margin: 0; + padding: 25px 30px; + background: #ffffff; + border-radius: 12px; + box-shadow: 0 8px 22px rgba(0, 0, 0, 0.08); +} + +/* ================= PAGE TITLE ================= */ +.container h2 { + text-align: center; + color: #007bff; + font-size: 22px; + font-weight: 700; + margin-bottom: 20px; +} + +h3 { + text-align: center; + margin-top: 10px; +} + +/* ================= FORM ================= */ +form { + width: 100%; +} + form label { - display: block; - margin-top: 10px; - font-weight: bold; - color: #333; + display: block; + margin-top: 10px; + font-weight: 600; + color: #333; } -select { - width: 100%; - max-width: 300px; - /* restrict width on desktop/laptop */ - padding: 10px 12px; - border: 1px solid #ccc; - border-radius: 6px; - margin-top: 6px; - font-size: 15px; - background-color: white; - cursor: pointer; - transition: 0.2s; +/* ================= INPUTS ================= */ +form input, +form select { + width: 100%; + padding: 10px 12px; + margin-top: 6px; + border: 1px solid #ccc; + border-radius: 6px; + background-color: #f8f9ff; + font-size: 14px; } -select:focus { - border-color: #007bff; - box-shadow: 0 0 5px rgba(0, 123, 255, 0.5); - outline: none; +form input:focus, +form select:focus { + outline: none; + border-color: #007bff; + box-shadow: 0 0 6px rgba(0, 123, 255, 0.35); +} + +/* ================= AUTO FIELDS ================= */ +.auto { + background-color: #d5edd7; + font-weight: 600; +} + +/* ================= FORM GROUP ================= */ +.form-group { + margin-bottom: 16px; + display: flex; + flex-direction: column; + font-weight: 600; +} + +/* Inline two columns */ +.form-group.inline-2 { + flex-direction: row; + gap: 16px; +} + +.form-group.inline-2 > div { + flex: 1; } /* ================= BUTTONS ================= */ button { - margin-top: 20px; - padding: 10px 18px; - background-color: #007bff; - color: white; - border: none; - cursor: pointer; - border-radius: 6px; - font-size: 15px; - font-weight: 600; - transition: 0.3s; + display: block; + width: 60%; + margin: 25px auto 0; + padding: 12px 20px; + background-color: #28a745; + color: #fff; + border: none; + border-radius: 6px; + font-size: 15px; + font-weight: 600; + cursor: pointer; + transition: 0.3s; } button:hover { - background-color: #0069d9; -} - -/* ================= MAIN CONTAINER ================= */ -.main { - margin-left: 260px; - /* sidebar width if exists */ - padding: 70px 30px 40px 30px; - /* top padding for navbar */ - margin-top: 50px; - /* extra top spacing */ - width: auto; - transition: 0.3s; -} - -.container { - max-width: 800px; - margin: 0 auto; - background: #ffffff; - padding: 35px 40px; - border-radius: 12px; - box-shadow: 0 8px 22px rgba(0, 0, 0, 0.08); + background-color: #218838; + box-shadow: 0 4px 10px rgba(40, 167, 69, 0.35); } /* ================= BACK BUTTON ================= */ .back-btn { - display: inline-block; - margin-top: 20px; - padding: 10px 18px; - background-color: #007bff; - color: white; - font-size: 15px; - font-weight: 600; - border-radius: 6px; - text-decoration: none; - transition: 0.3s; + display: inline-block; + margin-top: 20px; + padding: 10px 18px; + background-color: #007bff; + color: #fff; + font-size: 15px; + font-weight: 600; + border-radius: 6px; + text-decoration: none; + transition: 0.3s; } .back-btn:hover { - background-color: #006ae6; + background-color: #006ae6; } -/* ================= MESSAGES ================= */ +/* ================= STICKY FILTER BAR ================= */ +.head { + position: sticky; + top: 60px; + background: #fff; + z-index: 1000; + padding: 15px 0; + border-bottom: 1px solid #ccc; +} + +/* ================= SELECT + DOWNLOAD ================= */ +.select-download-wrapper { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 10px; +} + +.select-download-wrapper select { + max-width: 300px; +} + +select { + width: 100%; + max-width: 300px; + padding: 10px 12px; + border: 1px solid #ccc; + border-radius: 6px; + margin-top: 6px; + font-size: 15px; + background-color: white; + cursor: pointer; + transition: 0.2s; +} + +select:focus { + border-color: #007bff; + box-shadow: 0 0 5px rgba(0,123,255,0.5); + outline: none; +} + +/* ================= DOWNLOAD BUTTON ================= */ +#downloadBtn { + display: none; + padding: 10px 20px; + font-size: 16px; + background-color: #28a745; + color: #fff; + text-decoration: none; + border-radius: 4px; + white-space: nowrap; + transition: 0.3s; +} + +#downloadBtn:hover { + background-color: #218838; +} + +/* ================= TABLE PREVIEW ================= */ +#previewContent { + max-height: 60vh; + overflow-y: auto; + overflow-x: auto; + width: 100%; + margin-top: 15px; +} + +#previewContent table { + width: 100%; + min-width: 600px; + border-collapse: collapse; +} + +#previewContent th, +#previewContent td { + padding: 10px; + border: 1px solid #ccc; + white-space: nowrap; +} + +/* Sticky table header */ +#previewContent th { + position: sticky; + top: 0; + background-color: #007bff; + color: #fff; + z-index: 10; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +/* ================= MESSAGE ================= */ .message { - margin-top: 15px; - color: #555; - font-size: 14px; + margin-top: 15px; + color: #555; + font-size: 14px; } /* ================= RESPONSIVE ================= */ - -/* Tablets (<= 992px) */ @media (max-width: 992px) { - .main { - margin-left: 0; - /* remove sidebar spacing */ - padding: 50px 20px 20px 20px; - } + .main { + margin-left: 0; + width: 100%; + padding: 20px; + } - .container { - padding: 25px 20px; - } - - select { - max-width: 100%; - /* full width */ - } - - button { - width: 100%; - /* full width */ - padding: 12px 0; - } + button { + width: 100%; + } +} + +@media (max-width: 768px) { + .container { + padding: 18px; + } + + .container h2 { + font-size: 18px; + } + + .form-group.inline-2 { + flex-direction: column; + gap: 10px; + } } -/* Mobile (<= 576px) */ @media (max-width: 576px) { - .main { - padding: 40px 15px 15px 15px; - } + .main { + padding: 15px; + margin-top: 70px; + } - .container { - padding: 20px; - } + #previewContent table { + min-width: 500px; + } - h2 { - font-size: 22px; - text-align: center; - } + #previewContent th, + #previewContent td { + font-size: 13px; + padding: 8px; + } +} - select { - font-size: 14px; - padding: 10px; - } - - button { - font-size: 14px; - padding: 12px 0; - } - - .back-btn { - width: 100%; - text-align: center; - padding: 12px 0; - } - - .message { - font-size: 13px; - } -} \ No newline at end of file +@media (max-width: 420px) { + form input, + form select { + font-size: 13px; + padding: 9px; + } +} diff --git a/static/js/summary_preview.js b/static/js/summary_preview.js new file mode 100644 index 0000000..cf1ab60 --- /dev/null +++ b/static/js/summary_preview.js @@ -0,0 +1,55 @@ +document.getElementById("year").addEventListener("change", function () { + const year = this.value; + + const downloadBtn = document.getElementById("downloadBtn"); + const previewDiv = document.getElementById("preview"); + const contentDiv = document.getElementById("previewContent"); + + if (!year) { + downloadBtn.style.display = "none"; + previewDiv.style.display = "none"; + contentDiv.innerHTML = ""; + return; + } + + downloadBtn.href = `/summary/download?year=${year}`; + downloadBtn.style.display = "inline-block"; + + fetch(`/summary/preview?year=${year}`) + .then(res => res.json()) + .then(data => { + let html = `
| Particular | +ITR | +AO | +CIT | +ITAT | +
|---|---|---|---|---|
| ${row.Particular} | +${row.ITR} | +${row.AO} | +${row.CIT} | +${row.ITAT} | +