Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <title>Intelligent Partial Invoice Matching</title> | |
| <!-- Bootstrap CSS --> | |
| <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"> | |
| <!-- Font Awesome for icons --> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> | |
| <!-- Google Fonts --> | |
| <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;800;900&display=swap" rel="stylesheet"> | |
| <style> | |
| /* Your existing CSS variables and styles */ | |
| :root { | |
| --primary-color: #4a90e2; | |
| --secondary-color: #2c5282; | |
| --success-color: #48bb78; | |
| --danger-color: #e53e3e; | |
| --background-light: #f7fafc; | |
| --background-dark: #1a202c; | |
| --text-light: #2d3748; | |
| --text-dark: #f7fafc; | |
| --card-light: #ffffff; | |
| --card-dark: #2d3748; | |
| --input-light: #f8fafc; | |
| --input-dark: #2d3748; | |
| --border-light: #e2e8f0; | |
| --border-dark: #4a5568; | |
| --spacing-unit: 1rem; | |
| --border-radius: 12px; | |
| --transition-speed: 0.3s; | |
| } | |
| body { | |
| font-family: 'Poppins', sans-serif; | |
| margin: 0; | |
| min-height: 100vh; | |
| transition: background-color var(--transition-speed), color var(--transition-speed); | |
| background: var(--background-light); | |
| color: var(--text-light); | |
| padding: calc(var(--spacing-unit) * 2); | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| } | |
| body.dark { | |
| background: var(--background-dark); | |
| color: var(--text-dark); | |
| } | |
| .container { | |
| max-width: 1400px; | |
| width: 100%; | |
| padding: calc(var(--spacing-unit) * 2); | |
| margin: 0 auto; | |
| } | |
| h1 { | |
| font-size: clamp(2.5rem, 5vw, 4rem); | |
| font-weight: 900; | |
| text-align: center; | |
| margin: calc(var(--spacing-unit) * 4) 0; | |
| padding: calc(var(--spacing-unit) * 2); | |
| line-height: 1.1; | |
| letter-spacing: -0.02em; | |
| background: linear-gradient(135deg, #2563eb, #7c3aed); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| transform: scale(1); | |
| transition: transform 0.3s ease; | |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.1); | |
| animation: fadeInDown 1.2s ease-out; | |
| } | |
| .theme-toggle { | |
| position: fixed; | |
| top: 20px; | |
| right: 20px; | |
| background: linear-gradient(135deg, #2563eb, #7c3aed); | |
| color: white; | |
| border: none; | |
| border-radius: 50%; | |
| width: 45px; | |
| height: 45px; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: all var(--transition-speed); | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.15); | |
| z-index: 1000; | |
| } | |
| .theme-toggle:hover { | |
| transform: rotate(180deg) scale(1.1); | |
| box-shadow: 0 6px 16px rgba(0,0,0,0.2); | |
| } | |
| .upload-container, .table-container { | |
| background: var(--card-light); | |
| padding: calc(var(--spacing-unit) * 3); | |
| border-radius: var(--border-radius); | |
| box-shadow: 0 8px 32px rgba(0,0,0,0.1); | |
| margin-bottom: calc(var(--spacing-unit) * 4); | |
| transition: transform var(--transition-speed), box-shadow var(--transition-speed); | |
| animation: fadeIn 1s ease-out; | |
| } | |
| body.dark .upload-container, | |
| body.dark .table-container { | |
| background: var(--card-dark); | |
| box-shadow: 0 8px 32px rgba(0,0,0,0.2); | |
| } | |
| .upload-container:hover, | |
| .table-container:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 12px 48px rgba(0,0,0,0.15); | |
| } | |
| .form-group { | |
| margin-bottom: calc(var(--spacing-unit) * 2); | |
| } | |
| .form-control { | |
| border-radius: var(--border-radius); | |
| border: 2px solid var(--border-light); | |
| padding: calc(var(--spacing-unit) * 1.5); | |
| transition: all var(--transition-speed); | |
| background: var(--input-light); | |
| color: var(--text-light); | |
| height: auto; | |
| } | |
| body.dark .form-control { | |
| background: var(--input-dark); | |
| border-color: var(--border-dark); | |
| color: var(--text-dark); | |
| } | |
| .form-control:focus { | |
| border-color: #2563eb; | |
| box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2); | |
| } | |
| .form-control::file-selector-button { | |
| padding: 8px 16px; | |
| border-radius: 8px; | |
| border: none; | |
| background: linear-gradient(135deg, #2563eb, #7c3aed); | |
| color: white; | |
| margin-right: 16px; | |
| transition: all 0.3s; | |
| } | |
| .form-control::file-selector-button:hover { | |
| background: linear-gradient(135deg, #1d4ed8, #6d28d9); | |
| transform: translateY(-1px); | |
| } | |
| .btn-custom { | |
| padding: calc(var(--spacing-unit) * 1.5) calc(var(--spacing-unit) * 3); | |
| border-radius: var(--border-radius); | |
| font-weight: 600; | |
| transition: all var(--transition-speed); | |
| position: relative; | |
| overflow: hidden; | |
| border: none; | |
| background: linear-gradient(135deg, #2563eb, #7c3aed); | |
| color: white; | |
| } | |
| .btn-custom::after { | |
| content: ''; | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| width: 0; | |
| height: 0; | |
| background: rgba(255,255,255,0.2); | |
| border-radius: 50%; | |
| transform: translate(-50%, -50%); | |
| transition: width 0.6s, height 0.6s; | |
| } | |
| .btn-custom:hover::after { | |
| width: 300%; | |
| height: 300%; | |
| } | |
| .btn-custom:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 12px rgba(0,0,0,0.2); | |
| } | |
| .table { | |
| margin-top: calc(var(--spacing-unit) * 2); | |
| } | |
| .table thead th { | |
| background: linear-gradient(135deg, #2563eb, #7c3aed); | |
| color: white; | |
| font-weight: 600; | |
| padding: calc(var(--spacing-unit) * 1.5); | |
| border: none; | |
| } | |
| .table tbody tr { | |
| transition: background-color var(--transition-speed); | |
| color: var(--text-light); | |
| } | |
| body.dark .table tbody tr { | |
| color: var(--text-dark); | |
| } | |
| .table tbody tr:hover { | |
| background-color: rgba(37, 99, 235, 0.1); | |
| } | |
| .edit-btn { | |
| background: linear-gradient(135deg, #dc2626, #ef4444) ; | |
| color: white ; | |
| padding: calc(var(--spacing-unit) * 0.75) calc(var(--spacing-unit) * 1.5); | |
| font-size: 0.9rem; | |
| border-radius: var(--border-radius); | |
| border: none; | |
| } | |
| .edit-btn:hover { | |
| background: linear-gradient(135deg, #b91c1c, #dc2626) ; | |
| transform: translateY(-2px); | |
| } | |
| .frozen-badge { | |
| padding: calc(var(--spacing-unit) * 0.75) calc(var(--spacing-unit) * 1.5); | |
| border-radius: var(--border-radius); | |
| background: linear-gradient(135deg, #059669, #10b981); | |
| color: white; | |
| } | |
| .modal-content { | |
| border-radius: var(--border-radius); | |
| overflow: hidden; | |
| } | |
| .modal-header { | |
| background: linear-gradient(135deg, #dc2626, #ef4444); | |
| color: white; | |
| border: none; | |
| padding: calc(var(--spacing-unit) * 2); | |
| } | |
| .modal-title { | |
| color: white; | |
| } | |
| .modal-body { | |
| padding: calc(var(--spacing-unit) * 3); | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(20px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| @keyframes fadeInDown { | |
| from { opacity: 0; transform: translateY(-50px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| /* Dark mode specific styles */ | |
| body.dark .table { | |
| color: var(--text-dark); | |
| } | |
| body.dark .form-control { | |
| background: var(--card-dark); | |
| } | |
| body.dark .modal-content { | |
| background: var(--card-dark); | |
| color: var(--text-dark); | |
| } | |
| body.dark .modal-body { | |
| color: var(--text-dark); | |
| } | |
| body.dark .close { | |
| color: white; | |
| } | |
| body.dark label { | |
| color: var(--text-dark); | |
| } | |
| .btn-success { | |
| background: linear-gradient(135deg, #059669, #10b981) ; | |
| border: none ; | |
| } | |
| .btn-info { | |
| background: linear-gradient(135deg, #0284c7, #0ea5e9) ; | |
| border: none ; | |
| } | |
| .btn-secondary { | |
| background: linear-gradient(135deg, #4b5563, #6b7280) ; | |
| border: none ; | |
| } | |
| .btn-danger { | |
| background: linear-gradient(135deg, #dc2626, #ef4444) ; | |
| border: none ; | |
| } | |
| /* ===================================================== | |
| Updated Clock Loader CSS (Purple Clock with Moving Hands) | |
| ====================================================== */ | |
| .loader-container { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0, 0, 0, 0.7); | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 9999; | |
| } | |
| .clock-loader { | |
| text-align: center; | |
| } | |
| .clock-face { | |
| width: 120px; | |
| height: 120px; | |
| border: 8px solid #7c3aed; | |
| border-radius: 50%; | |
| position: relative; | |
| background: rgba(124, 58, 237, 0.1); | |
| margin: 0 auto 20px; | |
| box-shadow: 0 0 20px rgba(124, 58, 237, 0.3); | |
| } | |
| /* Positioning each hand so its bottom is at the center of the clock-face */ | |
| .hand { | |
| position: absolute; | |
| left: 50%; | |
| bottom: 50%; | |
| transform-origin: bottom; | |
| transform: translateX(-50%) rotate(0deg); | |
| } | |
| .hour-hand { | |
| width: 4px; | |
| height: 25px; | |
| background: #7c3aed; | |
| animation: rotate-hour 8s linear infinite; | |
| } | |
| .minute-hand { | |
| width: 3px; | |
| height: 35px; | |
| background: #7c3aed; | |
| animation: rotate-minute 6s linear infinite; | |
| } | |
| .second-hand { | |
| width: 2px; | |
| height: 45px; | |
| background: #2563eb; | |
| animation: rotate-second 2s linear infinite; | |
| } | |
| .center-dot { | |
| width: 12px; | |
| height: 12px; | |
| background: #7c3aed; | |
| border-radius: 50%; | |
| position: absolute; | |
| top: calc(50% - 6px); | |
| left: calc(50% - 6px); | |
| box-shadow: 0 0 10px rgba(124, 58, 237, 0.5); | |
| } | |
| /* Optional tick marks on the clock-face */ | |
| .tick { | |
| position: absolute; | |
| width: 3px; | |
| height: 10px; | |
| background: #7c3aed; | |
| top: 4px; | |
| left: calc(50% - 1.5px); | |
| transform-origin: 50% 60px; | |
| } | |
| .tick-1 { transform: rotate(0deg); } | |
| .tick-2 { transform: rotate(90deg); } | |
| .tick-3 { transform: rotate(180deg); } | |
| .tick-4 { transform: rotate(270deg); } | |
| @keyframes rotate-hour { | |
| from { transform: translateX(-50%) rotate(0deg); } | |
| to { transform: translateX(-50%) rotate(360deg); } | |
| } | |
| @keyframes rotate-minute { | |
| from { transform: translateX(-50%) rotate(0deg); } | |
| to { transform: translateX(-50%) rotate(360deg); } | |
| } | |
| @keyframes rotate-second { | |
| from { transform: translateX(-50%) rotate(0deg); } | |
| to { transform: translateX(-50%) rotate(360deg); } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Theme toggle button --> | |
| <button class="theme-toggle" onclick="toggleTheme()"> | |
| <i class="fas fa-moon"></i> | |
| </button> | |
| <!-- Loader Container (hidden by default) --> | |
| <div class="loader-container" id="loader-container" style="display: none;"> | |
| <div class="clock-loader"> | |
| <div class="clock-face"> | |
| <div class="hand hour-hand"></div> | |
| <div class="hand minute-hand"></div> | |
| <div class="hand second-hand"></div> | |
| <div class="center-dot"></div> | |
| <div class="tick tick-1"></div> | |
| <div class="tick tick-2"></div> | |
| <div class="tick tick-3"></div> | |
| <div class="tick tick-4"></div> | |
| </div> | |
| <div class="loading-text" style="color: #fff; font-size: 18px; margin-top: 20px; font-weight: 500;">Processing Invoices...</div> | |
| </div> | |
| </div> | |
| <div class="container mx-auto"> | |
| <h1>Intelligent Partial Invoice Matching</h1> | |
| <div class="upload-container"> | |
| <form id="uploadForm" method="post" enctype="multipart/form-data"> | |
| <div class="form-group"> | |
| <label for="file1">Upload First Dataset (CSV/Excel):</label> | |
| <input type="file" class="form-control" id="file1" name="file1" required> | |
| </div> | |
| <div class="form-group"> | |
| <label for="file2">Upload Second Dataset (CSV/Excel):</label> | |
| <input type="file" class="form-control" id="file2" name="file2" required> | |
| </div> | |
| <button type="submit" class="btn btn-primary btn-custom btn-block"> | |
| <span>Process Invoices</span> | |
| </button> | |
| </form> | |
| </div> | |
| {% if results %} | |
| <div class="table-container"> | |
| <h2 class="text-center font-bold mb-4">Matched Invoices Preview</h2> | |
| <div class="table-responsive"> | |
| <table class="table table-striped table-bordered" id="resultsTable"> | |
| <thead> | |
| <tr> | |
| <th>Invoice Number 1</th> | |
| <th>Invoice Number 2</th> | |
| <th>Similarity Score</th> | |
| <th>Manual Review Status</th> | |
| <th>Recommendation</th> | |
| <th>Reason</th> | |
| <th>Comments</th> | |
| <th class="action-btn">Action</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {% for row in results %} | |
| <tr data-index="{{ loop.index0 }}"> | |
| <td class="invoice_number1">{{ row.invoice_number1 }}</td> | |
| <td class="invoice_number2">{{ row.invoice_number2 }}</td> | |
| <td class="similarity_score">{{ row.similarity_score }}</td> | |
| <td class="manual_review_status"> | |
| {% if row.recommendation == "Exact Match" %} | |
| No | |
| {% else %} | |
| Needs Review | |
| {% endif %} | |
| </td> | |
| <td class="recommendation">{{ row.recommendation }}</td> | |
| <td class="reason">{{ row.reason }}</td> | |
| <td class="comments">{{ row.comments }}</td> | |
| <td class="action-btn"> | |
| <div class="text-center"> | |
| {% if row.recommendation == "Exact Match" %} | |
| <button class="btn btn-success btn-custom freeze-btn" disabled style="padding: 10px 20px;filter: brightness(1.3);"> | |
| <i class="fas fa-lock"></i> Freeze | |
| </button> | |
| {% else %} | |
| <input type="checkbox" class="select-review-checkbox" data-index="{{ loop.index0 }}" style="width:30px; height:30px;"> | |
| {% endif %} | |
| </div> | |
| </td> | |
| </tr> | |
| {% endfor %} | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <div class="row mb-4"> | |
| <div class="col-md-4 mb-3"> | |
| <a href="{{ url_for('download_csv') }}" class="btn btn-success btn-custom btn-block"> | |
| <i class="fas fa-file-csv"></i> Download CSV Report | |
| </a> | |
| </div> | |
| <div class="col-md-4 mb-3"> | |
| <a href="{{ url_for('download_excel') }}" class="btn btn-info btn-custom btn-block"> | |
| <i class="fas fa-file-excel"></i> Download Excel Report | |
| </a> | |
| </div> | |
| <div class="col-md-4 mb-3"> | |
| <!-- New Download Summary Stats Button --> | |
| <button id="downloadStatsBtn" class="btn btn-secondary btn-custom btn-block"> | |
| <i class="fas fa-chart-bar"></i> Download Summary Stats | |
| </button> | |
| </div> | |
| </div> | |
| <div class="text-center mb-5"> | |
| <button id="saveUpdates" class="btn btn-primary btn-custom"> | |
| <i class="fas fa-save"></i> Save All Updates | |
| </button> | |
| </div> | |
| {% endif %} | |
| </div> | |
| <!-- Edit Modal --> | |
| <div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true"> | |
| <div class="modal-dialog modal-dialog-centered"> | |
| <div class="modal-content"> | |
| <div class="modal-header"> | |
| <h5 class="modal-title" id="editModalLabel"> | |
| <i class="fas fa-edit"></i> Edit Invoice Match | |
| </h5> | |
| <button type="button" class="close text-white" data-dismiss="modal" aria-label="Close"> | |
| <span aria-hidden="true">×</span> | |
| </button> | |
| </div> | |
| <form id="editForm"> | |
| <div class="modal-body"> | |
| <!-- Non-editable fields --> | |
| <div class="form-group"> | |
| <label>Invoice Number 1:</label> | |
| <p id="modalInvoice1" class="font-weight-bold"></p> | |
| </div> | |
| <div class="form-group"> | |
| <label>Invoice Number 2 (Current):</label> | |
| <p id="modalInvoice2" class="font-weight-bold"></p> | |
| </div> | |
| <div class="form-group"> | |
| <label>Similarity Score:</label> | |
| <p id="modalScore" class="font-weight-bold"></p> | |
| </div> | |
| <!-- New select box for corrected invoice from dataset2 --> | |
| <div class="form-group"> | |
| <label for="modalSelectInvoice2">Select Correct Invoice from Dataset 2:</label> | |
| <select class="form-control" id="modalSelectInvoice2"> | |
| <option value="">-- Select --</option> | |
| {% for inv in unique_values %} | |
| <option value="{{ inv }}">{{ inv }}</option> | |
| {% endfor %} | |
| </select> | |
| </div> | |
| <!-- Editable fields --> | |
| <div class="form-group"> | |
| <label for="modalReviewStatus">Manual Review Status:</label> | |
| <select class="form-control" id="modalReviewStatus" required> | |
| <option value="Needs Review">Needs Review</option> | |
| <option value="No Review Needed">No Review Needed</option> | |
| </select> | |
| </div> | |
| <div class="form-group"> | |
| <label for="modalRecommendation">Recommendation:</label> | |
| <select class="form-control" id="modalRecommendation" required> | |
| <option value="Unmatched">Unmatched</option> | |
| <option value="Partial Match">Partial Match</option> | |
| <option value="Exact Match">Exact Match</option> | |
| </select> | |
| </div> | |
| <div class="form-group"> | |
| <label for="modalReason">Reason:</label> | |
| <textarea class="form-control" id="modalReason" rows="3" required></textarea> | |
| </div> | |
| <div class="form-group"> | |
| <label for="modalComments">Comments:</label> | |
| <textarea class="form-control" id="modalComments" rows="2"></textarea> | |
| </div> | |
| <!-- Hidden index --> | |
| <input type="hidden" id="modalRowIndex"> | |
| </div> | |
| <div class="modal-footer"> | |
| <button type="button" class="btn btn-secondary btn-custom" data-dismiss="modal"> | |
| <i class="fas fa-times"></i> Cancel | |
| </button> | |
| <button type="submit" class="btn btn-danger btn-custom"> | |
| <i class="fas fa-save"></i> Save Changes | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- jQuery, Popper.js, Bootstrap JS --> | |
| <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"></script> | |
| <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js"></script> | |
| <script> | |
| // Theme toggle functionality | |
| function toggleTheme() { | |
| const body = document.body; | |
| const themeToggle = document.querySelector('.theme-toggle i'); | |
| body.classList.toggle('dark'); | |
| if (body.classList.contains('dark')) { | |
| themeToggle.classList.remove('fa-moon'); | |
| themeToggle.classList.add('fa-sun'); | |
| localStorage.setItem('theme', 'dark'); | |
| } else { | |
| themeToggle.classList.remove('fa-sun'); | |
| themeToggle.classList.add('fa-moon'); | |
| localStorage.setItem('theme', 'light'); | |
| } | |
| } | |
| // Check for saved theme preference | |
| const savedTheme = localStorage.getItem('theme'); | |
| if (savedTheme === 'dark') { | |
| document.body.classList.add('dark'); | |
| document.querySelector('.theme-toggle i').classList.replace('fa-moon', 'fa-sun'); | |
| } | |
| // New JS: Download Summary Stats (Excel & JSON) when button is clicked | |
| document.getElementById('downloadStatsBtn').addEventListener('click', function() { | |
| // Trigger Excel stats download | |
| const excelLink = document.createElement('a'); | |
| excelLink.href = "{{ url_for('download_stats_excel') }}"; | |
| excelLink.style.display = 'none'; | |
| document.body.appendChild(excelLink); | |
| excelLink.click(); | |
| document.body.removeChild(excelLink); | |
| // Trigger JSON stats download | |
| const jsonLink = document.createElement('a'); | |
| jsonLink.href = "{{ url_for('download_stats_json') }}"; | |
| jsonLink.style.display = 'none'; | |
| document.body.appendChild(jsonLink); | |
| jsonLink.click(); | |
| document.body.removeChild(jsonLink); | |
| }); | |
| // Populate modal with row data when an edit button is clicked | |
| $(document).on("click", ".edit-btn", function() { | |
| var rowIndex = $(this).data("index"); | |
| var row = $("#resultsTable tbody tr").eq(rowIndex); | |
| var invoice1 = row.find(".invoice_number1").text().trim(); | |
| var invoice2 = row.find(".invoice_number2").text().trim(); | |
| var score = row.find(".similarity_score").text().trim(); | |
| var reviewStatus = row.find(".manual_review_status").text().trim(); | |
| var recommendation = row.find(".recommendation").text().trim(); | |
| var reason = row.find(".reason").text().trim(); | |
| var comments = row.find(".comments").text().trim(); | |
| $("#modalRowIndex").val(rowIndex); | |
| $("#modalInvoice1").text(invoice1); | |
| $("#modalInvoice2").text(invoice2); | |
| $("#modalScore").text(score); | |
| $("#modalReviewStatus").val(reviewStatus); | |
| $("#modalRecommendation").val(recommendation); | |
| $("#modalReason").val(reason); | |
| $("#modalComments").val(comments); | |
| // Reset the select box | |
| $("#modalSelectInvoice2").val(""); | |
| }); | |
| // Updated edit form submit handler: feedback is sent to the server, which returns recalculated values. | |
| $("#editForm").on("submit", function(e) { | |
| e.preventDefault(); | |
| var rowIndex = $("#modalRowIndex").val(); | |
| var selectedInvoice2 = $("#modalSelectInvoice2").val(); | |
| var newComments = $("#modalComments").val(); | |
| var row = $("#resultsTable tbody tr").eq(rowIndex); | |
| var invoice1 = row.find(".invoice_number1").text().trim(); | |
| // Send feedback to the server | |
| $.ajax({ | |
| url: "{{ url_for('save_feedback') }}", | |
| type: "POST", | |
| contentType: "application/json", | |
| data: JSON.stringify({ | |
| invoice_number1: invoice1, | |
| selected_invoice2: selectedInvoice2, | |
| comments: newComments | |
| }), | |
| success: function(response) { | |
| alert(response.message); | |
| }, | |
| error: function(xhr, status, error) { | |
| alert("Error saving feedback: " + error); | |
| } | |
| }); | |
| $("#editModal").modal("hide"); | |
| }); | |
| // When "Save All Updates" is clicked, update each row's action cell based on its type. | |
| $("#saveUpdates").on("click", function() { | |
| var updatedData = []; | |
| $("#resultsTable tbody tr").each(function() { | |
| var row = $(this); | |
| var rec = row.find(".recommendation").text().trim(); | |
| var actionCell = row.find("td.action-btn"); | |
| var rowIndex = row.data("index"); | |
| // For Exact Match, always freeze | |
| if(rec === "Exact Match") { | |
| row.find(".manual_review_status").text("No"); | |
| actionCell.html('<div class="text-center"><button class="btn btn-success btn-custom freeze-btn" disabled style="padding: 10px 20px;filter: brightness(1.3);"><i class="fas fa-lock"></i> Freeze</button></div>'); | |
| } else { | |
| // For Partial Match or Unmatched, check the checkbox state. | |
| var checkbox = row.find("input.select-review-checkbox"); | |
| if(checkbox.length > 0) { | |
| if(checkbox.is(":checked")) { | |
| // If checked, auto-freeze this row. | |
| row.find(".manual_review_status").text("No"); | |
| actionCell.html('<div class="text-center"><button class="btn btn-success btn-custom freeze-btn" disabled style="padding: 10px 20px;filter: brightness(1.3);"><i class="fas fa-lock"></i> Freeze</button></div>'); | |
| } else { | |
| // If not checked, allow manual editing. | |
| actionCell.html('<button class="btn btn-sm edit-btn btn-custom" data-index="'+ rowIndex +'" data-toggle="modal" data-target="#editModal"><i class="fas fa-edit"></i> Edit</button>'); | |
| } | |
| } | |
| } | |
| updatedData.push({ | |
| invoice_number1: row.find(".invoice_number1").text().trim(), | |
| invoice_number2: row.find(".invoice_number2").text().trim(), | |
| similarity_score: parseFloat(row.find(".similarity_score").text().trim()), | |
| manual_review_status: row.find(".manual_review_status").text().trim(), | |
| recommendation: row.find(".recommendation").text().trim(), | |
| reason: row.find(".reason").text().trim(), | |
| comments: row.find(".comments").text().trim(), | |
| editable: row.find("button.edit-btn").length > 0 | |
| }); | |
| }); | |
| $.ajax({ | |
| url: "{{ url_for('save_updates') }}", | |
| type: "POST", | |
| contentType: "application/json", | |
| data: JSON.stringify(updatedData), | |
| success: function(response) { | |
| alert("Updates taken successfully!"); | |
| }, | |
| error: function(xhr, status, error) { | |
| alert("Error saving updates: " + error); | |
| } | |
| }); | |
| }); | |
| // Hide loader when processing is complete (if the server sends processing_complete = true) | |
| {% if processing_complete %} | |
| document.getElementById('loader-container').style.display = 'none'; | |
| {% endif %} | |
| // Show loader on file upload submission | |
| document.getElementById('uploadForm').addEventListener('submit', function(e) { | |
| // Show loader | |
| document.getElementById('loader-container').style.display = 'flex'; | |
| // Optional: Disable the submit button to prevent double submission | |
| const submitButton = this.querySelector('button[type="submit"]'); | |
| if (submitButton) { | |
| submitButton.disabled = true; | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |