diff --git a/activity.log b/activity.log deleted file mode 100644 index e69de29..0000000 diff --git a/app.log b/app.log deleted file mode 100644 index e69de29..0000000 diff --git a/controllers/log_controller.py b/controllers/log_controller.py index 145c91b..ab6bd30 100644 --- a/controllers/log_controller.py +++ b/controllers/log_controller.py @@ -21,7 +21,6 @@ def activity_log(): end_date, user_name ) - return render_template( "activity_log.html", logs=filtered_logs, diff --git a/model/FolderAndFile.py b/model/FolderAndFile.py index 65dedef..30f5336 100644 --- a/model/FolderAndFile.py +++ b/model/FolderAndFile.py @@ -27,13 +27,27 @@ class FolderAndFile: os.makedirs(folder, exist_ok=True) return folder - # ----------------------------- - # FILE PATH METHODS - # ----------------------------- + @staticmethod + def get_logs_folder(): + folder = os.path.join(current_app.root_path, "logs") + + if not os.path.exists(folder): + os.makedirs(folder) + + os.makedirs(folder, exist_ok=True) + return folder + + # FILE PATH METHODS - download @staticmethod def get_download_path(filename): return os.path.join(FolderAndFile.get_download_folder(), filename) - + + # FILE PATH METHODS - upload file @staticmethod def get_upload_path(filename): - return os.path.join(FolderAndFile.get_upload_folder(), filename) \ No newline at end of file + return os.path.join(FolderAndFile.get_upload_folder(), filename) + + # FILE PATH METHODS - activity log file + @staticmethod + def get_activity_log_path(filename): + return os.path.join(FolderAndFile.get_logs_folder(), filename) \ No newline at end of file diff --git a/model/Log.py b/model/Log.py index 021e5cc..d3ed22f 100644 --- a/model/Log.py +++ b/model/Log.py @@ -1,29 +1,41 @@ import os -from datetime import datetime from flask import current_app -from flask_login import current_user +from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user +from datetime import datetime + +from model.FolderAndFile import FolderAndFile class LogHelper: @staticmethod def log_action(action, details=""): - """Add a log entry.""" - log_data = LogData() - log_data.add_log(action, details) - + """Log user actions with timestamp, user, action, and details.""" + logData = LogData() + logData.WriteLog(action, details="") class LogData: + filepath = "" + timestamp = None def __init__(self): - self.filepath = os.path.join(current_app.root_path, 'activity.log') + self.filepath = FolderAndFile.get_activity_log_path('activity.log') self.timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - self.user = getattr(current_user, "cn", None) \ - or getattr(current_user, "username", None) \ - or getattr(current_user, "sAMAccountName", "Unknown") + self.user = LogData.get_current_user() - def add_log(self, action, details=""): - """Create/Add a log entry.""" + @staticmethod + def get_current_user(): + if hasattr(current_user, "cn") and current_user.cn: + return current_user.cn + elif hasattr(current_user, "username") and current_user.username: + return current_user.username + elif hasattr(current_user, "sAMAccountName") and current_user.sAMAccountName: + return current_user.sAMAccountName + return "Unknown" + + def WriteLog(self, action, details=""): + """Log user actions with timestamp, user, action, and details.""" + with open(self.filepath, "a", encoding="utf-8") as f: f.write( f"Timestamp: {self.timestamp} | " @@ -32,73 +44,41 @@ class LogData: f"Details: {details}\n" ) - def get_all_logs(self): - """Read all logs.""" + def GetActivitiesLog(self): logs = [] + if os.path.exists(self.filepath): - with open(self.filepath, 'r', encoding="utf-8") as f: + with open(self.filepath, 'r') as f: for line in f: parts = line.strip().split(" | ") if len(parts) == 4: logs.append({ - "timestamp": parts[0].split(":", 1)[1].strip(), - "user": parts[1].split(":", 1)[1].strip(), - "action": parts[2].split(":", 1)[1].strip(), - "details": parts[3].split(":", 1)[1].strip() + "timestamp": parts[0].replace("Timestamp:", "").strip(), + "user": parts[1].replace("User:", "").strip(), + "action": parts[2].replace("Action:", "").strip(), + "details": parts[3].replace("Details:", "").strip() }) return logs - def get_filtered_logs(self, start_date=None, end_date=None, user_name=None): - """Filter logs by date and/or user.""" - logs = self.get_all_logs() + def GetFilteredActivitiesLog(self, startDate, endDate, userName): + filtered_logs = self.GetActivitiesLog() - # Filter by date - if start_date or end_date: - start_dt = datetime.strptime(start_date, "%Y-%m-%d") if start_date else datetime.min - end_dt = datetime.strptime(end_date, "%Y-%m-%d") if end_date else datetime.max - logs = [ - log for log in logs - if start_dt <= datetime.strptime(log["timestamp"], "%Y-%m-%d %H:%M:%S") <= end_dt - ] + # Date filter + if startDate or endDate: + try: + start_dt = datetime.strptime(startDate, "%Y-%m-%d") if startDate else datetime.min + end_dt = datetime.strptime(endDate, "%Y-%m-%d") if endDate else datetime.max - # Filter by username - if user_name: - logs = [log for log in logs if user_name.lower() in log.get("user", "").lower()] - - return logs - - def update_log(self, index, action=None, details=None): - """Update a specific log entry by index (0-based).""" - logs = self.get_all_logs() - if 0 <= index < len(logs): - if action: - logs[index]["action"] = action - if details: - logs[index]["details"] = details - self._rewrite_logs_file(logs) - return True - return False - - def delete_log(self, index): - """Delete a specific log entry by index (0-based).""" - logs = self.get_all_logs() - if 0 <= index < len(logs): - logs.pop(index) - self._rewrite_logs_file(logs) - return True - return False - - # ------------------- INTERNAL HELPER ------------------- - - def _rewrite_logs_file(self, logs): - """Overwrite the log file with current logs.""" - with open(self.filepath, "w", encoding="utf-8") as f: - for log in logs: - f.write( - f"Timestamp: {log['timestamp']} | " - f"User: {log['user']} | " - f"Action: {log['action']} | " - f"Details: {log['details']}\n" - ) + filtered_logs = [ + log for log in filtered_logs + if start_dt <= datetime.strptime(log["timestamp"], "%Y-%m-%d %H:%M:%S") <= end_dt + ] + except Exception as e: + print("Date filter error:", e) + + # Username filter + if userName: + filtered_logs = [log for log in filtered_logs if userName.lower() in log["user"].lower()] + return filtered_logs \ No newline at end of file diff --git a/static/js/search_on_table.js b/static/js/search_on_table.js index de2f886..4b0ab1e 100644 --- a/static/js/search_on_table.js +++ b/static/js/search_on_table.js @@ -1,26 +1,23 @@ - // Search on table using search inpute options function searchTable() { let input = document.getElementById("searchBar").value.toLowerCase(); - let rows = document.querySelectorAll("table tbody tr"); + let tables = document.querySelectorAll("table"); - rows.forEach(row => { - let blockName = row.cells[1].textContent.toLowerCase(); - let districtName = row.cells[2].textContent.toLowerCase(); - let villageName = row.cells[3].textContent.toLowerCase(); + tables.forEach(table => { + let rows = table.querySelectorAll("tr"); - if (blockName.includes(input) || districtName.includes(input)|| villageName.includes(input)) { - row.style.display = ""; - } else { - row.style.display = "none"; - } + rows.forEach((row, index) => { + if (index === 0) return; // header skip + + let text = row.textContent.toLowerCase(); + + row.style.display = text.includes(input) ? "" : "none"; + }); }); } - - // Common Sorting Script for Tables function sortTable(n, dir) { var table, rows, switching, i, x, y, shouldSwitch; @@ -57,14 +54,14 @@ function sortTable(n, dir) { } // Attach sorting functionality to all sortable tables -document.addEventListener("DOMContentLoaded", function() { +document.addEventListener("DOMContentLoaded", function () { // Find all elements with the class "sortable-header" var sortableHeaders = document.querySelectorAll(".sortable-header"); - sortableHeaders.forEach(function(header) { + sortableHeaders.forEach(function (header) { // Attach click event for ascending sort if (header.querySelector(".sort-asc")) { - header.querySelector(".sort-asc").addEventListener("click", function() { + header.querySelector(".sort-asc").addEventListener("click", function () { var columnIndex = Array.from(header.parentNode.children).indexOf(header); sortTable(columnIndex, "asc"); }); @@ -72,7 +69,7 @@ document.addEventListener("DOMContentLoaded", function() { // Attach click event for descending sort if (header.querySelector(".sort-desc")) { - header.querySelector(".sort-desc").addEventListener("click", function() { + header.querySelector(".sort-desc").addEventListener("click", function () { var columnIndex = Array.from(header.parentNode.children).indexOf(header); sortTable(columnIndex, "desc"); }); @@ -105,4 +102,31 @@ document.addEventListener("DOMContentLoaded", function () { displayButton.classList.add("active-button"); addButton.classList.remove("active-button"); }); +}); + +document.addEventListener("DOMContentLoaded", function () { + + let tables = document.querySelectorAll("table"); + + tables.forEach(table => { + let header = table.querySelector("tr:first-child"); + + if (header) { + header.style.position = "sticky"; + header.style.top = "0"; + header.style.background = "#fff"; + header.style.zIndex = "2"; + } + + if (!table.parentElement.classList.contains("table-wrapper")) { + let wrapper = document.createElement("div"); + wrapper.classList.add("table-wrapper"); + wrapper.style.maxHeight = "65vh" + wrapper.style.overflowY = "auto"; + + table.parentNode.insertBefore(wrapper, table); + wrapper.appendChild(table); + } + }); + }); \ No newline at end of file diff --git a/templates/edit_invoice.html b/templates/edit_invoice.html index 6d1c245..f8ab8f8 100644 --- a/templates/edit_invoice.html +++ b/templates/edit_invoice.html @@ -7,6 +7,7 @@ Edit Invoice + @@ -204,15 +205,17 @@ type: "POST", url: $(this).attr("action"), data: $(this).serialize(), + dataType: 'json', // ensure JSON is returned success: function (response) { if (response.status === "success") { - $("#invoiceSuccessAlert").fadeIn().delay(3000).fadeOut(); alert("Invoice updated successfully!"); // <-- Popup alert - } + // ✅ Redirect to Add Invoice page (table part visible) + window.location.href = "{{ url_for('invoice.add_invoice') }}#addTable"; + } }, error: function (xhr) { - alert("Error: " + xhr.responseJSON.message); + alert("Error: " + xhr.responseJSON?.message || "Something went wrong!"); } }); });