updated code
This commit is contained in:
Binary file not shown.
BIN
app/Controllers/__pycache__/reports.cpython-314.pyc
Normal file
BIN
app/Controllers/__pycache__/reports.cpython-314.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -104,6 +104,57 @@ def filter_tasks_controller():
|
||||
|
||||
|
||||
|
||||
# def download_filtered_tasks_controller():
|
||||
# district = request.args.get('district')
|
||||
# block = request.args.get('block')
|
||||
# village = request.args.get('village')
|
||||
|
||||
# query = (
|
||||
# db.session.query(Task)
|
||||
# .join(WorkDetail,
|
||||
# Task.village_name == WorkDetail.name_of_village)
|
||||
# .filter(
|
||||
# WorkDetail.district == district,
|
||||
# WorkDetail.block == block,
|
||||
# WorkDetail.name_of_village == village
|
||||
# )
|
||||
# )
|
||||
|
||||
# tasks = query.all()
|
||||
|
||||
# data = []
|
||||
# for task in tasks:
|
||||
# data.append({
|
||||
# "Task Name": task.task_name,
|
||||
# "Unit": task.unit,
|
||||
# "Qty": task.qty,
|
||||
# "Rate": task.rate,
|
||||
# "BOQ Amount": task.boq_amount,
|
||||
# "Prev Billed Qty": task.previous_billed_qty,
|
||||
# "Prev Billing Amount": task.previous_billing_amount,
|
||||
# "RA Bill Qty": task.in_this_ra_bill_qty,
|
||||
# "RA Bill Amount": task.in_this_ra_billing_amount,
|
||||
# "Cum Billed Qty": task.cumulative_billed_qty,
|
||||
# "Cum Billed Amount": task.cumulative_billed_amount,
|
||||
# "Variation Qty": task.variation_qty,
|
||||
# "Variation Amount": task.variation_amount,
|
||||
# })
|
||||
|
||||
# df = pd.DataFrame(data)
|
||||
|
||||
# output = BytesIO()
|
||||
# df.to_excel(output, index=False, engine='openpyxl')
|
||||
# output.seek(0)
|
||||
|
||||
# filename = f"{district}_{block}_{village}_tasks.xlsx"
|
||||
|
||||
# return send_file(
|
||||
# output,
|
||||
# download_name=filename,
|
||||
# as_attachment=True,
|
||||
# mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
# )
|
||||
|
||||
def download_filtered_tasks_controller():
|
||||
district = request.args.get('district')
|
||||
block = request.args.get('block')
|
||||
@@ -140,10 +191,86 @@ def download_filtered_tasks_controller():
|
||||
"Variation Amount": task.variation_amount,
|
||||
})
|
||||
|
||||
df = pd.DataFrame(data)
|
||||
df = pd.DataFrame(data).fillna("")
|
||||
|
||||
output = BytesIO()
|
||||
df.to_excel(output, index=False, engine='openpyxl')
|
||||
|
||||
with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
|
||||
df.to_excel(writer, index=False, sheet_name='Tasks')
|
||||
|
||||
workbook = writer.book
|
||||
worksheet = writer.sheets['Tasks']
|
||||
|
||||
# ================= HEADER =================
|
||||
header_format = workbook.add_format({
|
||||
'bold': True,
|
||||
'text_wrap': True,
|
||||
'valign': 'center',
|
||||
'align': 'center',
|
||||
'bg_color': '#D9E1F2',
|
||||
'border': 1
|
||||
})
|
||||
|
||||
for col_num, value in enumerate(df.columns.values):
|
||||
worksheet.write(0, col_num, value, header_format)
|
||||
|
||||
# ================= FORMATS =================
|
||||
normal_format = workbook.add_format({
|
||||
'text_wrap': True,
|
||||
'valign': 'top',
|
||||
'border': 1
|
||||
})
|
||||
|
||||
bold_format = workbook.add_format({
|
||||
'text_wrap': True,
|
||||
'valign': 'top',
|
||||
'bold': True,
|
||||
'border': 1
|
||||
})
|
||||
|
||||
# ================= COLUMN WIDTH =================
|
||||
for i, col in enumerate(df.columns):
|
||||
|
||||
max_len = (
|
||||
df[col]
|
||||
.astype(str)
|
||||
.map(lambda x: len(str(x)))
|
||||
.max()
|
||||
)
|
||||
|
||||
max_len = max(max_len, len(col)) + 2
|
||||
|
||||
if col == "Task Name":
|
||||
worksheet.set_column(i, i, 50)
|
||||
worksheet.set_row(1, 40)
|
||||
else:
|
||||
worksheet.set_column(i, i, max_len)
|
||||
|
||||
# ================= ROW LOGIC =================
|
||||
unit_col = 1 # Unit is 2nd column
|
||||
|
||||
for row_idx in range(len(df)):
|
||||
|
||||
unit_value = str(df.iloc[row_idx, unit_col]).strip()
|
||||
|
||||
# 🔥 RULE:
|
||||
# numeric → normal
|
||||
# letters OR empty → bold
|
||||
|
||||
is_numeric = unit_value.replace(".", "", 1).isdigit()
|
||||
|
||||
if is_numeric:
|
||||
row_format = normal_format
|
||||
else:
|
||||
row_format = bold_format
|
||||
|
||||
for col_idx in range(len(df.columns)):
|
||||
worksheet.write(row_idx + 1, col_idx, df.iloc[row_idx, col_idx], row_format)
|
||||
|
||||
# ================= FEATURES =================
|
||||
worksheet.freeze_panes(1, 0)
|
||||
worksheet.autofilter(0, 0, len(df), len(df.columns) - 1)
|
||||
|
||||
output.seek(0)
|
||||
|
||||
filename = f"{district}_{block}_{village}_tasks.xlsx"
|
||||
|
||||
268
app/Controllers/reports.py
Normal file
268
app/Controllers/reports.py
Normal file
@@ -0,0 +1,268 @@
|
||||
from flask import Blueprint, request, send_from_directory, redirect, url_for, current_app, flash
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
from app.models import Task
|
||||
|
||||
reports = Blueprint('reports', __name__)
|
||||
|
||||
|
||||
def clean_text(text):
|
||||
if not isinstance(text, str):
|
||||
return ""
|
||||
return text.strip().replace(",", "").replace("(", "").replace(")", "") \
|
||||
.replace(".", "").replace("&", "").replace("\n", "").lower()
|
||||
|
||||
|
||||
def safe_float(value):
|
||||
try:
|
||||
return round(float(value), 2)
|
||||
except (ValueError, TypeError):
|
||||
return 0.0
|
||||
|
||||
|
||||
@reports.route('/report_excel', methods=['GET'])
|
||||
def generate_report():
|
||||
|
||||
main_task_rexp = r'[\\/*?:"<>|]'
|
||||
|
||||
block = request.args.get('block', '')
|
||||
main_task = request.args.get('main_task', '')
|
||||
|
||||
block_clean = clean_text(block)
|
||||
main_task_clean = clean_text(main_task)
|
||||
|
||||
if not block_clean:
|
||||
return "Please select a Block.", 400
|
||||
|
||||
if not main_task_clean:
|
||||
return "Please select a Main Task.", 400
|
||||
|
||||
# ---------------- FETCH DATA ----------------
|
||||
all_tasks = Task.query.filter(Task.block_name == block).all()
|
||||
|
||||
# MAIN TASK
|
||||
# main_task_record = next(
|
||||
# (task for task in all_tasks
|
||||
# if clean_text(task.task_name) == main_task_clean),
|
||||
# None
|
||||
# )
|
||||
|
||||
main_task_records = [
|
||||
task for task in all_tasks
|
||||
# if clean_text(task.task_name) == main_task_clean
|
||||
if main_task_clean in clean_text(task.task_name)
|
||||
]
|
||||
# SUBTASKS (ONLY TRUE CHILDREN)
|
||||
subtasks_query = [
|
||||
task for task in all_tasks
|
||||
if task.parent_task_name
|
||||
and main_task_clean in clean_text(task.parent_task_name)
|
||||
# and clean_text(task.parent_task_name) == main_task_clean
|
||||
]
|
||||
|
||||
# ---------------- VALIDATION ----------------
|
||||
if not subtasks_query and not main_task_records:
|
||||
flash("No Task Data Found", "error")
|
||||
return redirect(url_for('main.generate_report_page'))
|
||||
|
||||
# ---------------- BUILD REPORT ----------------
|
||||
report_data = []
|
||||
|
||||
# CASE 1: SUBTASKS EXIST
|
||||
if subtasks_query:
|
||||
|
||||
for task in subtasks_query:
|
||||
|
||||
boq_amount = safe_float(task.boq_amount)
|
||||
previous_billing_amount = safe_float(task.previous_billing_amount)
|
||||
remaining_amount = boq_amount - previous_billing_amount
|
||||
tender_amount = safe_float(task.qty) * safe_float(task.rate)
|
||||
|
||||
report_data.append([
|
||||
task.id,
|
||||
(task.village_name or "").strip(),
|
||||
(task.task_name or "").strip(),
|
||||
(task.unit or "").strip(),
|
||||
safe_float(task.qty),
|
||||
safe_float(task.rate),
|
||||
tender_amount,
|
||||
safe_float(task.previous_billed_qty),
|
||||
previous_billing_amount,
|
||||
remaining_amount
|
||||
])
|
||||
|
||||
# CASE 2: ONLY MAIN TASK EXISTS
|
||||
# elif main_task_record:
|
||||
|
||||
# task = main_task_record
|
||||
|
||||
# boq_amount = safe_float(task.boq_amount)
|
||||
# previous_billing_amount = safe_float(task.previous_billing_amount)
|
||||
# remaining_amount = boq_amount - previous_billing_amount
|
||||
# tender_amount = safe_float(task.qty) * safe_float(task.rate)
|
||||
|
||||
# report_data.append([
|
||||
# task.id,
|
||||
# (task.village_name or "").strip(),
|
||||
# (task.task_name or "").strip(),
|
||||
# safe_float(task.qty),
|
||||
# safe_float(task.rate),
|
||||
# tender_amount,
|
||||
# safe_float(task.previous_billed_qty),
|
||||
# previous_billing_amount,
|
||||
# remaining_amount
|
||||
# ])
|
||||
elif main_task_records:
|
||||
|
||||
for task in main_task_records:
|
||||
|
||||
boq_amount = safe_float(task.boq_amount)
|
||||
previous_billing_amount = safe_float(task.previous_billing_amount)
|
||||
remaining_amount = boq_amount - previous_billing_amount
|
||||
tender_amount = safe_float(task.qty) * safe_float(task.rate)
|
||||
|
||||
report_data.append([
|
||||
task.id,
|
||||
(task.village_name or "").strip(),
|
||||
(task.task_name or "").strip(),
|
||||
(task.unit or "").strip(),
|
||||
safe_float(task.qty),
|
||||
safe_float(task.rate),
|
||||
tender_amount,
|
||||
safe_float(task.previous_billed_qty),
|
||||
previous_billing_amount,
|
||||
remaining_amount
|
||||
])
|
||||
# ---------------- FILE NAME ----------------
|
||||
sanitized_main_task = re.sub(main_task_rexp, "", main_task)
|
||||
|
||||
if len(sanitized_main_task) > 30:
|
||||
sanitized_main_task = sanitized_main_task[:30]
|
||||
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
|
||||
file_name = f"{sanitized_main_task}_{timestamp}.xlsx"
|
||||
file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], file_name)
|
||||
|
||||
# ---------------- EXCEL ----------------
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "Report"
|
||||
|
||||
thin_border = Border(
|
||||
left=Side(style="thin"),
|
||||
right=Side(style="thin"),
|
||||
top=Side(style="thin"),
|
||||
bottom=Side(style="thin")
|
||||
)
|
||||
|
||||
header_fill = PatternFill(start_color="FFC000", end_color="FFC000", fill_type="solid")
|
||||
|
||||
# ---------------------------------------------------
|
||||
# REPORT TITLE
|
||||
# ---------------------------------------------------
|
||||
|
||||
ws.merge_cells("A1:J1")
|
||||
title_cell = ws["A1"]
|
||||
|
||||
title_cell.value = "MAIN TASK REPORT"
|
||||
title_cell.font = Font(bold=True, size=14)
|
||||
title_cell.alignment = Alignment(horizontal="center")
|
||||
title_cell.fill = header_fill
|
||||
title_cell.border = thin_border
|
||||
|
||||
|
||||
# ---------------------------------------------------
|
||||
# DETAIL ROWS
|
||||
# ---------------------------------------------------
|
||||
|
||||
ws["A2"] = "District"
|
||||
ws["B2"] = request.args.get("district", "")
|
||||
|
||||
ws["A3"] = "Block"
|
||||
ws["B3"] = block
|
||||
|
||||
ws["A4"] = "Main Task"
|
||||
ws["B4"] = main_task
|
||||
|
||||
|
||||
# Style details rows
|
||||
for r in range(2,5):
|
||||
ws[f"A{r}"].font = Font(bold=True)
|
||||
ws[f"A{r}"].fill = header_fill
|
||||
ws[f"A{r}"].border = thin_border
|
||||
ws[f"B{r}"].border = thin_border
|
||||
|
||||
|
||||
# ---------------------------------------------------
|
||||
# TABLE HEADER STARTS ROW 6
|
||||
# ---------------------------------------------------
|
||||
|
||||
start_row = 6
|
||||
|
||||
headers = [
|
||||
"Task ID",
|
||||
"Village",
|
||||
"Task Name",
|
||||
"Unit",
|
||||
"Tender Qty",
|
||||
"Tender Rate",
|
||||
"Tender Amount",
|
||||
"Previous Bill Qty",
|
||||
"Previous Bill Amount",
|
||||
"Remaining Amount"
|
||||
]
|
||||
|
||||
|
||||
for col_num, value in enumerate(headers,1):
|
||||
cell = ws.cell(row=start_row, column=col_num)
|
||||
cell.value = value
|
||||
cell.font = Font(bold=True)
|
||||
cell.alignment = Alignment(horizontal="center")
|
||||
cell.fill = header_fill
|
||||
cell.border = thin_border
|
||||
|
||||
|
||||
# ---------------------------------------------------
|
||||
# DATA ROWS START AFTER HEADER
|
||||
# ---------------------------------------------------
|
||||
|
||||
data_row = start_row + 1
|
||||
|
||||
for row_data in report_data:
|
||||
for col_num, value in enumerate(row_data,1):
|
||||
cell = ws.cell(
|
||||
row=data_row,
|
||||
column=col_num,
|
||||
value=value
|
||||
)
|
||||
cell.border = thin_border
|
||||
|
||||
data_row += 1
|
||||
|
||||
|
||||
# Freeze header while scrolling
|
||||
ws.freeze_panes = "A7"
|
||||
|
||||
|
||||
# Column Width
|
||||
for i in range(1, len(headers)+1):
|
||||
ws.column_dimensions[
|
||||
ws.cell(row=start_row,column=i).column_letter
|
||||
].width = 20
|
||||
|
||||
wb.save(file_path)
|
||||
|
||||
return redirect(url_for('reports.download_report', filename=file_name))
|
||||
|
||||
|
||||
@reports.route('/download/<filename>')
|
||||
def download_report(filename):
|
||||
return send_from_directory(
|
||||
current_app.config['UPLOAD_FOLDER'],
|
||||
filename,
|
||||
as_attachment=True
|
||||
)
|
||||
@@ -29,6 +29,54 @@ def recalc_task(task):
|
||||
|
||||
|
||||
|
||||
# def update_tasks_controller():
|
||||
# try:
|
||||
# updates = request.get_json()
|
||||
# update_count = 0
|
||||
|
||||
# formula_fields = [
|
||||
# "previous_billing_amount",
|
||||
# "in_this_ra_billing_amount",
|
||||
# "cumulative_billed_qty",
|
||||
# "cumulative_billed_amount",
|
||||
# "variation_qty",
|
||||
# "variation_amount"
|
||||
# ]
|
||||
# for key, new_value in updates.items():
|
||||
# if '_' not in key:
|
||||
# continue
|
||||
# field_name, task_id_str = key.rsplit('_', 1)
|
||||
# if not task_id_str.isdigit():
|
||||
# continue
|
||||
# task = Task.query.get(int(task_id_str))
|
||||
# if task:
|
||||
# if field_name in formula_fields:
|
||||
# continue
|
||||
# current_value = getattr(task, field_name, None)
|
||||
# if str(current_value) != str(new_value):
|
||||
# setattr(task, field_name, new_value)
|
||||
# recalc_task(task)
|
||||
# update_count += 1
|
||||
# log_activity(
|
||||
# current_user.username,
|
||||
# "Task Update",
|
||||
# f"Task ID {task.id} - {field_name} changed to {new_value}"
|
||||
# )
|
||||
# if update_count > 0:
|
||||
# db.session.commit()
|
||||
# log_activity(
|
||||
# current_user.username,
|
||||
# "Database Commit",
|
||||
# f"{update_count} task field(s) updated"
|
||||
# )
|
||||
# return jsonify({'message': f'count: {update_count} field(s) updated.'})
|
||||
# return jsonify({'message': 'No fields were updated.'})
|
||||
# except Exception as e:
|
||||
# log_activity(current_user.username, "Error", str(e))
|
||||
# return jsonify({'error': 'Update failed'}), 500
|
||||
|
||||
|
||||
|
||||
def update_tasks_controller():
|
||||
try:
|
||||
updates = request.get_json()
|
||||
@@ -42,39 +90,72 @@ def update_tasks_controller():
|
||||
"variation_qty",
|
||||
"variation_amount"
|
||||
]
|
||||
|
||||
numeric_fields = [
|
||||
"qty","rate","boq_amount",
|
||||
"previous_billed_qty",
|
||||
"in_this_ra_bill_qty"
|
||||
]
|
||||
|
||||
for key, new_value in updates.items():
|
||||
if '_' not in key:
|
||||
continue
|
||||
|
||||
field_name, task_id_str = key.rsplit('_', 1)
|
||||
|
||||
if not task_id_str.isdigit():
|
||||
continue
|
||||
|
||||
task = Task.query.get(int(task_id_str))
|
||||
|
||||
if task:
|
||||
|
||||
# Skip formula fields
|
||||
if field_name in formula_fields:
|
||||
continue
|
||||
|
||||
current_value = getattr(task, field_name, None)
|
||||
|
||||
# Convert numeric fields
|
||||
if field_name in numeric_fields:
|
||||
try:
|
||||
new_value = float(new_value) if new_value != "" else 0
|
||||
except:
|
||||
new_value = 0
|
||||
|
||||
# Update only if changed
|
||||
if str(current_value) != str(new_value):
|
||||
setattr(task, field_name, new_value)
|
||||
|
||||
# Recalculate formulas
|
||||
recalc_task(task)
|
||||
|
||||
update_count += 1
|
||||
|
||||
log_activity(
|
||||
current_user.username,
|
||||
"Task Update",
|
||||
f"Task ID {task.id} - {field_name} changed to {new_value}"
|
||||
)
|
||||
|
||||
if update_count > 0:
|
||||
db.session.commit()
|
||||
|
||||
log_activity(
|
||||
current_user.username,
|
||||
"Database Commit",
|
||||
f"{update_count} task field(s) updated"
|
||||
)
|
||||
|
||||
return jsonify({'message': f'count: {update_count} field(s) updated.'})
|
||||
|
||||
return jsonify({'message': 'No fields were updated.'})
|
||||
|
||||
except Exception as e:
|
||||
log_activity(current_user.username, "Error", str(e))
|
||||
return jsonify({'error': 'Update failed'}), 500
|
||||
|
||||
|
||||
def display_tasks_controller():
|
||||
work_details = WorkDetail.query.order_by(
|
||||
WorkDetail.uploaded_at.desc()
|
||||
|
||||
@@ -6,15 +6,16 @@ from app import db
|
||||
from app.models import Task, WorkDetail
|
||||
from app.service.logger import log_activity
|
||||
|
||||
# keep helper inside controller
|
||||
|
||||
def to_2_decimal(value):
|
||||
try:
|
||||
if value is None or value == "":
|
||||
return None
|
||||
return round(float(value), 2)
|
||||
return round(float(str(value).replace(",", "")), 2)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def upload_controller():
|
||||
if 'file' not in request.files:
|
||||
return "No file part"
|
||||
@@ -29,7 +30,11 @@ def upload_controller():
|
||||
|
||||
log_activity(current_user.username, "File Upload", f"Uploaded file: {file.filename}")
|
||||
|
||||
# =========================
|
||||
# READ WORK DETAILS (TOP PART)
|
||||
# =========================
|
||||
work_details_data = pd.read_excel(filepath, nrows=11, header=None, dtype=str)
|
||||
|
||||
work_details_dict = {
|
||||
"name_of_work": work_details_data.iloc[0, 1],
|
||||
"cover_agreement_no": work_details_data.iloc[1, 1],
|
||||
@@ -43,11 +48,43 @@ def upload_controller():
|
||||
"measurement_book": work_details_data.iloc[9, 1],
|
||||
"district": work_details_data.iloc[10, 1]
|
||||
}
|
||||
|
||||
work_details_dict = {k: (None if pd.isna(v) else v) for k, v in work_details_dict.items()}
|
||||
|
||||
# =========================
|
||||
# CHECK EXISTING WORKDETAIL (FOR OVERWRITE)
|
||||
# =========================
|
||||
existing_work = WorkDetail.query.filter_by(
|
||||
scheme_id=work_details_dict["scheme_id"],
|
||||
date_of_billing=work_details_dict["date_of_billing"],
|
||||
name_of_village=work_details_dict["name_of_village"]
|
||||
).first()
|
||||
|
||||
if existing_work:
|
||||
# 🔥 DELETE OLD TASKS
|
||||
Task.query.filter_by(work_detail_id=existing_work.id).delete()
|
||||
|
||||
# UPDATE WORK DETAIL
|
||||
for key, value in work_details_dict.items():
|
||||
setattr(existing_work, key, value)
|
||||
|
||||
work_detail = existing_work
|
||||
|
||||
log_activity(current_user.username, "Overwrite", "Old data deleted and replaced")
|
||||
|
||||
else:
|
||||
# CREATE NEW WORK DETAIL
|
||||
work_detail = WorkDetail(**work_details_dict)
|
||||
db.session.add(work_detail)
|
||||
|
||||
db.session.flush() # 🔥 get work_detail.id
|
||||
|
||||
# =========================
|
||||
# READ MAIN DATA
|
||||
# =========================
|
||||
data = pd.read_excel(filepath, skiprows=10)
|
||||
data = data.astype(object).where(pd.notna(data), None)
|
||||
|
||||
expected_columns = [
|
||||
"serial_number", "task_name", "unit", "qty", "rate", "boq_amount",
|
||||
"previous_billed_qty", "previous_billing_amount",
|
||||
@@ -55,22 +92,48 @@ def upload_controller():
|
||||
"cumulative_billed_qty", "cumulative_billed_amount",
|
||||
"variation_qty", "variation_amount", "remark"
|
||||
]
|
||||
if data.shape[1] == len(expected_columns):
|
||||
data.columns = expected_columns
|
||||
|
||||
# Validate excel columns
|
||||
if data.shape[1] < len(expected_columns):
|
||||
|
||||
missing_cols = expected_columns[data.shape[1]:]
|
||||
|
||||
return (
|
||||
"Excel is missing required columns: "
|
||||
+ ", ".join(missing_cols),
|
||||
400
|
||||
)
|
||||
|
||||
elif data.shape[1] > len(expected_columns):
|
||||
|
||||
return (
|
||||
"Invalid Excel format. Extra unexpected columns found.",
|
||||
400
|
||||
)
|
||||
|
||||
else:
|
||||
data.columns = expected_columns[:data.shape[1]]
|
||||
data.columns = expected_columns
|
||||
# =========================
|
||||
# INSERT DATA (FRESH)
|
||||
# =========================
|
||||
tasks_to_add = []
|
||||
|
||||
current_main_task_serial = None
|
||||
current_main_task_name = None
|
||||
for _, row in data.iterrows():
|
||||
|
||||
for index, row in data.iterrows():
|
||||
task_name = str(row["task_name"]) if row["task_name"] else ""
|
||||
serial_number = str(row["serial_number"]) if row["serial_number"] else None
|
||||
|
||||
if serial_number:
|
||||
current_main_task_serial = serial_number
|
||||
current_main_task_name = task_name
|
||||
parent_id = None
|
||||
else:
|
||||
parent_id = current_main_task_serial
|
||||
|
||||
task = Task(
|
||||
work_detail_id=work_detail.id,
|
||||
district=work_details_dict.get("district"),
|
||||
block_name=work_details_dict["block"],
|
||||
village_name=work_details_dict["name_of_village"],
|
||||
@@ -90,17 +153,20 @@ def upload_controller():
|
||||
variation_amount=to_2_decimal(row["variation_amount"]),
|
||||
parent_id=parent_id,
|
||||
parent_task_name=current_main_task_name if not serial_number else None,
|
||||
remark=row["remark"]
|
||||
# remark=row["remark"],
|
||||
remark=None if pd.isna(row["remark"]) else str(row["remark"]).strip(),
|
||||
row_index=index # 🔥 optional but useful
|
||||
)
|
||||
db.session.add(task)
|
||||
|
||||
tasks_to_add.append(task)
|
||||
|
||||
db.session.bulk_save_objects(tasks_to_add) # 🔥 FAST INSERT
|
||||
db.session.commit()
|
||||
|
||||
log_activity(
|
||||
current_user.username,
|
||||
"Database Insert",
|
||||
f"Inserted work details and tasks from {file.filename}"
|
||||
f"Inserted {len(tasks_to_add)} rows from {file.filename}"
|
||||
)
|
||||
|
||||
return redirect(url_for('main.display_tasks'))
|
||||
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ def create_app():
|
||||
# ---------------------------
|
||||
try:
|
||||
from app.routes.main import main as main_bp
|
||||
from app.routes.reports import reports as reports_bp
|
||||
from app.Controllers.reports import reports as reports_bp
|
||||
app.register_blueprint(main_bp)
|
||||
app.register_blueprint(reports_bp)
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
6209
app/activity.log
6209
app/activity.log
File diff suppressed because it is too large
Load Diff
108
app/models.py
108
app/models.py
@@ -4,6 +4,10 @@ from datetime import datetime
|
||||
|
||||
String_size = db.String(255)
|
||||
|
||||
|
||||
# ==========================
|
||||
# USER MODEL
|
||||
# ==========================
|
||||
class User(UserMixin, db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(String_size, unique=True, nullable=False)
|
||||
@@ -22,37 +26,15 @@ class User(UserMixin, db.Model):
|
||||
def __repr__(self):
|
||||
return f'<User {self.username}>'
|
||||
|
||||
# ==========================
|
||||
# DB models below
|
||||
# ==========================
|
||||
|
||||
class Task(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
district = db.Column(String_size)
|
||||
block_name = db.Column(String_size)
|
||||
village_name = db.Column(String_size)
|
||||
serial_number = db.Column(String_size)
|
||||
parent_id = db.Column(String_size, nullable=True)
|
||||
parent_task_name = db.Column(db.Text)
|
||||
task_name = db.Column(db.Text)
|
||||
unit = db.Column(String_size)
|
||||
qty = db.Column(String_size)
|
||||
rate = db.Column(String_size)
|
||||
boq_amount = db.Column(String_size)
|
||||
previous_billed_qty = db.Column(String_size)
|
||||
previous_billing_amount = db.Column(String_size)
|
||||
remaining_amount = db.Column(String_size)
|
||||
in_this_ra_bill_qty = db.Column(String_size)
|
||||
in_this_ra_billing_amount = db.Column(String_size)
|
||||
cumulative_billed_qty = db.Column(String_size)
|
||||
cumulative_billed_amount = db.Column(String_size)
|
||||
variation_qty = db.Column(String_size)
|
||||
variation_amount = db.Column(String_size)
|
||||
remark = db.Column(String_size, nullable=True)
|
||||
uploaded_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
# ==========================
|
||||
# WORK DETAIL (PARENT)
|
||||
# ==========================
|
||||
class WorkDetail(db.Model):
|
||||
__tablename__ = 'work_detail'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
|
||||
name_of_work = db.Column(String_size)
|
||||
cover_agreement_no = db.Column(String_size)
|
||||
name_of_contractor = db.Column(String_size)
|
||||
@@ -63,12 +45,74 @@ class WorkDetail(db.Model):
|
||||
scheme_id = db.Column(String_size)
|
||||
measurement_book = db.Column(String_size)
|
||||
date_of_billing = db.Column(String_size)
|
||||
uploaded_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
district = db.Column(db.String(255))
|
||||
district = db.Column(String_size)
|
||||
|
||||
uploaded_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
# 🔥 RELATIONSHIP (VERY IMPORTANT)
|
||||
tasks = db.relationship(
|
||||
'Task',
|
||||
backref='work_detail',
|
||||
cascade="all, delete-orphan"
|
||||
)
|
||||
|
||||
|
||||
# ==========================
|
||||
# TASK (CHILD TABLE)
|
||||
# ==========================
|
||||
class Task(db.Model):
|
||||
__tablename__ = 'task'
|
||||
|
||||
class ActivityLog(db.Model): # OUTSIDE WorkDetail
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user = db.Column(db.String(100)) # can link with User model later
|
||||
|
||||
# 🔥 ADD THIS (FIX YOUR ERROR)
|
||||
work_detail_id = db.Column(db.Integer, db.ForeignKey('work_detail.id'))
|
||||
|
||||
district = db.Column(String_size)
|
||||
block_name = db.Column(String_size)
|
||||
village_name = db.Column(String_size)
|
||||
|
||||
serial_number = db.Column(String_size)
|
||||
|
||||
parent_id = db.Column(String_size, nullable=True)
|
||||
parent_task_name = db.Column(db.Text)
|
||||
|
||||
task_name = db.Column(db.Text)
|
||||
|
||||
unit = db.Column(String_size)
|
||||
|
||||
# 👉 keep as string (your current structure)
|
||||
qty = db.Column(String_size)
|
||||
rate = db.Column(String_size)
|
||||
boq_amount = db.Column(String_size)
|
||||
|
||||
previous_billed_qty = db.Column(String_size)
|
||||
previous_billing_amount = db.Column(String_size)
|
||||
remaining_amount = db.Column(String_size)
|
||||
|
||||
in_this_ra_bill_qty = db.Column(String_size)
|
||||
in_this_ra_billing_amount = db.Column(String_size)
|
||||
|
||||
cumulative_billed_qty = db.Column(String_size)
|
||||
cumulative_billed_amount = db.Column(String_size)
|
||||
|
||||
variation_qty = db.Column(String_size)
|
||||
variation_amount = db.Column(String_size)
|
||||
|
||||
remark = db.Column(String_size, nullable=True)
|
||||
|
||||
uploaded_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
# 🔥 OPTIONAL BUT VERY USEFUL
|
||||
row_index = db.Column(db.Integer)
|
||||
|
||||
|
||||
# ==========================
|
||||
# ACTIVITY LOG
|
||||
# ==========================
|
||||
class ActivityLog(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user = db.Column(db.String(100))
|
||||
action = db.Column(db.String(255))
|
||||
details = db.Column(db.Text)
|
||||
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
Binary file not shown.
@@ -1,210 +0,0 @@
|
||||
from flask import Blueprint, request, render_template, send_from_directory, redirect, url_for, current_app, flash
|
||||
from app.__init__ import db
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
from app.models import Task
|
||||
|
||||
reports = Blueprint('reports', __name__)
|
||||
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
def clean_text(text):
|
||||
if not isinstance(text, str):
|
||||
return ""
|
||||
return text.strip().replace(",", "").replace("(", "").replace(")", "")\
|
||||
.replace(".", "").replace("&", "").replace("\n", "").lower()
|
||||
|
||||
@reports.route('/report_excel', methods=['GET'])
|
||||
def generate_report():
|
||||
main_task_rexp = r'[\\/*?:"<>|]'
|
||||
block = request.args.get('block', '')
|
||||
main_task = request.args.get('main_task', '')
|
||||
|
||||
block_clean = clean_text(block)
|
||||
main_task_clean = clean_text(main_task)
|
||||
|
||||
if not block_clean:
|
||||
return "Please select a Block.", 400
|
||||
if not main_task_clean:
|
||||
return "Please select a Main Task.", 400
|
||||
|
||||
print(f"Block selected: {block}")
|
||||
print(f"Main task selected: {main_task}")
|
||||
|
||||
# Filter main task records based on cleaned block and task name
|
||||
main_task_records = [task for task in Task.query.filter_by(block_name=block).all()
|
||||
if clean_text(task.task_name) == main_task_clean]
|
||||
|
||||
print(f"Found {len(main_task_records)} main task records")
|
||||
|
||||
# if not main_task_records:
|
||||
# return f"Main Task '{main_task}' not found in the selected block '{block}'.", 404
|
||||
if not main_task_records:
|
||||
flash("No data found for selected Block and Main Task", "error")
|
||||
# return redirect(url_for('generate_report_page'))
|
||||
return redirect(url_for('main.generate_report_page'))
|
||||
|
||||
report_data = {}
|
||||
|
||||
def safe_float(value):
|
||||
try:
|
||||
return round(float(value), 2)
|
||||
except (ValueError, TypeError):
|
||||
return 0.0
|
||||
|
||||
# Process subtasks for each main task
|
||||
for main_task_record in main_task_records:
|
||||
main_task_serial_number = main_task_record.serial_number
|
||||
|
||||
|
||||
# Get all subtasks under the selected main task (match cleaned names)
|
||||
subtasks_query = [
|
||||
task for task in Task.query.filter(Task.block_name == block).all()
|
||||
if clean_text(task.parent_task_name) == main_task_clean
|
||||
]
|
||||
|
||||
print(f"Found {len(subtasks_query)} subtasks for main task '{main_task}'")
|
||||
|
||||
for task in subtasks_query:
|
||||
key = task.village_name.strip() if task.village_name else ""
|
||||
totalElemList = 26
|
||||
if key not in report_data:
|
||||
report_data[key] = [None] * totalElemList
|
||||
report_data[key][0] = task.id
|
||||
report_data[key][1] = task.village_name
|
||||
|
||||
boq_amount = safe_float(task.boq_amount)
|
||||
previous_billing_amount = safe_float(task.previous_billing_amount)
|
||||
remaining_amount = safe_float(boq_amount - previous_billing_amount)
|
||||
tender_amount = safe_float(task.qty) * safe_float(task.rate)
|
||||
values = [
|
||||
safe_float(task.qty),
|
||||
safe_float(task.rate),
|
||||
tender_amount,
|
||||
safe_float(task.previous_billed_qty),
|
||||
previous_billing_amount,
|
||||
remaining_amount
|
||||
]
|
||||
|
||||
# Determine task type section
|
||||
task_name_clean = task.task_name.lower() if task.task_name else ""
|
||||
start, end = (
|
||||
(2, 8) if "supply" in task_name_clean else
|
||||
(8, 14) if "erection" in task_name_clean else
|
||||
(14, 20) if "testing" in task_name_clean else
|
||||
(20, 26) if "commissioning" in task_name_clean else
|
||||
(None, None)
|
||||
)
|
||||
|
||||
if start is not None and end is not None:
|
||||
for i in range(start, end):
|
||||
current_value = safe_float(report_data[key][i]) if report_data[key][i] is not None else 0
|
||||
report_data[key][i] = current_value + values[i - start]
|
||||
|
||||
print(f"Number of villages in report data: {len(report_data)}")
|
||||
|
||||
# if not report_data:
|
||||
# return f"No matching data found for the selected block '{block}' and main task '{main_task}'.", 404
|
||||
if not report_data:
|
||||
flash("Sub task data not found", "error")
|
||||
# return redirect(url_for('generate_report_page'))
|
||||
return redirect(url_for('main.generate_report_page'))
|
||||
|
||||
# Generate Excel report
|
||||
sanitized_main_task = re.sub(main_task_rexp, "", main_task)
|
||||
|
||||
max_length = 30
|
||||
|
||||
if len(sanitized_main_task) > max_length:
|
||||
sanitized_main_task = sanitized_main_task[:max_length]
|
||||
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
|
||||
# file_name = f"{sanitized_main_task}_Report.xlsx"
|
||||
file_name = f"{sanitized_main_task}_{timestamp}.xlsx"
|
||||
file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], file_name)
|
||||
|
||||
wb = Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "Subtask Report"
|
||||
|
||||
# Excel formatting
|
||||
thin_border = Border(left=Side(style="thin"), right=Side(style="thin"),
|
||||
top=Side(style="thin"), bottom=Side(style="thin"))
|
||||
header_fill = PatternFill(start_color="FFC000", end_color="FFC000", fill_type="solid")
|
||||
subheader_fill = PatternFill(start_color="92D050", end_color="92D050", fill_type="solid")
|
||||
data_row_fill1 = PatternFill(start_color="FFFFFF", end_color="FFFFFF", fill_type="solid")
|
||||
data_row_fill2 = PatternFill(start_color="D9EAD3", end_color="D9EAD3", fill_type="solid")
|
||||
total_row_fill = PatternFill(start_color="FFF2CC", end_color="FFF2CC", fill_type="solid")
|
||||
|
||||
ws.merge_cells(start_row=1, start_column=3, end_row=1, end_column=8)
|
||||
ws["A1"] = "Task ID"
|
||||
ws["B1"] = "Village Name"
|
||||
ws["C1"] = "Supply (70%)"
|
||||
ws.merge_cells(start_row=1, start_column=9, end_row=1, end_column=14)
|
||||
ws["I1"] = "Erection (20%)"
|
||||
ws.merge_cells(start_row=1, start_column=15, end_row=1, end_column=20)
|
||||
ws["O1"] = "Testing (5%)"
|
||||
ws.merge_cells(start_row=1, start_column=21, end_row=1, end_column=26)
|
||||
ws["U1"] = "Commissioning (5%)"
|
||||
|
||||
for start_col in [3, 9, 15, 21]:
|
||||
ws.cell(row=2, column=start_col).value = "Tender Qty"
|
||||
ws.cell(row=2, column=start_col + 1).value = "Tender Rate"
|
||||
ws.cell(row=2, column=start_col + 2).value = "Tender Amount"
|
||||
ws.cell(row=2, column=start_col + 3).value = "Previous Bill QTY"
|
||||
ws.cell(row=2, column=start_col + 4).value = "Previous Bill Amount"
|
||||
ws.cell(row=2, column=start_col + 5).value = "Remaining Amount"
|
||||
|
||||
# Style header and subheaders
|
||||
for col in range(1, 27):
|
||||
col_letter = ws.cell(row=2, column=col).column_letter
|
||||
ws[f"{col_letter}1"].font = Font(bold=True, size=12)
|
||||
ws[f"{col_letter}1"].alignment = Alignment(horizontal="center", vertical="center")
|
||||
ws[f"{col_letter}1"].fill = header_fill
|
||||
ws[f"{col_letter}1"].border = thin_border
|
||||
|
||||
ws[f"{col_letter}2"].font = Font(bold=True, size=11)
|
||||
ws[f"{col_letter}2"].alignment = Alignment(horizontal="center", vertical="center")
|
||||
ws[f"{col_letter}2"].fill = subheader_fill
|
||||
ws[f"{col_letter}2"].border = thin_border
|
||||
|
||||
# Fill data
|
||||
row_index = 3
|
||||
totals = ["Total", ""] + [0] * 24
|
||||
sum_columns = [4, 6, 7, 10, 12, 13, 16, 18, 19, 22, 24, 25]
|
||||
|
||||
for row_data in report_data.values():
|
||||
ws.append(row_data)
|
||||
fill = data_row_fill1 if row_index % 2 != 0 else data_row_fill2
|
||||
for col in range(1, 27):
|
||||
ws.cell(row=row_index, column=col).fill = fill
|
||||
ws.cell(row=row_index, column=col).border = thin_border
|
||||
for i in sum_columns:
|
||||
totals[i] += safe_float(row_data[i])
|
||||
row_index += 1
|
||||
|
||||
# Add totals
|
||||
ws.append(totals)
|
||||
for col in range(1, 27):
|
||||
ws.cell(row=row_index, column=col).font = Font(bold=True)
|
||||
ws.cell(row=row_index, column=col).fill = total_row_fill
|
||||
ws.cell(row=row_index, column=col).alignment = Alignment(horizontal="center")
|
||||
ws.cell(row=row_index, column=col).border = thin_border
|
||||
|
||||
for i in range(1, 27):
|
||||
ws.column_dimensions[ws.cell(row=2, column=i).column_letter].width = 20
|
||||
|
||||
wb.save(file_path)
|
||||
print(f"Report generated: {file_name}")
|
||||
return redirect(url_for('reports.download_report', filename=file_name))
|
||||
|
||||
|
||||
@reports.route('/download/<filename>')
|
||||
def download_report(filename):
|
||||
return send_from_directory(current_app.config['UPLOAD_FOLDER'], filename, as_attachment=True)
|
||||
@@ -1,110 +1,376 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Activity Logs</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Segoe UI", Tahoma, sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
margin: 20px;
|
||||
}
|
||||
h2 {
|
||||
text-align: center;
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Activity Logs</title>
|
||||
|
||||
<style>
|
||||
*{
|
||||
margin:0;
|
||||
padding:0;
|
||||
box-sizing:border-box;
|
||||
font-family:'Segoe UI',sans-serif;
|
||||
}
|
||||
|
||||
body{
|
||||
background:#eef2f7;
|
||||
display:flex;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar{
|
||||
width:260px;
|
||||
height:100vh;
|
||||
position:fixed;
|
||||
left:0;
|
||||
top:0;
|
||||
background:linear-gradient(180deg,#0f172a,#1e293b);
|
||||
padding:25px;
|
||||
color:#fff;
|
||||
}
|
||||
|
||||
.sidebar h2{
|
||||
text-align:center;
|
||||
margin-bottom:30px;
|
||||
font-size:24px;
|
||||
}
|
||||
|
||||
.sidebar a{
|
||||
display:block;
|
||||
padding:14px 16px;
|
||||
margin:10px 0;
|
||||
border-radius:10px;
|
||||
text-decoration:none;
|
||||
color:#dbeafe;
|
||||
transition:.3s;
|
||||
}
|
||||
|
||||
.sidebar a:hover{
|
||||
background:#3b82f6;
|
||||
color:white;
|
||||
}
|
||||
|
||||
/* Main */
|
||||
.main{
|
||||
margin-left:260px;
|
||||
width:100%;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header{
|
||||
height:50px;
|
||||
background:#fff;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
padding-left:30px;
|
||||
font-size:18px;
|
||||
font-weight:600;
|
||||
box-shadow:0 2px 8px rgba(0,0,0,.08);
|
||||
color:#1e293b;
|
||||
}
|
||||
|
||||
/* Title */
|
||||
.page-title{
|
||||
text-align:center;
|
||||
margin:30px 0 20px;
|
||||
font-size:32px;
|
||||
color:#1e293b;
|
||||
font-weight:700;
|
||||
}
|
||||
|
||||
/* Filter Card */
|
||||
.filter-box{
|
||||
width:95%;
|
||||
max-width:1200px;
|
||||
margin:auto;
|
||||
background:white;
|
||||
padding:25px;
|
||||
border-radius:16px;
|
||||
box-shadow:0 4px 12px rgba(0,0,0,.06);
|
||||
}
|
||||
|
||||
.filter-form{
|
||||
display:flex;
|
||||
flex-wrap:wrap;
|
||||
gap:15px;
|
||||
justify-content:center;
|
||||
align-items:end;
|
||||
}
|
||||
|
||||
.filter-group{
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
min-width:220px;
|
||||
}
|
||||
|
||||
label{
|
||||
font-weight:600;
|
||||
margin-bottom:8px;
|
||||
color:#334155;
|
||||
}
|
||||
|
||||
input{
|
||||
padding:11px 14px;
|
||||
border:1px solid #cbd5e1;
|
||||
border-radius:8px;
|
||||
outline:none;
|
||||
}
|
||||
|
||||
input:focus{
|
||||
border-color:#3b82f6;
|
||||
}
|
||||
|
||||
.btn{
|
||||
border:none;
|
||||
padding:12px 20px;
|
||||
border-radius:8px;
|
||||
cursor:pointer;
|
||||
font-weight:600;
|
||||
transition:.3s;
|
||||
}
|
||||
|
||||
.btn-primary{
|
||||
background:#3b82f6;
|
||||
color:#fff;
|
||||
}
|
||||
|
||||
.btn-primary:hover{
|
||||
background:#2563eb;
|
||||
}
|
||||
|
||||
.btn-secondary{
|
||||
background:#64748b;
|
||||
color:white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover{
|
||||
background:#475569;
|
||||
}
|
||||
|
||||
/* Table Section */
|
||||
.table-box{
|
||||
width:95%;
|
||||
max-width:1400px;
|
||||
margin:30px auto;
|
||||
background:white;
|
||||
border-radius:16px;
|
||||
overflow:auto;
|
||||
box-shadow:0 4px 12px rgba(0,0,0,.06);
|
||||
max-height:57vh;
|
||||
}
|
||||
|
||||
table{
|
||||
width:100%;
|
||||
border-collapse:collapse;
|
||||
min-width:1000px;
|
||||
}
|
||||
|
||||
th{
|
||||
position:sticky;
|
||||
top:0;
|
||||
background:#3b82f6;
|
||||
color:white;
|
||||
padding:15px;
|
||||
font-size:15px;
|
||||
}
|
||||
|
||||
td{
|
||||
padding:14px;
|
||||
text-align:center;
|
||||
border-bottom:1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
tr:nth-child(even){
|
||||
background:#f8fafc;
|
||||
}
|
||||
|
||||
tr:hover{
|
||||
background:#eaf2ff;
|
||||
}
|
||||
|
||||
/* Back Button */
|
||||
.top-actions{
|
||||
width:95%;
|
||||
max-width:1200px;
|
||||
margin:auto;
|
||||
display:flex;
|
||||
justify-content:flex-end;
|
||||
margin-bottom:15px;
|
||||
}
|
||||
|
||||
.back-btn{
|
||||
background:#10b981;
|
||||
color:white;
|
||||
padding:12px 18px;
|
||||
text-decoration:none;
|
||||
border-radius:8px;
|
||||
font-weight:600;
|
||||
}
|
||||
|
||||
.back-btn:hover{
|
||||
background:#059669;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media(max-width:900px){
|
||||
|
||||
.sidebar{
|
||||
width:75px;
|
||||
padding:15px 10px;
|
||||
}
|
||||
|
||||
.sidebar h2{
|
||||
display:none;
|
||||
}
|
||||
|
||||
.sidebar a{
|
||||
font-size:12px;
|
||||
padding:10px;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
.main{
|
||||
margin-left:75px;
|
||||
}
|
||||
|
||||
.page-title{
|
||||
font-size:26px;
|
||||
}
|
||||
|
||||
.filter-form{
|
||||
flex-direction:column;
|
||||
align-items:stretch;
|
||||
}
|
||||
|
||||
.filter-group{
|
||||
width:100%;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar .logo {
|
||||
width: 60px;
|
||||
height: auto;
|
||||
margin-left: 80px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.top-buttons {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
form {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
input, button {
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
button {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
table {
|
||||
width: 90%;
|
||||
margin: auto;
|
||||
border-collapse: collapse;
|
||||
background: white;
|
||||
box-shadow: 0 0 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
th, td {
|
||||
padding: 12px;
|
||||
border: 1px solid #ddd;
|
||||
text-align: center;
|
||||
}
|
||||
th {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
tr:nth-child(even) {
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
</style>
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Activity Logs</h2>
|
||||
|
||||
<div class="top-buttons">
|
||||
<a href="{{ url_for('main.dashboard') }}">
|
||||
<button type="button">⬅ Back to Dashboard</button>
|
||||
</a>
|
||||
</div>
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar">
|
||||
<img
|
||||
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA+CAYAAAB3NHh5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA4JpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTMyIDc5LjE1OTI4NCwgMjAxNi8wNC8xOS0xMzoxMzo0MCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo4NkEzOEVGMzEwQTQxMUU4QUFBQkY0QTA2QzlEM0MxNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2QUQzQTIzRDE4NTkxMUU4ODE2Q0IwMTY0RjgxQTVGNyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2QUQzQTIzQzE4NTkxMUU4ODE2Q0IwMTY0RjgxQTVGNyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNS41IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOmJhNmE4ZTBmLTI5ZWItNDBlMy05ZWFhLTYzNTdiYjdkMzcwNCIgc3RSZWY6ZG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOmRhMjgyMWMwLTYwYmQtMTE3Yi04ZGU3LWNjZmQ1MDgzNjUxNiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PrVd/GcAAASESURBVHja7FtrSBRRFL7rrg9MMjUzspTSSqUg6GFBmohh9SeIMOhXhBX0KzL80YN+SBD+qJ+RBhFEQogV0RMLKyuiqIig6GlZRmlRavl2O8c9I7dtZ2adM3d9zB742GHuzpn7zZl7zrnn3nF5vV7hJHEh4aLKaxfgeAXAjee0NoDX7ziCjl2yDsANwHbAsQD3mALYA3ii04e9gEZAP6BM5z9nAA2AKos8FwK+1pcX53noxCJAMuPBzQLEATbotEfpnMeHfIiuSzK4vhnw2KA9GJkryGIoz5hvyjlAr0F7e4BzuYBbdPwG8Mvg+m+APmYfh/qgWbiXqWzQpN3fUSyj19hj8gbYKV7Zwsp9hXS8BHBXIhsqiQglYTf9LgbcGQWywxKqG7cAssiy0aMVkWQLpzGVoYfvMWjvBlQwyKKPiWT2MU628DZACkPZa0CGQXs00zHOBnwBrLF4fTbgnb+X7mJ0qN/EH3iZYUVLfKz2sQMwSSZ8kkKFVTkKOKxw/KF1Zkhx26rUaFb5xFTUpjiWRtmQeHSGMiyNGQkTdgrhVKaeJBvycbM4zE2S/vHSpTRv7beo7CEgXiFh7GwTTSetCE5/f8iEMdOaajJFM5KtgHyFhDcC/gBeMfKEeJnwAcDyMTz0FgAqmTowG6zWxnCrA/zVc9lp9TmAcEgLAOE4HCY8zgoAZuI28RPdiuO45umHw9LuIKoWHPlOiY1RNeIETfI5gkWM6YABv/MZFMeHCWONOAFwWRFhrKjkEPFAshMwB/CReZ9LZn/QCM8ErBe+ZQ9VUid8JdpAclH4llO40kmFjBdmTmsXYL4NFQWzIkGXDt7adA8cGlgZzQrGS+OMpABwW2HVwkrbSAWH5gNBa0nBhCUk3TjOQ9BkIp0phyQ9wl6a+dwb56QTiEOmkNa2IgzyzpWA+zZ2oMNiG0eSydLp/l5aj/Qq4VsLyrXh5jgJz9NpK1Bo6UQqHuCCfbVZ2aSP5smbBW+pYx5gh4FDRM+6D/BZEWksQf2ULVxC5g80L8YqyFPx/xrvSOQUjaUSnfYyevWyFVr6t0wY92AsVXizJJNximnfFuFbAVElGHnyNKfVothjDpr4iyjNAgqlVfbSA+GKR7gAMLEIexzA1SMTjnUA4VjZspsAMQxluGqxVqhbFMetjQcB7xk6umXChUZzyCCkFnBcIeGblI3tZ+h4if3UCJczE49EoXbLA87VcWNLBUMHLvjVamO42YZqxljf8tAcjsNOIuxyGuEMGyoLPQr72SP4Ww/T5LBURZ226rweyWUUC+I2mW9jZ3GTeCnjHk0y4fOCNl8yvHQO4/oBYbzJPIaszKmmdsiEa4R+vSkYwe0IRyxee0X4PkEoNPgPfiKQSsmDVcHaXL5HshBH2i2OsauAdXRstNgWacOcvU12WtyPl6xcf53yb01Ub7sIeQHA6zcZKJ7IiYdMth5QNNpxOJ2pZ5rJGMZcGOvbuEq4WifZSWHoD0bSZS99FvCB6QHRcdXptONOuNP0KuuN+wYiZkV/MPJg6Ck77WPLvwIMAHzX4zyhUFlrAAAAAElFTkSuQmCC"
|
||||
alt="LCEPL Logo"
|
||||
class="logo"
|
||||
/>
|
||||
|
||||
<form method="get" action="{{ url_for('main.activity_log') }}" class="filter-form">
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username" placeholder="Enter username" value="{{ username or '' }}">
|
||||
<center><h2>Activity Logs</h2></center>
|
||||
<a href="{{ url_for('main.dashboard') }}">Dashboard</a>
|
||||
<a href="{{ url_for('main.activity_log') }}">Activity Logs</a>
|
||||
|
||||
<label for="start_date">Start Date:</label>
|
||||
<input type="date" id="start_date" name="start_date" value="{{ start_date or '' }}">
|
||||
</div>
|
||||
|
||||
<label for="end_date">End Date:</label>
|
||||
<input type="date" id="end_date" name="end_date" value="{{ end_date or '' }}">
|
||||
|
||||
<button type="submit" class="btn btn-primary">Filter</button>
|
||||
</form>
|
||||
<div class="main">
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Timestamp</th>
|
||||
<th>User</th>
|
||||
<th>Action</th>
|
||||
<th>Details</th>
|
||||
</tr>
|
||||
{% for log in logs %}
|
||||
<tr>
|
||||
<td>{{ log.timestamp }}</td>
|
||||
<td>{{ log.user }}</td>
|
||||
<td>{{ log.action }}</td>
|
||||
<td>{{ log.details }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if logs|length == 0 %}
|
||||
<tr>
|
||||
<td colspan="4">No logs found</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
<div class="header">
|
||||
LAXMI CIVIL ENGINEERING SERVICES PVT. LTD.
|
||||
</div>
|
||||
|
||||
<h1 class="page-title">Activity Logs</h1>
|
||||
|
||||
<div class="top-actions">
|
||||
<a href="{{ url_for('main.dashboard') }}" class="back-btn">
|
||||
⬅ Back to Dashboard
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="filter-box">
|
||||
<form method="get"
|
||||
action="{{ url_for('main.activity_log') }}"
|
||||
class="filter-form">
|
||||
|
||||
<div class="filter-group">
|
||||
<label>Username</label>
|
||||
<input type="text"
|
||||
name="username"
|
||||
placeholder="Enter username"
|
||||
value="{{ username or '' }}">
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label>Start Date</label>
|
||||
<input type="date"
|
||||
name="start_date"
|
||||
value="{{ start_date or '' }}">
|
||||
</div>
|
||||
|
||||
<div class="filter-group">
|
||||
<label>End Date</label>
|
||||
<input type="date"
|
||||
name="end_date"
|
||||
value="{{ end_date or '' }}">
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" type="submit">
|
||||
Filter
|
||||
</button>
|
||||
|
||||
<button class="btn btn-secondary"
|
||||
type="button"
|
||||
onclick="resetFilter()">
|
||||
Reset
|
||||
</button>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Logs Table -->
|
||||
<div class="table-box">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Timestamp</th>
|
||||
<th>User</th>
|
||||
<th>Action</th>
|
||||
<th>Details</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for log in logs %}
|
||||
<tr>
|
||||
<td>{{ log.timestamp }}</td>
|
||||
<td>{{ log.user }}</td>
|
||||
<td>{{ log.action }}</td>
|
||||
<td>{{ log.details }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
{% if logs|length == 0 %}
|
||||
<tr>
|
||||
<td colspan="4">
|
||||
No logs found
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function resetFilter(){
|
||||
window.location.href="{{ url_for('main.activity_log') }}";
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function resetFilter() {
|
||||
window.location.href = "{{ url_for('main.activity_log') }}";
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,474 +1,369 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Filtered Task Display</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f7f9fc;
|
||||
margin: 20px;
|
||||
position: relative;
|
||||
}
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Filtered Task Display</title>
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Segoe UI', sans-serif;
|
||||
}
|
||||
|
||||
form {
|
||||
body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-end;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
background: #eef2f7;
|
||||
}
|
||||
|
||||
label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
select,
|
||||
button {
|
||||
padding: 5px 10px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
button[type="submit"] {
|
||||
background-color: #4CAF50;
|
||||
/* Sidebar */
|
||||
.sidebar {
|
||||
width: 260px;
|
||||
background: linear-gradient(180deg, #0f172a, #1e293b);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
button[type="submit"]:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
.sidebar a {
|
||||
display: block;
|
||||
padding: 12px;
|
||||
margin: 8px 0;
|
||||
color: #cbd5e1;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.button-container1 {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
.button-container1 button {
|
||||
background-color: #4CAF50;
|
||||
.sidebar a:hover {
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.button-container1 button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
.sidebar .logo {
|
||||
width: 60px;
|
||||
margin: 20px auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
/* Main */
|
||||
.main {
|
||||
margin-left: 260px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
height: 60px;
|
||||
background: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 20px;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
/* Title */
|
||||
.report_title {
|
||||
text-align: center;
|
||||
margin: 20px 0;
|
||||
font-size: 26px;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
/* Filters */
|
||||
form {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
background: white;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
select {
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ccc;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.button-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-mode-button {
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
.edit-mode-button {
|
||||
padding: 10px 18px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-mode-button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
.edit-mode-button:hover {
|
||||
background: #2563eb;
|
||||
}
|
||||
|
||||
#selectedFilters {
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
/* Table */
|
||||
.table-container {
|
||||
margin: 20px;
|
||||
overflow: auto;
|
||||
max-height: 65vh;
|
||||
background: white;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
table {
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 30px;
|
||||
margin-left: 275px;
|
||||
}
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
th:nth-child(1),
|
||||
td:nth-child(1) {
|
||||
width: 300px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th:not(:first-child),
|
||||
td:not(:first-child) {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #007bff;
|
||||
th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.main-task-row {
|
||||
background-color: #d9eaf7;
|
||||
tr:nth-child(even) {
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.main-task-row {
|
||||
background: #dbeafe;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.subtask-row {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
.edit-field {
|
||||
.edit-field {
|
||||
display: none;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.no-tasks-message {
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
select {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
/* Sidebar Styling */
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
background-color: #f4f4f4;
|
||||
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
|
||||
padding: 20px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.sidebar .logo {
|
||||
.readonly {
|
||||
background: #e5e7eb;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.sidebar .logo {
|
||||
width: 60px;
|
||||
height: auto;
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
.sidebar h2 {
|
||||
color: white;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
.sidebar a {
|
||||
display: block;
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
padding: 10px 15px;
|
||||
margin: 10px 0;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sidebar a:hover {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header {
|
||||
width: 100%;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 15px 0;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
h1.report_title {
|
||||
text-align: center;
|
||||
margin-top: 100px;
|
||||
color: #333;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
overflow-y: auto;
|
||||
}
|
||||
th {
|
||||
position: sticky; /* Ensures the header stays on top */
|
||||
top: 58px; /* Keeps the header fixed at the top */
|
||||
z-index: 1; /* Ensures it's above the content */
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar">
|
||||
<img
|
||||
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA+CAYAAAB3NHh5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA4JpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTMyIDc5LjE1OTI4NCwgMjAxNi8wNC8xOS0xMzoxMzo0MCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo4NkEzOEVGMzEwQTQxMUU4QUFBQkY0QTA2QzlEM0MxNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2QUQzQTIzRDE4NTkxMUU4ODE2Q0IwMTY0RjgxQTVGNyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2QUQzQTIzQzE4NTkxMUU4ODE2Q0IwMTY0RjgxQTVGNyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNS41IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOmJhNmE4ZTBmLTI5ZWItNDBlMy05ZWFhLTYzNTdiYjdkMzcwNCIgc3RSZWY6ZG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOmRhMjgyMWMwLTYwYmQtMTE3Yi04ZGU3LWNjZmQ1MDgzNjUxNiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PrVd/GcAAASESURBVHja7FtrSBRRFL7rrg9MMjUzspTSSqUg6GFBmohh9SeIMOhXhBX0KzL80YN+SBD+qJ+RBhFEQogV0RMLKyuiqIig6GlZRmlRavl2O8c9I7dtZ2adM3d9zB742GHuzpn7zZl7zrnn3nF5vV7hJHEh4aLKaxfgeAXAjee0NoDX7ziCjl2yDsANwHbAsQD3mALYA3ii04e9gEZAP6BM5z9nAA2AKos8FwK+1pcX53noxCJAMuPBzQLEATbotEfpnMeHfIiuSzK4vhnw2KA9GJkryGIoz5hvyjlAr0F7e4BzuYBbdPwG8Mvg+m+APmYfh/qgWbiXqWzQpN3fUSyj19hj8gbYKV7Zwsp9hXS8BHBXIhsqiQglYTf9LgbcGQWywxKqG7cAssiy0aMVkWQLpzGVoYfvMWjvBlQwyKKPiWT2MU628DZACkPZa0CGQXs00zHOBnwBrLF4fTbgnb+X7mJ0qN/EH3iZYUVLfKz2sQMwSSZ8kkKFVTkKOKxw/KF1Zkhx26rUaFb5xFTUpjiWRtmQeHSGMiyNGQkTdgrhVKaeJBvycbM4zE2S/vHSpTRv7beo7CEgXiFh7GwTTSetCE5/f8iEMdOaajJFM5KtgHyFhDcC/gBeMfKEeJnwAcDyMTz0FgAqmTowG6zWxnCrA/zVc9lp9TmAcEgLAOE4HCY8zgoAZuI28RPdiuO45umHw9LuIKoWHPlOiY1RNeIETfI5gkWM6YABv/MZFMeHCWONOAFwWRFhrKjkEPFAshMwB/CReZ9LZn/QCM8ErBe+ZQ9VUid8JdpAclH4llO40kmFjBdmTmsXYL4NFQWzIkGXDt7adA8cGlgZzQrGS+OMpABwW2HVwkrbSAWH5gNBa0nBhCUk3TjOQ9BkIp0phyQ9wl6a+dwb56QTiEOmkNa2IgzyzpWA+zZ2oMNiG0eSydLp/l5aj/Qq4VsLyrXh5jgJz9NpK1Bo6UQqHuCCfbVZ2aSP5smbBW+pYx5gh4FDRM+6D/BZEWksQf2ULVxC5g80L8YqyFPx/xrvSOQUjaUSnfYyevWyFVr6t0wY92AsVXizJJNximnfFuFbAVElGHnyNKfVothjDpr4iyjNAgqlVfbSA+GKR7gAMLEIexzA1SMTjnUA4VjZspsAMQxluGqxVqhbFMetjQcB7xk6umXChUZzyCCkFnBcIeGblI3tZ+h4if3UCJczE49EoXbLA87VcWNLBUMHLvjVamO42YZqxljf8tAcjsNOIuxyGuEMGyoLPQr72SP4Ww/T5LBURZ226rweyWUUC+I2mW9jZ3GTeCnjHk0y4fOCNl8yvHQO4/oBYbzJPIaszKmmdsiEa4R+vSkYwe0IRyxee0X4PkEoNPgPfiKQSsmDVcHaXL5HshBH2i2OsauAdXRstNgWacOcvU12WtyPl6xcf53yb01Ub7sIeQHA6zcZKJ7IiYdMth5QNNpxOJ2pZ5rJGMZcGOvbuEq4WifZSWHoD0bSZS99FvCB6QHRcdXptONOuNP0KuuN+wYiZkV/MPJg6Ck77WPLvwIMAHzX4zyhUFlrAAAAAElFTkSuQmCC"
|
||||
alt="LCEPL Logo"
|
||||
class="logo"
|
||||
/>
|
||||
<h2>Filter Task</h2>
|
||||
<a href="/">Dashboard</a>
|
||||
<a href="/upload_excel">Upload Excel</a>
|
||||
<a href="/generate_report_page">Print Report</a>
|
||||
<a href="/filter_tasks">Filter Tasks</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="header">LAXMI CIVIL ENGINEERING SERVICES PVT. LTD.</div>
|
||||
<h1 class="report_title">Filtered Tasks</h1>
|
||||
<div class="main">
|
||||
|
||||
<form method="GET" action="{{ url_for('main.filter_tasks') }}">
|
||||
<!-- District Dropdown -->
|
||||
<label for="district">District:</label>
|
||||
<div class="header">LAXMI CIVIL ENGINEERING SERVICES</div>
|
||||
<h1 class="report_title">Filtered Tasks</h1>
|
||||
|
||||
<!-- Filters -->
|
||||
<form method="GET" action="{{ url_for('main.filter_tasks') }}">
|
||||
<select name="district" id="district" onchange="this.form.submit()">
|
||||
<option value="">-- Select District --</option>
|
||||
<option value="">District</option>
|
||||
{% for d in districts %}
|
||||
<option value="{{ d }}" {% if d == selected_district %}selected{% endif %}>{{ d }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<!-- Block Dropdown -->
|
||||
<label for="block">Block:</label>
|
||||
<select name="block" id="block" onchange="this.form.submit()">
|
||||
<option value="">-- Select Block --</option>
|
||||
<option value="">Block</option>
|
||||
{% for b in blocks %}
|
||||
<option value="{{ b }}" {% if b == selected_block %}selected{% endif %}>{{ b }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<!-- Village Dropdown -->
|
||||
<label for="village">Village:</label>
|
||||
<select name="village" id="village" onchange="this.form.submit()">
|
||||
<option value="">-- Select Village --</option>
|
||||
<option value="">Village</option>
|
||||
{% for v in villages %}
|
||||
<option value="{{ v }}" {% if v == selected_village %}selected{% endif %}>{{ v }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
</form>
|
||||
|
||||
<div id="selectedFilters">
|
||||
{% if selected_district or selected_block or selected_village %}
|
||||
<p>
|
||||
{% if selected_district %}<strong>District:</strong> {{ selected_district }}{% endif %}
|
||||
{% if selected_block %} <strong> | Block:</strong> {{ selected_block }}{% endif %}
|
||||
{% if selected_village %} <strong> | Village:</strong> {{ selected_village }}{% endif %}
|
||||
</p>
|
||||
{% else %}
|
||||
<p><strong>No filters applied. Showing all tasks.</strong></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<!-- Buttons -->
|
||||
<div class="button-container">
|
||||
<button class="edit-mode-button" onclick="toggleEditMode()">Edit</button>
|
||||
<button class="edit-mode-button" id="cancelBtn" onclick="cancelEditMode()" style="display:none;">Cancel</button>
|
||||
<button class="edit-mode-button" onclick="submitChangedFields()">Save</button>
|
||||
<button class="edit-mode-button" onclick="downloadExcel()">Download</button>
|
||||
</div>
|
||||
|
||||
<div class="button-container1">
|
||||
<button onclick="window.location.href='/'">Home Page</button>
|
||||
</div>
|
||||
|
||||
<div class="button-container">
|
||||
<button class="edit-mode-button" type="button" onclick="toggleEditMode()">Edit Tasks</button>
|
||||
<button class="edit-mode-button" type="button" id="cancelBtn" onclick="cancelEditMode()" style="display: none;">Cancel</button>
|
||||
<button class="edit-mode-button" type="button" onclick="submitChangedFields()">Submit Updates</button>
|
||||
<button class="edit-mode-button" type="button" onclick="downloadExcel()">Download Excel</button>
|
||||
</div>
|
||||
|
||||
{% if grouped_tasks %}
|
||||
<table class="table-container">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Task Name</th>
|
||||
<th>Unit</th>
|
||||
<th>Qty</th>
|
||||
<th>Rate</th>
|
||||
<th>BOQ Amt</th>
|
||||
<th>Prev Billed Qty</th>
|
||||
<th>Prev Bill Amt</th>
|
||||
<th>RA Bill Qty</th>
|
||||
<th>RA Bill Amt</th>
|
||||
<th>Cum Billed Qty</th>
|
||||
<th>Cum Billed Amt</th>
|
||||
<th>Var Qty</th>
|
||||
<th>Var Amt</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% set formula_fields = [
|
||||
{% set readonly_fields = [
|
||||
"previous_billing_amount",
|
||||
"in_this_ra_billing_amount",
|
||||
"cumulative_billed_qty",
|
||||
"cumulative_billed_amount",
|
||||
"variation_qty",
|
||||
"variation_amount"
|
||||
] %}
|
||||
] %}
|
||||
|
||||
{% for group in grouped_tasks %}
|
||||
<!-- <tr class="main-task-row">
|
||||
<td colspan="1">{{ group.task_name }}</td>
|
||||
</tr> -->
|
||||
<!-- <tr class="main-task-row">
|
||||
{% for field in ["task_name","unit","qty","rate","boq_amount","previous_billed_qty","previous_billing_amount","in_this_ra_bill_qty","in_this_ra_billing_amount","cumulative_billed_qty","cumulative_billed_amount","variation_qty","variation_amount"] %}
|
||||
<td>
|
||||
{{ '' if group[field] in [None, 0, "0"] else group[field] }}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr> -->
|
||||
<tr class="main-task-row">
|
||||
{% if grouped_tasks %}
|
||||
<div class="table-container">
|
||||
<table id="taskTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Task</th>
|
||||
<th>Unit</th>
|
||||
<th>Qty</th>
|
||||
<th>Rate</th>
|
||||
<th>BOQ</th>
|
||||
<th>Prev Qty</th>
|
||||
<th>Prev Amt</th>
|
||||
<th>RA Qty</th>
|
||||
<th>RA Amt</th>
|
||||
<th>Cum Qty</th>
|
||||
<th>Cum Amt</th>
|
||||
<th>Var Qty</th>
|
||||
<th>Var Amt</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for group in grouped_tasks %}
|
||||
<tr class="main-task-row">
|
||||
{% for field in ["task_name","unit","qty","rate","boq_amount","previous_billed_qty","previous_billing_amount","in_this_ra_bill_qty","in_this_ra_billing_amount","cumulative_billed_qty","cumulative_billed_amount","variation_qty","variation_amount"] %}
|
||||
<td>
|
||||
<span class="static-text">
|
||||
{{ group[field] if group[field] is not none else '' }}
|
||||
{{ '' if group[field] is none or group[field] == '0' or group[field] == 0 else group[field] }}
|
||||
</span>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
class="edit-field"
|
||||
name="{{ field }}_main_{{ loop.index }}"
|
||||
value="{{ group[field] if group[field] is not none else '' }}"
|
||||
data-original-value="{{ group[field] if group[field] is not none else '' }}"
|
||||
class="edit-field {% if field in readonly_fields %}readonly{% endif %}"
|
||||
name="{{ field }}_{{ group.id }}"
|
||||
value="{{ group[field] or '' }}"
|
||||
data-original-value="{{ group[field] or '' }}"
|
||||
{% if field in readonly_fields %}readonly{% endif %}
|
||||
>
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% for sub in group.subtasks %}
|
||||
<tr class="subtask-row">
|
||||
{% for field in ["task_name","unit","qty","rate","boq_amount","previous_billed_qty","previous_billing_amount","in_this_ra_bill_qty","in_this_ra_billing_amount","cumulative_billed_qty","cumulative_billed_amount","variation_qty","variation_amount"] %}
|
||||
<td>
|
||||
<span class="static-text">
|
||||
{{ sub[field] if sub[field] is not none else '' }}
|
||||
</span>
|
||||
|
||||
{% for sub in group.subtasks %}
|
||||
<tr>
|
||||
{% for field in ["task_name","unit","qty","rate","boq_amount","previous_billed_qty","previous_billing_amount","in_this_ra_bill_qty","in_this_ra_billing_amount","cumulative_billed_qty","cumulative_billed_amount","variation_qty","variation_amount"] %}
|
||||
<td>
|
||||
<span class="static-text">{{ sub[field] or '' }}</span>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
class="edit-field"
|
||||
class="edit-field {% if field in readonly_fields %}readonly{% endif %}"
|
||||
name="{{ field }}_{{ sub.id }}"
|
||||
value="{{ sub[field] if sub[field] is not none else '' }}"
|
||||
data-original-value="{{ sub[field] if sub[field] is not none else '' }}"
|
||||
{% if field in formula_fields %} readonly style="background:#f5f5f5;" {% endif %}
|
||||
value="{{ sub[field] or '' }}"
|
||||
data-original-value="{{ sub[field] or '' }}"
|
||||
{% if field in readonly_fields %}readonly{% endif %}
|
||||
>
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="no-tasks-message">No tasks found for the selected filters.</p>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- ✅ Scripts -->
|
||||
<script>
|
||||
// Toggle edit mode
|
||||
function toggleEditMode() {
|
||||
document.querySelectorAll('.static-text').forEach(span => span.style.display = 'none');
|
||||
document.querySelectorAll('.edit-field').forEach(input => input.style.display = 'inline-block');
|
||||
document.getElementById('cancelBtn').style.display = 'inline-block';
|
||||
}
|
||||
</div>
|
||||
|
||||
// Cancel edit mode
|
||||
function cancelEditMode() {
|
||||
document.querySelectorAll('.edit-field').forEach(input => {
|
||||
input.style.display = 'none';
|
||||
<script>
|
||||
|
||||
function toggleEditMode(){
|
||||
document.querySelectorAll('.static-text').forEach(e=>e.style.display='none');
|
||||
document.querySelectorAll('.edit-field').forEach(e=>e.style.display='block');
|
||||
document.getElementById('cancelBtn').style.display='inline-block';
|
||||
}
|
||||
|
||||
function cancelEditMode(){
|
||||
document.querySelectorAll('.edit-field').forEach(input=>{
|
||||
input.style.display='none';
|
||||
input.value = input.dataset.originalValue;
|
||||
});
|
||||
document.querySelectorAll('.static-text').forEach(span => span.style.display = 'inline-block');
|
||||
document.getElementById('cancelBtn').style.display = 'none';
|
||||
}
|
||||
|
||||
// Submit changed fields only
|
||||
function submitChangedFields() {
|
||||
document.querySelectorAll('.static-text').forEach(e=>e.style.display='inline');
|
||||
document.getElementById('cancelBtn').style.display='none';
|
||||
}
|
||||
|
||||
function submitChangedFields(){
|
||||
const updates = {};
|
||||
document.querySelectorAll('.edit-field').forEach(input => {
|
||||
const original = input.dataset.originalValue;
|
||||
const current = input.value;
|
||||
if (current !== original) {
|
||||
updates[input.name] = current;
|
||||
|
||||
document.querySelectorAll('.edit-field').forEach(input=>{
|
||||
if(!input.readOnly && input.value !== input.dataset.originalValue){
|
||||
updates[input.name] = input.value;
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(updates).length === 0) {
|
||||
alert('No changes detected.');
|
||||
if(Object.keys(updates).length === 0){
|
||||
alert("No changes detected");
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/update_tasks', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(updates)
|
||||
fetch('/update_tasks',{
|
||||
method:'POST',
|
||||
headers:{'Content-Type':'application/json'},
|
||||
body:JSON.stringify(updates)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
alert(data.message || 'Tasks updated successfully.');
|
||||
window.location.reload();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error updating tasks:', error);
|
||||
alert('Failed to update tasks.');
|
||||
.then(res=>res.json())
|
||||
.then(()=>{
|
||||
alert("Updated successfully");
|
||||
location.reload();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Filtering dependent dropdowns
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const districtDropdown = document.getElementById("district");
|
||||
const blockDropdown = document.getElementById("block");
|
||||
const villageDropdown = document.getElementById("village");
|
||||
function downloadExcel(){
|
||||
const d=document.getElementById("district").value;
|
||||
const b=document.getElementById("block").value;
|
||||
const v=document.getElementById("village").value;
|
||||
|
||||
districtDropdown.addEventListener("change", function () {
|
||||
let district = this.value;
|
||||
blockDropdown.innerHTML = '<option value="">Select Block</option>';
|
||||
villageDropdown.innerHTML = '<option value="">Select Village</option>';
|
||||
window.location=`/download_filtered_tasks?district=${d}&block=${b}&village=${v}`;
|
||||
}
|
||||
|
||||
if (district) {
|
||||
fetch(`/get_blocks?district=${district}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
data.forEach(block => {
|
||||
let option = document.createElement("option");
|
||||
option.value = block;
|
||||
option.textContent = block;
|
||||
blockDropdown.appendChild(option);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
blockDropdown.addEventListener("change", function () {
|
||||
let block = this.value;
|
||||
villageDropdown.innerHTML = '<option value="">Select Village</option>';
|
||||
|
||||
if (block) {
|
||||
fetch(`/get_villages?block=${block}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
data.forEach(village => {
|
||||
let option = document.createElement("option");
|
||||
option.value = village;
|
||||
option.textContent = village;
|
||||
villageDropdown.appendChild(option);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function downloadExcel() {
|
||||
const district = document.getElementById("district").value;
|
||||
const block = document.getElementById("block").value;
|
||||
const village = document.getElementById("village").value;
|
||||
|
||||
let url = `/download_filtered_tasks?district=${district}&block=${block}&village=${village}`;
|
||||
window.location.href = url;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,196 +1,216 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Task Report Generator</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Generate Report</title>
|
||||
|
||||
<!-- Select2 CSS -->
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/css/select2.min.css" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/css/select2.min.css" rel="stylesheet" />
|
||||
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: #f1f5f9;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Arial', sans-serif;
|
||||
background-color: #f4f7fc;
|
||||
padding: 0 20px;
|
||||
margin-left: 275px;
|
||||
}
|
||||
/* Sidebar */
|
||||
.sidebar {
|
||||
width: 240px;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
background: linear-gradient(180deg, #0f172a, #1e293b);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
h1.report_title {
|
||||
text-align: center;
|
||||
margin-top: 100px;
|
||||
color: #333;
|
||||
font-size: 28px;
|
||||
}
|
||||
.sidebar img {
|
||||
width: 60px;
|
||||
display: block;
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
|
||||
form {
|
||||
max-width: 500px;
|
||||
margin: 60px auto;
|
||||
padding: 30px;
|
||||
background-color: #ffffff;
|
||||
.sidebar a {
|
||||
display: block;
|
||||
color: #cbd5f5;
|
||||
text-decoration: none;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
form label {
|
||||
.sidebar a:hover {
|
||||
background: rgba(59,130,246,0.2);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
margin-left: 270px;
|
||||
height: 60px;
|
||||
background: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 20px;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
/* Main */
|
||||
.main {
|
||||
margin-left: 280px;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
/* Card */
|
||||
.card {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 10px 25px rgba(0,0,0,0.08);
|
||||
max-width: 500px;
|
||||
margin: 100px auto;
|
||||
}
|
||||
|
||||
/* Title */
|
||||
.title {
|
||||
text-align: center;
|
||||
margin-bottom: 35px;
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Form */
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
font-weight: 500;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
select, button {
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
font-size: 16px;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ddd;
|
||||
margin-bottom: 20px;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
margin-bottom: 25px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover { background-color: #218838; }
|
||||
|
||||
.form-group { margin-bottom: 15px; }
|
||||
|
||||
.sidebar {
|
||||
width: 295px;
|
||||
background-color: #f4f4f4;
|
||||
box-shadow: 2px 0 5px rgba(0,0,0,0.1);
|
||||
padding: 20px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.sidebar .logo { width: 60px; margin-bottom: 20px; }
|
||||
|
||||
.sidebar a {
|
||||
display: block;
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
padding: 10px 15px;
|
||||
margin: 10px 0;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sidebar a:hover {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.header {
|
||||
/* Button */
|
||||
.btn {
|
||||
width: 100%;
|
||||
background-color: #007bff;
|
||||
padding: 12px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(135deg, #3b82f6, #2563eb);
|
||||
color: white;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
transition: 0.3s;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Select2 Fix */
|
||||
.select2-container .select2-selection--single {
|
||||
height: 42px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.select2-container .select2-selection__rendered {
|
||||
line-height: 42px;
|
||||
}
|
||||
|
||||
.select2-container .select2-selection__arrow {
|
||||
height: 42px;
|
||||
}
|
||||
|
||||
.sidebar .logo {
|
||||
width: 60px;
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
.sidebar h2 {
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 15px 0;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
</style>
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar">
|
||||
<!-- <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA..." class="logo"/> -->
|
||||
<img
|
||||
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA+CAYAAAB3NHh5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA4JpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTMyIDc5LjE1OTI4NCwgMjAxNi8wNC8xOS0xMzoxMzo0MCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo4NkEzOEVGMzEwQTQxMUU4QUFBQkY0QTA2QzlEM0MxNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2QUQzQTIzRDE4NTkxMUU4ODE2Q0IwMTY0RjgxQTVGNyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2QUQzQTIzQzE4NTkxMUU4ODE2Q0IwMTY0RjgxQTVGNyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNS41IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOmJhNmE4ZTBmLTI5ZWItNDBlMy05ZWFhLTYzNTdiYjdkMzcwNCIgc3RSZWY6ZG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOmRhMjgyMWMwLTYwYmQtMTE3Yi04ZGU3LWNjZmQ1MDgzNjUxNiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PrVd/GcAAASESURBVHja7FtrSBRRFL7rrg9MMjUzspTSSqUg6GFBmohh9SeIMOhXhBX0KzL80YN+SBD+qJ+RBhFEQogV0RMLKyuiqIig6GlZRmlRavl2O8c9I7dtZ2adM3d9zB742GHuzpn7zZl7zrnn3nF5vV7hJHEh4aLKaxfgeAXAjee0NoDX7ziCjl2yDsANwHbAsQD3mALYA3ii04e9gEZAP6BM5z9nAA2AKos8FwK+1pcX53noxCJAMuPBzQLEATbotEfpnMeHfIiuSzK4vhnw2KA9GJkryGIoz5hvyjlAr0F7e4BzuYBbdPwG8Mvg+m+APmYfh/qgWbiXqWzQpN3fUSyj19hj8gbYKV7Zwsp9hXS8BHBXIhsqiQglYTf9LgbcGQWywxKqG7cAssiy0aMVkWQLpzGVoYfvMWjvBlQwyKKPiWT2MU628DZACkPZa0CGQXs00zHOBnwBrLF4fTbgnb+X7mJ0qN/EH3iZYUVLfKz2sQMwSSZ8kkKFVTkKOKxw/KF1Zkhx26rUaFb5xFTUpjiWRtmQeHSGMiyNGQkTdgrhVKaeJBvycbM4zE2S/vHSpTRv7beo7CEgXiFh7GwTTSetCE5/f8iEMdOaajJFM5KtgHyFhDcC/gBeMfKEeJnwAcDyMTz0FgAqmTowG6zWxnCrA/zVc9lp9TmAcEgLAOE4HCY8zgoAZuI28RPdiuO45umHw9LuIKoWHPlOiY1RNeIETfI5gkWM6YABv/MZFMeHCWONOAFwWRFhrKjkEPFAshMwB/CReZ9LZn/QCM8ErBe+ZQ9VUid8JdpAclH4llO40kmFjBdmTmsXYL4NFQWzIkGXDt7adA8cGlgZzQrGS+OMpABwW2HVwkrbSAWH5gNBa0nBhCUk3TjOQ9BkIp0phyQ9wl6a+dwb56QTiEOmkNa2IgzyzpWA+zZ2oMNiG0eSydLp/l5aj/Qq4VsLyrXh5jgJz9NpK1Bo6UQqHuCCfbVZ2aSP5smbBW+pYx5gh4FDRM+6D/BZEWksQf2ULVxC5g80L8YqyFPx/xrvSOQUjaUSnfYyevWyFVr6t0wY92AsVXizJJNximnfFuFbAVElGHnyNKfVothjDpr4iyjNAgqlVfbSA+GKR7gAMLEIexzA1SMTjnUA4VjZspsAMQxluGqxVqhbFMetjQcB7xk6umXChUZzyCCkFnBcIeGblI3tZ+h4if3UCJczE49EoXbLA87VcWNLBUMHLvjVamO42YZqxljf8tAcjsNOIuxyGuEMGyoLPQr72SP4Ww/T5LBURZ226rweyWUUC+I2mW9jZ3GTeCnjHk0y4fOCNl8yvHQO4/oBYbzJPIaszKmmdsiEa4R+vSkYwe0IRyxee0X4PkEoNPgPfiKQSsmDVcHaXL5HshBH2i2OsauAdXRstNgWacOcvU12WtyPl6xcf53yb01Ub7sIeQHA6zcZKJ7IiYdMth5QNNpxOJ2pZ5rJGMZcGOvbuEq4WifZSWHoD0bSZS99FvCB6QHRcdXptONOuNP0KuuN+wYiZkV/MPJg6Ck77WPLvwIMAHzX4zyhUFlrAAAAAElFTkSuQmCC"
|
||||
alt="LCEPL Logo"
|
||||
class="logo"
|
||||
/>
|
||||
<h2>Print Report</h2>
|
||||
<a href="/">Dashboard</a>
|
||||
<a href="/upload_excel">Upload Excel</a>
|
||||
<a href="/generate_report_page">Print Report</a>
|
||||
<a href="/filter_tasks">Filter Tasks</a>
|
||||
</div>
|
||||
|
||||
<div class="header">LAXMI CIVIL ENGINEERING SERVICES PVT. LTD.</div>
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
LAXMI CIVIL ENGINEERING SERVICES PVT. LTD.
|
||||
</div>
|
||||
|
||||
<h1 class="report_title">Generate Task Report</h1>
|
||||
<!-- Main -->
|
||||
<div class="main">
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<script>
|
||||
alert("{{ message }}");
|
||||
</script>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
<div class="card">
|
||||
<div class="title">Generate Task Report</div>
|
||||
|
||||
<form action="/report_excel" method="GET">
|
||||
<form action="/report_excel" method="GET">
|
||||
|
||||
<!-- District -->
|
||||
<div class="form-group">
|
||||
<label for="district">District</label>
|
||||
<label>District</label>
|
||||
<select name="district" id="district" required>
|
||||
<option value="" disabled selected>Select District</option>
|
||||
<option value="">Select District</option>
|
||||
{% for d in districts %}
|
||||
<option value="{{ d }}">{{ d }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Block -->
|
||||
<div class="form-group">
|
||||
<label for="block">Block</label>
|
||||
<label>Block</label>
|
||||
<select name="block" id="block" required>
|
||||
<option value="" disabled selected>Select Block</option>
|
||||
<option value="">Select Block</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Main Task -->
|
||||
<div class="form-group">
|
||||
<label for="main_task">Main Task</label>
|
||||
<!-- Task -->
|
||||
<label>Main Task</label>
|
||||
<select name="main_task" id="main_task" required>
|
||||
<option value="" disabled selected>Select Main Task</option>
|
||||
<option value="">Select Main Task</option>
|
||||
</select>
|
||||
|
||||
<button class="btn">Generate Report</button>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<button type="submit">Generate Report</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
<!-- jQuery -->
|
||||
<!-- JS -->
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
|
||||
<!-- Select2 -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/js/select2.min.js"></script>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
|
||||
$('#district').select2({ width: '100%' });
|
||||
$('#block').select2({ width: '100%' });
|
||||
$('#main_task').select2({ width: '100%' });
|
||||
$('#district, #block, #main_task').select2({ width: '100%' });
|
||||
|
||||
// District → Blocks
|
||||
// District → Block
|
||||
$('#district').on('change', function () {
|
||||
|
||||
const district = $(this).val();
|
||||
|
||||
$('#block').empty().append('<option value="">Select Block</option>');
|
||||
@@ -198,43 +218,26 @@ $(document).ready(function () {
|
||||
|
||||
if (!district) return;
|
||||
|
||||
$.ajax({
|
||||
url: '/get_blocks_by_district',
|
||||
method: 'GET',
|
||||
data: { district: district },
|
||||
success: function (response) {
|
||||
|
||||
response.blocks.forEach(block => {
|
||||
$('#block').append(`<option value="${block}">${block}</option>`);
|
||||
$.get('/get_blocks_by_district', { district: district }, function (res) {
|
||||
res.blocks.forEach(b => {
|
||||
$('#block').append(`<option value="${b}">${b}</option>`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// Block → Tasks
|
||||
// Block → Task
|
||||
$('#block').on('change', function () {
|
||||
|
||||
const block = $(this).val();
|
||||
|
||||
$('#main_task').empty().append('<option value="">Select Main Task</option>');
|
||||
|
||||
if (!block) return;
|
||||
|
||||
$.ajax({
|
||||
url: '/get_tasks_by_block',
|
||||
method: 'GET',
|
||||
data: { block: block },
|
||||
success: function (response) {
|
||||
|
||||
response.tasks.forEach(task => {
|
||||
$('#main_task').append(`<option value="${task}">${task}</option>`);
|
||||
$.get('/get_tasks_by_block', { block: block }, function (res) {
|
||||
res.tasks.forEach(t => {
|
||||
$('#main_task').append(`<option value="${t}">${t}</option>`);
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,190 +1,196 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Tasks</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Tasks</title>
|
||||
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
text-align: center;
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 90%;
|
||||
margin: 10px auto;
|
||||
border-collapse: collapse;
|
||||
background-color: #fff;
|
||||
font-size: 0.85em;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
margin-left: 285px;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.main-task-row {
|
||||
background-color: #e3f2fd;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.subtask-row td {
|
||||
background-color: #fefefe;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 10px;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.button-container a,
|
||||
.button-container button {
|
||||
padding: 10px 20px;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
background-color: #007bff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button-container a {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
.top-buttons {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
.top-buttons button {
|
||||
padding: 8px;
|
||||
font-size: 12px;
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.top-buttons button:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
.static-text {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.edit-field {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#searchBox {
|
||||
width: 300px;
|
||||
padding: 8px;
|
||||
margin: 0 auto 10px;
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#taskTableWrapper {
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
margin: 15px auto;
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #007bff;
|
||||
border-radius: 50%;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
animation: spin 0.8s linear infinite;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Sidebar Styling */
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
background-color: #f4f4f4;
|
||||
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
|
||||
padding: 20px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.sidebar .logo {
|
||||
width: 60px;
|
||||
height: auto;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.sidebar a {
|
||||
display: block;
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
padding: 10px 15px;
|
||||
margin: 10px 0;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sidebar a:hover {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
.header {
|
||||
width: 100%;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 15px 0;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: #f1f5f9;
|
||||
}
|
||||
|
||||
</style>
|
||||
/* Sidebar */
|
||||
.sidebar {
|
||||
width: 240px;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
background: linear-gradient(180deg, #0f172a, #1e293b);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.sidebar .logo {
|
||||
width: 60px;
|
||||
display: block;
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
|
||||
.sidebar a {
|
||||
display: block;
|
||||
color: #cbd5f5;
|
||||
text-decoration: none;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.sidebar a:hover {
|
||||
background: rgba(59,130,246,0.2);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
margin-left: 270px;
|
||||
height: 60px;
|
||||
background: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 20px;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
/* Main */
|
||||
.main {
|
||||
margin-left: 280px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Card */
|
||||
.card {
|
||||
background: white;
|
||||
padding: 0px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 25px rgba(0,0,0,0.05);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.card-item{
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 25px rgba(0,0,0,0.05);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
/* Buttons */
|
||||
.btn {
|
||||
padding: 10px 16px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: #22c55e;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #ef4444;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Search */
|
||||
.search-box {
|
||||
width: 300px;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ddd;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
/* Table */
|
||||
.table-wrapper {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.main-task-row {
|
||||
background: #e0f2fe;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.subtask-row {
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
/* Editable */
|
||||
.static-text {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.edit-field {
|
||||
display: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Loader */
|
||||
.spinner {
|
||||
border: 4px solid #eee;
|
||||
border-top: 4px solid #3b82f6;
|
||||
border-radius: 50%;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
animation: spin 0.8s linear infinite;
|
||||
margin: auto;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
|
||||
.sidebar .logo {
|
||||
width: 60px;
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
max-height: 670px; /* scroll area height */
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
thead th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: #3b82f6;
|
||||
z-index: 10;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar">
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar">
|
||||
<img
|
||||
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA+CAYAAAB3NHh5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA4JpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTMyIDc5LjE1OTI4NCwgMjAxNi8wNC8xOS0xMzoxMzo0MCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo4NkEzOEVGMzEwQTQxMUU4QUFBQkY0QTA2QzlEM0MxNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2QUQzQTIzRDE4NTkxMUU4ODE2Q0IwMTY0RjgxQTVGNyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2QUQzQTIzQzE4NTkxMUU4ODE2Q0IwMTY0RjgxQTVGNyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNS41IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOmJhNmE4ZTBmLTI5ZWItNDBlMy05ZWFhLTYzNTdiYjdkMzcwNCIgc3RSZWY6ZG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOmRhMjgyMWMwLTYwYmQtMTE3Yi04ZGU3LWNjZmQ1MDgzNjUxNiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PrVd/GcAAASESURBVHja7FtrSBRRFL7rrg9MMjUzspTSSqUg6GFBmohh9SeIMOhXhBX0KzL80YN+SBD+qJ+RBhFEQogV0RMLKyuiqIig6GlZRmlRavl2O8c9I7dtZ2adM3d9zB742GHuzpn7zZl7zrnn3nF5vV7hJHEh4aLKaxfgeAXAjee0NoDX7ziCjl2yDsANwHbAsQD3mALYA3ii04e9gEZAP6BM5z9nAA2AKos8FwK+1pcX53noxCJAMuPBzQLEATbotEfpnMeHfIiuSzK4vhnw2KA9GJkryGIoz5hvyjlAr0F7e4BzuYBbdPwG8Mvg+m+APmYfh/qgWbiXqWzQpN3fUSyj19hj8gbYKV7Zwsp9hXS8BHBXIhsqiQglYTf9LgbcGQWywxKqG7cAssiy0aMVkWQLpzGVoYfvMWjvBlQwyKKPiWT2MU628DZACkPZa0CGQXs00zHOBnwBrLF4fTbgnb+X7mJ0qN/EH3iZYUVLfKz2sQMwSSZ8kkKFVTkKOKxw/KF1Zkhx26rUaFb5xFTUpjiWRtmQeHSGMiyNGQkTdgrhVKaeJBvycbM4zE2S/vHSpTRv7beo7CEgXiFh7GwTTSetCE5/f8iEMdOaajJFM5KtgHyFhDcC/gBeMfKEeJnwAcDyMTz0FgAqmTowG6zWxnCrA/zVc9lp9TmAcEgLAOE4HCY8zgoAZuI28RPdiuO45umHw9LuIKoWHPlOiY1RNeIETfI5gkWM6YABv/MZFMeHCWONOAFwWRFhrKjkEPFAshMwB/CReZ9LZn/QCM8ErBe+ZQ9VUid8JdpAclH4llO40kmFjBdmTmsXYL4NFQWzIkGXDt7adA8cGlgZzQrGS+OMpABwW2HVwkrbSAWH5gNBa0nBhCUk3TjOQ9BkIp0phyQ9wl6a+dwb56QTiEOmkNa2IgzyzpWA+zZ2oMNiG0eSydLp/l5aj/Qq4VsLyrXh5jgJz9NpK1Bo6UQqHuCCfbVZ2aSP5smbBW+pYx5gh4FDRM+6D/BZEWksQf2ULVxC5g80L8YqyFPx/xrvSOQUjaUSnfYyevWyFVr6t0wY92AsVXizJJNximnfFuFbAVElGHnyNKfVothjDpr4iyjNAgqlVfbSA+GKR7gAMLEIexzA1SMTjnUA4VjZspsAMQxluGqxVqhbFMetjQcB7xk6umXChUZzyCCkFnBcIeGblI3tZ+h4if3UCJczE49EoXbLA87VcWNLBUMHLvjVamO42YZqxljf8tAcjsNOIuxyGuEMGyoLPQr72SP4Ww/T5LBURZ226rweyWUUC+I2mW9jZ3GTeCnjHk0y4fOCNl8yvHQO4/oBYbzJPIaszKmmdsiEa4R+vSkYwe0IRyxee0X4PkEoNPgPfiKQSsmDVcHaXL5HshBH2i2OsauAdXRstNgWacOcvU12WtyPl6xcf53yb01Ub7sIeQHA6zcZKJ7IiYdMth5QNNpxOJ2pZ5rJGMZcGOvbuEq4WifZSWHoD0bSZS99FvCB6QHRcdXptONOuNP0KuuN+wYiZkV/MPJg6Ck77WPLvwIMAHzX4zyhUFlrAAAAAElFTkSuQmCC"
|
||||
alt="LCEPL Logo"
|
||||
@@ -193,83 +199,70 @@
|
||||
<a href="/">Dashboard</a>
|
||||
<a href="/upload_excel">Upload Excel</a>
|
||||
<a href="/generate_report_page">Print Report</a>
|
||||
<!--<a href="/tasks">Show Tasks</a>-->
|
||||
<a href="/filter_tasks">Filter Tasks</a>
|
||||
</div>
|
||||
<div class="header">LAXMI CIVIL ENGINEERING SERVICES PVT. LTD.</div>
|
||||
<h1>Uploaded Tasks</h1>
|
||||
</div>
|
||||
|
||||
<div class="top-buttons">
|
||||
<button onclick="window.location.href='/'">Home Page</button>
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
LAXMI CIVIL ENGINEERING SERVICES PVT. LTD.
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="card-item">
|
||||
<button class="btn btn-success" onclick="window.location.href='/generate_report_page'">
|
||||
Print Report
|
||||
</button>
|
||||
<button class="btn btn-primary" onclick="toggleEditMode()">Edit</button>
|
||||
<button class="btn btn-danger" onclick="cancelEditMode()" id="cancelBtn" style="display:none;">Cancel</button>
|
||||
<button class="btn btn-primary" onclick="submitChangedFields()">Save</button>
|
||||
</div>
|
||||
|
||||
<div class="button-container">
|
||||
<a href="/generate_report_page">Print Task Wise Report</a>
|
||||
<button onclick="toggleEditMode()">Edit Tasks</button>
|
||||
<button onclick="cancelEditMode()" id="cancelBtn" style="display: none; background-color: #dc3545;">Cancel Edit</button>
|
||||
<button onclick="submitChangedFields()">Submit Updates</button>
|
||||
</div>
|
||||
<!-- Search -->
|
||||
<input type="text" id="searchBox" class="search-box" placeholder="Search tasks...">
|
||||
|
||||
<div>
|
||||
<input type="text" id="searchBox" placeholder="Search tasks..." />
|
||||
</div>
|
||||
|
||||
{% if work_details %}
|
||||
<h2>Work Details</h2>
|
||||
<table class="work-details">
|
||||
<tr><th>Name of Work</th><td>{{ work_details.name_of_work }}</td></tr>
|
||||
<tr><th>Cover Agreement No</th><td>{{ work_details.cover_agreement_no }}</td></tr>
|
||||
<tr><th>Name of Contractor</th><td>{{ work_details.name_of_contractor }}</td></tr>
|
||||
<tr><th>Name of TPI Agency</th><td>{{ work_details.name_of_tpi_agency }}</td></tr>
|
||||
<tr><th>Name of Division</th><td>{{ work_details.name_of_division }}</td></tr>
|
||||
<tr><th>Name of District</th><td>{{ work_details.district }}</td></tr>
|
||||
<tr><th>Village</th><td>{{ work_details.name_of_village }}</td></tr>
|
||||
<tr><th>Block</th><td>{{ work_details.block }}</td></tr>
|
||||
<tr><th>Scheme ID</th><td>{{ work_details.scheme_id }}</td></tr>
|
||||
<tr><th>Measurement Book</th><td>{{ work_details.measurement_book }}</td></tr>
|
||||
<tr><th>Date of Billing</th><td>{{ work_details.date_of_billing }}</td></tr>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
<h2>Task Details</h2>
|
||||
<!-- Table -->
|
||||
<div class="card table-wrapper">
|
||||
<div class="spinner" id="loader"></div>
|
||||
|
||||
<div id="taskTableWrapper" style="opacity: 1;">
|
||||
<form id="task-form">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Main Task</th>
|
||||
<th>Task</th>
|
||||
<th>Unit</th>
|
||||
<th>Qty</th>
|
||||
<th>Rate</th>
|
||||
<th>BOQ Amt</th>
|
||||
<th>Prev Billed Qty</th>
|
||||
<th>Prev Bill Amt</th>
|
||||
<th>RA Bill Qty</th>
|
||||
<th>RA Bill Amt</th>
|
||||
<th>Cum Billed Qty</th>
|
||||
<th>Cum Billed Amt</th>
|
||||
<th>BOQ</th>
|
||||
<th>Prev Qty</th>
|
||||
<th>Prev Amt</th>
|
||||
<th>RA Qty</th>
|
||||
<th>RA Amt</th>
|
||||
<th>Cum Qty</th>
|
||||
<th>Cum Amt</th>
|
||||
<th>Var Qty</th>
|
||||
<th>Var Amt</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for group in grouped_tasks %}
|
||||
<tr class="main-task-row">
|
||||
{% for field in ["task_name", "unit", "qty", "rate", "boq_amount", "previous_billed_qty", "previous_billing_amount", "in_this_ra_bill_qty", "in_this_ra_billing_amount", "cumulative_billed_qty", "cumulative_billed_amount", "variation_qty", "variation_amount"] %}
|
||||
{% for field in ["task_name","unit","qty","rate","boq_amount","previous_billed_qty","previous_billing_amount","in_this_ra_bill_qty","in_this_ra_billing_amount","cumulative_billed_qty","cumulative_billed_amount","variation_qty","variation_amount"] %}
|
||||
<td>
|
||||
<span class="static-text">{{ group[field] }}</span>
|
||||
<input type="text" class="edit-field" name="{{ field }}_{{ group.id }}" value="{{ group[field] }}" data-original-value="{{ group[field] | trim }}" style="display: none;" />
|
||||
<input type="text" class="edit-field" name="{{ field }}_{{ group.id }}" value="{{ group[field] }}" data-original-value="{{ group[field] }}">
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
|
||||
{% for sub in group.subtasks %}
|
||||
<tr class="subtask-row">
|
||||
{% for field in ["task_name", "unit", "qty", "rate", "boq_amount", "previous_billed_qty", "previous_billing_amount", "in_this_ra_bill_qty", "in_this_ra_billing_amount", "cumulative_billed_qty", "cumulative_billed_amount", "variation_qty", "variation_amount"] %}
|
||||
{% for field in ["task_name","unit","qty","rate","boq_amount","previous_billed_qty","previous_billing_amount","in_this_ra_bill_qty","in_this_ra_billing_amount","cumulative_billed_qty","cumulative_billed_amount","variation_qty","variation_amount"] %}
|
||||
<td>
|
||||
<span class="static-text">{{ sub[field] }}</span>
|
||||
<input type="text" class="edit-field" name="{{ field }}_{{ sub.id }}" value="{{ sub[field] }}" data-original-value="{{ sub[field] | trim }}" style="display: none;" />
|
||||
<input type="text" class="edit-field" name="{{ field }}_{{ sub.id }}" value="{{ sub[field] }}" data-original-value="{{ sub[field] }}">
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
@@ -280,87 +273,50 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
</div>
|
||||
|
||||
|
||||
function toggleEditMode() {
|
||||
document.querySelectorAll('.static-text').forEach(span => span.style.display = 'none');
|
||||
document.querySelectorAll('.edit-field').forEach(input => input.style.display = 'inline-block');
|
||||
<script>
|
||||
function toggleEditMode() {
|
||||
document.querySelectorAll('.static-text').forEach(e => e.style.display = 'none');
|
||||
document.querySelectorAll('.edit-field').forEach(e => e.style.display = 'block');
|
||||
document.getElementById('cancelBtn').style.display = 'inline-block';
|
||||
}
|
||||
}
|
||||
|
||||
function cancelEditMode() {
|
||||
document.querySelectorAll('.static-text').forEach(span => span.style.display = 'inline-block');
|
||||
document.querySelectorAll('.edit-field').forEach(input => {
|
||||
input.style.display = 'none';
|
||||
input.value = input.getAttribute('data-original-value');
|
||||
function cancelEditMode() {
|
||||
document.querySelectorAll('.static-text').forEach(e => e.style.display = 'inline');
|
||||
document.querySelectorAll('.edit-field').forEach(e => {
|
||||
e.style.display = 'none';
|
||||
e.value = e.dataset.originalValue;
|
||||
});
|
||||
document.getElementById('cancelBtn').style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function submitChangedFields() {
|
||||
function submitChangedFields() {
|
||||
const updates = {};
|
||||
document.querySelectorAll('.edit-field').forEach(input => {
|
||||
const original = input.getAttribute('data-original-value') || '';
|
||||
const current = input.value.trim();
|
||||
if (current !== original.trim()) {
|
||||
updates[input.name] = current;
|
||||
if (input.value !== input.dataset.originalValue) {
|
||||
updates[input.name] = input.value;
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(updates).length === 0) {
|
||||
alert("No changes made.");
|
||||
return;
|
||||
}
|
||||
if (!Object.keys(updates).length) return alert("No changes");
|
||||
|
||||
fetch('/update_tasks', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: JSON.stringify(updates)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
alert(data.message || 'Update complete!');
|
||||
window.location.reload();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Update error:', error);
|
||||
alert('Error updating tasks');
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const searchBox = document.getElementById("searchBox");
|
||||
const tableWrapper = document.getElementById("taskTableWrapper");
|
||||
const loader = document.getElementById("loader");
|
||||
|
||||
document.querySelectorAll("tbody tr").forEach(row => {
|
||||
const text = Array.from(row.querySelectorAll("td")).map(cell => {
|
||||
const span = cell.querySelector(".static-text")?.innerText || '';
|
||||
const input = cell.querySelector(".edit-field")?.value || '';
|
||||
return (span + " " + input).toLowerCase();
|
||||
}).join(" ");
|
||||
row.setAttribute("data-search-text", text);
|
||||
});
|
||||
|
||||
let timeout;
|
||||
searchBox.addEventListener("input", function () {
|
||||
clearTimeout(timeout);
|
||||
loader.style.display = "block";
|
||||
tableWrapper.style.opacity = "0.3";
|
||||
}).then(res => res.json())
|
||||
.then(() => location.reload());
|
||||
}
|
||||
|
||||
/* Search */
|
||||
document.getElementById("searchBox").addEventListener("input", function () {
|
||||
const term = this.value.toLowerCase();
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
document.querySelectorAll("tbody tr").forEach(row => {
|
||||
const searchable = row.getAttribute("data-search-text");
|
||||
row.style.display = searchable.includes(term) ? "" : "none";
|
||||
row.style.display = row.innerText.toLowerCase().includes(term) ? "" : "none";
|
||||
});
|
||||
loader.style.display = "none";
|
||||
tableWrapper.style.opacity = "1";
|
||||
}, 150);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,166 +1,184 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Upload Excel</title>
|
||||
<style>
|
||||
/* General Styling */
|
||||
*{
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<title>Upload Excel</title>
|
||||
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 50vh;
|
||||
background-color: #f4f7f9;
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
/* Sidebar Styling */
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
background-color: #f4f4f4;
|
||||
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
|
||||
padding: 20px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.sidebar .logo {
|
||||
width: 60px;
|
||||
height: auto;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.sidebar a {
|
||||
display: block;
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
padding: 10px 15px;
|
||||
margin: 10px 0;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sidebar a:hover {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Main Container Styling */
|
||||
.main-content {
|
||||
margin: 400px auto;
|
||||
/* margin-left: 270px; */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
width: calc(100% - 270px);
|
||||
}
|
||||
|
||||
form {
|
||||
background-color: #ffffff;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
width: 90%;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 2rem;
|
||||
color: #007bff;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
input[type="file"] {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
input[type="file"]:focus {
|
||||
border-color: #007bff;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.upload {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background-color: #007bff;
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
.header {
|
||||
width: 100%;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 15px 0;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: #f1f5f9;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar">
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar {
|
||||
width: 240px;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
background: linear-gradient(180deg, #0f172a, #1e293b);
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar .logo {
|
||||
width: 60px;
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
|
||||
.sidebar h2 {
|
||||
color: white;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.sidebar a {
|
||||
color: #cbd5f5;
|
||||
text-decoration: none;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.sidebar a:hover {
|
||||
background: rgba(59,130,246,0.2);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
margin-left: 270px;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 20px;
|
||||
background: white;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
.main-content {
|
||||
margin-left: 270px;
|
||||
height: calc(100vh - 60px);
|
||||
}
|
||||
|
||||
/* Upload Card */
|
||||
.upload-card {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
margin: 150px auto;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 10px 25px rgba(0,0,0,0.08);
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.upload-card h1 {
|
||||
font-size: 20px;
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
/* File Input */
|
||||
.file-input {
|
||||
border: 2px dashed #cbd5f5;
|
||||
padding: 25px;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
margin-bottom: 20px;
|
||||
transition: 0.3s;
|
||||
color: #555;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.file-input:hover {
|
||||
border-color: #3b82f6;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.file-input input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Button */
|
||||
.upload-btn {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: linear-gradient(135deg, #3b82f6, #2563eb);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
margin-top: 40px;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.upload-btn:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="sidebar">
|
||||
<img
|
||||
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA+CAYAAAB3NHh5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA4JpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTMyIDc5LjE1OTI4NCwgMjAxNi8wNC8xOS0xMzoxMzo0MCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo4NkEzOEVGMzEwQTQxMUU4QUFBQkY0QTA2QzlEM0MxNyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2QUQzQTIzRDE4NTkxMUU4ODE2Q0IwMTY0RjgxQTVGNyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2QUQzQTIzQzE4NTkxMUU4ODE2Q0IwMTY0RjgxQTVGNyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNS41IE1hY2ludG9zaCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOmJhNmE4ZTBmLTI5ZWItNDBlMy05ZWFhLTYzNTdiYjdkMzcwNCIgc3RSZWY6ZG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOmRhMjgyMWMwLTYwYmQtMTE3Yi04ZGU3LWNjZmQ1MDgzNjUxNiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PrVd/GcAAASESURBVHja7FtrSBRRFL7rrg9MMjUzspTSSqUg6GFBmohh9SeIMOhXhBX0KzL80YN+SBD+qJ+RBhFEQogV0RMLKyuiqIig6GlZRmlRavl2O8c9I7dtZ2adM3d9zB742GHuzpn7zZl7zrnn3nF5vV7hJHEh4aLKaxfgeAXAjee0NoDX7ziCjl2yDsANwHbAsQD3mALYA3ii04e9gEZAP6BM5z9nAA2AKos8FwK+1pcX53noxCJAMuPBzQLEATbotEfpnMeHfIiuSzK4vhnw2KA9GJkryGIoz5hvyjlAr0F7e4BzuYBbdPwG8Mvg+m+APmYfh/qgWbiXqWzQpN3fUSyj19hj8gbYKV7Zwsp9hXS8BHBXIhsqiQglYTf9LgbcGQWywxKqG7cAssiy0aMVkWQLpzGVoYfvMWjvBlQwyKKPiWT2MU628DZACkPZa0CGQXs00zHOBnwBrLF4fTbgnb+X7mJ0qN/EH3iZYUVLfKz2sQMwSSZ8kkKFVTkKOKxw/KF1Zkhx26rUaFb5xFTUpjiWRtmQeHSGMiyNGQkTdgrhVKaeJBvycbM4zE2S/vHSpTRv7beo7CEgXiFh7GwTTSetCE5/f8iEMdOaajJFM5KtgHyFhDcC/gBeMfKEeJnwAcDyMTz0FgAqmTowG6zWxnCrA/zVc9lp9TmAcEgLAOE4HCY8zgoAZuI28RPdiuO45umHw9LuIKoWHPlOiY1RNeIETfI5gkWM6YABv/MZFMeHCWONOAFwWRFhrKjkEPFAshMwB/CReZ9LZn/QCM8ErBe+ZQ9VUid8JdpAclH4llO40kmFjBdmTmsXYL4NFQWzIkGXDt7adA8cGlgZzQrGS+OMpABwW2HVwkrbSAWH5gNBa0nBhCUk3TjOQ9BkIp0phyQ9wl6a+dwb56QTiEOmkNa2IgzyzpWA+zZ2oMNiG0eSydLp/l5aj/Qq4VsLyrXh5jgJz9NpK1Bo6UQqHuCCfbVZ2aSP5smbBW+pYx5gh4FDRM+6D/BZEWksQf2ULVxC5g80L8YqyFPx/xrvSOQUjaUSnfYyevWyFVr6t0wY92AsVXizJJNximnfFuFbAVElGHnyNKfVothjDpr4iyjNAgqlVfbSA+GKR7gAMLEIexzA1SMTjnUA4VjZspsAMQxluGqxVqhbFMetjQcB7xk6umXChUZzyCCkFnBcIeGblI3tZ+h4if3UCJczE49EoXbLA87VcWNLBUMHLvjVamO42YZqxljf8tAcjsNOIuxyGuEMGyoLPQr72SP4Ww/T5LBURZ226rweyWUUC+I2mW9jZ3GTeCnjHk0y4fOCNl8yvHQO4/oBYbzJPIaszKmmdsiEa4R+vSkYwe0IRyxee0X4PkEoNPgPfiKQSsmDVcHaXL5HshBH2i2OsauAdXRstNgWacOcvU12WtyPl6xcf53yb01Ub7sIeQHA6zcZKJ7IiYdMth5QNNpxOJ2pZ5rJGMZcGOvbuEq4WifZSWHoD0bSZS99FvCB6QHRcdXptONOuNP0KuuN+wYiZkV/MPJg6Ck77WPLvwIMAHzX4zyhUFlrAAAAAElFTkSuQmCC"
|
||||
alt="LCEPL Logo"
|
||||
class="logo"
|
||||
/>
|
||||
<h2>Upload Excel</h2>
|
||||
|
||||
<a href="/">Dashboard</a>
|
||||
<a href="/upload_excel">Upload Excel</a>
|
||||
<a href="/generate_report_page">Print Report</a>
|
||||
<!--<a href="/tasks">Show Tasks</a>-->
|
||||
<a href="/filter_tasks">Filter Tasks</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-content">
|
||||
<form action="/upload" method="post" enctype="multipart/form-data">
|
||||
<div class="header">LAXMI CIVIL ENGINEERING SERVICES PVT. LTD.</div>
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
LAXMI CIVIL ENGINEERING SERVICES PVT. LTD.
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="main-content">
|
||||
|
||||
<div class="upload-card">
|
||||
<h1>Upload Excel File</h1>
|
||||
<input type="file" name="file" required />
|
||||
<button class="upload" type="submit">Upload</button>
|
||||
|
||||
<form action="/upload" method="post" enctype="multipart/form-data">
|
||||
|
||||
<!-- FILE INPUT -->
|
||||
<label class="file-input">
|
||||
<span id="fileText">📁 Click to choose Excel file</span>
|
||||
<input type="file" name="file" id="fileInput" required>
|
||||
</label>
|
||||
|
||||
<!-- BUTTON -->
|
||||
<button type="submit" class="upload-btn">
|
||||
Upload File
|
||||
</button>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- JS -->
|
||||
<script>
|
||||
document.getElementById("fileInput").addEventListener("change", function() {
|
||||
const fileName = this.files.length > 0 ? this.files[0].name : "Click to choose Excel file";
|
||||
document.getElementById("fileText").innerText = "📄 " + fileName;
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
2
run.py
2
run.py
@@ -7,4 +7,4 @@ app.config['MAX_CONTENT_LENGTH'] = 64 * 1024 * 1024
|
||||
if __name__ == '__main__':
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
app.run(host='0.0.0.0', port=5002, debug=True)
|
||||
app.run(host='0.0.0.0', port=5001, debug=True)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user