| <!DOCTYPE html> |
| <html lang="pt-br"> |
|
|
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>XLSX File Reader & Editor</title> |
| |
| <link href="https://cdn.jsdelivr.net/npm/bootstrap@5/dist/css/bootstrap.min.css" rel="stylesheet"> |
| |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1/font/bootstrap-icons.css"> |
| |
| <script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script> |
| |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script> |
| <style> |
| .dropzone { |
| border: 2px dashed #0d6efd; |
| transition: all 0.3s ease; |
| cursor: pointer; |
| } |
| |
| .dropzone.active { |
| border-color: #198754; |
| background-color: rgba(25, 135, 84, 0.05); |
| } |
| |
| .table-container { |
| |
| overflow-y: auto; |
| } |
| |
| .editable-cell { |
| min-width: 120px; |
| border: 1px solid #dee2e6; |
| padding: 0.5rem; |
| } |
| |
| .editable-cell:focus { |
| outline: 2px solid #0d6efd; |
| background-color: rgba(13, 110, 253, 0.1); |
| } |
| |
| .sheet-tab { |
| cursor: pointer; |
| padding: 0.5rem 1rem; |
| border-radius: 0.375rem; |
| margin-right: 0.5rem; |
| background-color: #f8f9fa; |
| transition: all 0.2s ease; |
| } |
| |
| .sheet-tab:hover { |
| background-color: #e9ecef; |
| } |
| |
| .sheet-tab.active { |
| background-color: #0d6efd; |
| color: white; |
| } |
| |
| .file-info { |
| background-color: #f8f9fa; |
| border-radius: 0.375rem; |
| padding: 1rem; |
| } |
| |
| .how-to-use-card { |
| height: 100%; |
| } |
| </style> |
| </head> |
|
|
| <body class="bg-light"> |
| <div class="container py-5"> |
| <div class="text-center mb-5"> |
| <h1 class="display-5 fw-bold text-dark mb-3">Excel File Reader & Editor</h1> |
| <p class="lead text-muted">Upload, view, edit and export Excel files directly in your browser</p> |
| </div> |
|
|
| <div class="shadow-sm mb-5"> |
| <div class=""> |
| <div class="row"> |
| |
| |
| <div class="col-md"> |
| <div id="dropzone" class="dropzone rounded-0 p-3 text-center mb-4"> |
| <div class="d-flex flex-column align-items-center justify-content-center"> |
| <i class="bi bi-file-earmark-excel text-primary fs-1 mb-3"></i> |
| <p class="text-muted mb-2">Drag & drop your Excel file here</p> |
| <p class="text-muted small mb-3">or</p> |
| |
| |
| |
| <input type="file" id="fileInput" class="d-none" accept=".xlsx, .xls, .csv" /> |
| </div> |
| </div> |
|
|
| |
| </div> |
| <div class="col-md-3"> |
| <h5 class="fw-bold mb-3">File Information</h5> |
| <div class="file-info"> |
| <div class="d-flex align-items-center mb-2"> |
| <i class="bi bi-file-earmark text-muted me-2"></i> |
| <span id="fileName" class="text-dark">No file selected</span> |
| </div> |
| <div class="d-flex align-items-center mb-2"> |
| <i class="bi bi-collection text-muted me-2"></i> |
| <span id="sheetCount" class="text-dark">0 sheets</span> |
| </div> |
| <div class="d-flex align-items-center"> |
| <i class="bi bi-table text-muted me-2"></i> |
| <span id="rowCount" class="text-dark">0 rows</span> |
| </div> |
| </div> |
| </div> |
| |
|
|
| |
| <div class="col-ms"> |
| <div id="sheetTabs" class="d-flex overflow-auto py-2 mb-3"> |
| |
| </div> |
|
|
| |
| <div id="tableContainer" class="table-container border rounded-0 overflow-hidden"> |
| <div class="text-center py-5 text-muted" id="emptyState"> |
| <i class="bi bi-file-earmark-excel display-4 opacity-25 mb-3"></i> |
| <p class="h5">Upload an Excel file to get started</p> |
| </div> |
| <table id="dataTable" class="table table-bordered m-0 d-none overflow-auto"> |
| |
| </table> |
| </div> |
|
|
| |
| <div class="d-flex flex-wrap gap-2 mt-4"> |
| <button id="exportBtn" class="btn btn-success" disabled> |
| <i class="bi bi-download me-2"></i> Export Excel |
| </button> |
| <button id="addRowBtn" class="btn btn-primary" disabled> |
| <i class="bi bi-plus me-2"></i> Add Row |
| </button> |
| <button id="addColBtn" class="btn btn-primary" disabled> |
| <i class="bi bi-plus me-2"></i> Add Column |
| </button> |
| <button id="resetBtn" class="btn btn-secondary"> |
| <i class="bi bi-arrow-counterclockwise me-2"></i> Reset |
| </button> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="card shadow-sm"> |
| <div class="card-body p-4"> |
| <h2 class="h4 fw-bold mb-4">How to use</h2> |
| <div class="row g-4"> |
| <div class="col-md-4"> |
| <div class="card bg-primary bg-opacity-10 how-to-use-card"> |
| <div class="card-body"> |
| <div class="d-flex align-items-center mb-3"> |
| <div class="bg-primary bg-opacity-25 p-2 rounded-circle me-3"> |
| <i class="bi bi-upload text-primary"></i> |
| </div> |
| <h3 class="h6 fw-bold mb-0 text-primary">Upload</h3> |
| </div> |
| <p class="card-text text-muted">Drag & drop or click to upload your Excel file (.xlsx, .xls, .csv)</p> |
| </div> |
| </div> |
| </div> |
| <div class="col-md-4"> |
| <div class="card bg-success bg-opacity-10 how-to-use-card"> |
| <div class="card-body"> |
| <div class="d-flex align-items-center mb-3"> |
| <div class="bg-success bg-opacity-25 p-2 rounded-circle me-3"> |
| <i class="bi bi-pencil text-success"></i> |
| </div> |
| <h3 class="h6 fw-bold mb-0 text-success">Edit</h3> |
| </div> |
| <p class="card-text text-muted">Click on any cell to edit its content. Add/remove rows and columns as needed.</p> |
| </div> |
| </div> |
| </div> |
| <div class="col-md-4"> |
| <div class="card bg-info bg-opacity-10 how-to-use-card"> |
| <div class="card-body"> |
| <div class="d-flex align-items-center mb-3"> |
| <div class="bg-info bg-opacity-25 p-2 rounded-circle me-3"> |
| <i class="bi bi-download text-info"></i> |
| </div> |
| <h3 class="h6 fw-bold mb-0 text-info">Export</h3> |
| </div> |
| <p class="card-text text-muted">Download your edited file back to Excel format when you're done.</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <script src="https://cdn.jsdelivr.net/npm/bootstrap@5/dist/js/bootstrap.bundle.min.js"></script> |
|
|
| <script> |
| document.addEventListener("DOMContentLoaded", function () { |
| |
| const fileInput = document.getElementById("fileInput"); |
| const dropzone = document.getElementById("dropzone"); |
| const fileName = document.getElementById("fileName"); |
| const sheetCount = document.getElementById("sheetCount"); |
| const rowCount = document.getElementById("rowCount"); |
| const tableContainer = document.getElementById("tableContainer"); |
| const dataTable = document.getElementById("dataTable"); |
| const emptyState = document.getElementById("emptyState"); |
| const sheetTabs = document.getElementById("sheetTabs"); |
| const exportBtn = document.getElementById("exportBtn"); |
| const addRowBtn = document.getElementById("addRowBtn"); |
| const addColBtn = document.getElementById("addColBtn"); |
| const resetBtn = document.getElementById("resetBtn"); |
| |
| |
| let workbook = null; |
| let currentSheetName = ""; |
| let sheetData = []; |
| let headers = []; |
| |
| |
| fileInput.addEventListener("change", handleFileSelect); |
| dropzone.addEventListener("click", () => fileInput.click()); |
| dropzone.addEventListener("dragover", handleDragOver); |
| dropzone.addEventListener("dragleave", handleDragLeave); |
| dropzone.addEventListener("drop", handleDrop); |
| exportBtn.addEventListener("click", exportToExcel); |
| addRowBtn.addEventListener("click", addNewRow); |
| addColBtn.addEventListener("click", addNewColumn); |
| resetBtn.addEventListener("click", resetApp); |
| |
| |
| function handleFileSelect(e) { |
| const file = e.target.files[0]; |
| if (!file) return; |
| |
| processExcelFile(file); |
| } |
| |
| function handleDragOver(e) { |
| e.preventDefault(); |
| e.stopPropagation(); |
| dropzone.classList.add("active"); |
| } |
| |
| function handleDragLeave(e) { |
| e.preventDefault(); |
| e.stopPropagation(); |
| dropzone.classList.remove("active"); |
| } |
| |
| function handleDrop(e) { |
| e.preventDefault(); |
| e.stopPropagation(); |
| dropzone.classList.remove("active"); |
| |
| const file = e.dataTransfer.files[0]; |
| if (!file) return; |
| |
| processExcelFile(file); |
| } |
| |
| function processExcelFile(file) { |
| fileName.textContent = file.name; |
| |
| const reader = new FileReader(); |
| reader.onload = function (e) { |
| const data = new Uint8Array(e.target.result); |
| workbook = XLSX.read(data, { type: "array" }); |
| |
| updateSheetInfo(); |
| renderSheetTabs(); |
| loadFirstSheet(); |
| enableButtons(); |
| }; |
| reader.readAsArrayBuffer(file); |
| } |
| |
| function updateSheetInfo() { |
| if (!workbook) return; |
| |
| sheetCount.textContent = `${workbook.SheetNames.length} sheet${ |
| workbook.SheetNames.length !== 1 ? "s" : "" |
| }`; |
| } |
| |
| function renderSheetTabs() { |
| if (!workbook) return; |
| |
| sheetTabs.innerHTML = ""; |
| workbook.SheetNames.forEach((sheetName) => { |
| const tab = document.createElement("div"); |
| tab.className = "sheet-tab"; |
| tab.textContent = sheetName; |
| tab.addEventListener("click", () => switchSheet(sheetName)); |
| |
| if (sheetName === workbook.SheetNames[0]) { |
| tab.classList.add("active"); |
| } |
| |
| sheetTabs.appendChild(tab); |
| }); |
| } |
| |
| function switchSheet(sheetName) { |
| if (!workbook || currentSheetName === sheetName) return; |
| |
| |
| document.querySelectorAll(".sheet-tab").forEach((tab) => { |
| tab.classList.remove("active"); |
| if (tab.textContent === sheetName) { |
| tab.classList.add("active"); |
| } |
| }); |
| |
| currentSheetName = sheetName; |
| loadSheetData(); |
| renderTable(); |
| } |
| |
| function loadFirstSheet() { |
| if (!workbook || workbook.SheetNames.length === 0) return; |
| |
| currentSheetName = workbook.SheetNames[0]; |
| loadSheetData(); |
| renderTable(); |
| } |
| |
| function loadSheetData() { |
| if (!workbook || !currentSheetName) return; |
| |
| const worksheet = workbook.Sheets[currentSheetName]; |
| sheetData = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: "" }); |
| |
| if (sheetData.length > 0) { |
| headers = sheetData[0]; |
| rowCount.textContent = `${sheetData.length - 1} row${ |
| sheetData.length - 1 !== 1 ? "s" : "" |
| }`; |
| } else { |
| headers = []; |
| rowCount.textContent = "0 rows"; |
| } |
| } |
| |
| function renderTable() { |
| if (!sheetData || sheetData.length === 0) { |
| emptyState.classList.remove("d-none"); |
| dataTable.classList.add("d-none"); |
| return; |
| } |
| |
| emptyState.classList.add("d-none"); |
| dataTable.classList.remove("d-none"); |
| |
| |
| dataTable.innerHTML = ""; |
| |
| |
| const thead = document.createElement("thead"); |
| const headerRow = document.createElement("tr"); |
| headerRow.className = "table-light"; |
| |
| |
| const cornerCell = document.createElement("th"); |
| cornerCell.className = "text-center"; |
| cornerCell.style.padding = "0px 45px"; |
| headerRow.appendChild(cornerCell); |
| |
| |
| headers.forEach((header, colIndex) => { |
| const th = document.createElement("th"); |
| th.className = "text-nowrap"; |
| th.textContent = header || `Column ${colIndex + 1}`; |
| |
| |
| const deleteBtn = document.createElement("button"); |
| deleteBtn.className = "btn btn-sm btn-link text-danger ms-2 p-0"; |
| deleteBtn.innerHTML = '<i class="bi bi-x"></i>'; |
| deleteBtn.addEventListener("click", () => deleteColumn(colIndex)); |
| |
| th.appendChild(deleteBtn); |
| headerRow.appendChild(th); |
| }); |
| |
| |
| const newColHeader = document.createElement("th"); |
| newColHeader.className = "text-center"; |
| newColHeader.style.width = "50px"; |
| |
| const addColBtn = document.createElement("button"); |
| addColBtn.className = "btn btn-sm btn-link text-primary p-0"; |
| addColBtn.innerHTML = '<i class="bi bi-plus"></i>'; |
| addColBtn.addEventListener("click", addNewColumn); |
| |
| newColHeader.appendChild(addColBtn); |
| headerRow.appendChild(newColHeader); |
| |
| thead.appendChild(headerRow); |
| dataTable.appendChild(thead); |
| |
| |
| const tbody = document.createElement("tbody"); |
| |
| |
| for (let rowIndex = 1; rowIndex < sheetData.length; rowIndex++) { |
| const row = document.createElement("tr"); |
| |
| |
| const rowNumCell = document.createElement("td"); |
| rowNumCell.className = "text-center text-muted"; |
| rowNumCell.textContent = rowIndex; |
| |
| |
| const deleteRowBtn = document.createElement("button"); |
| deleteRowBtn.className = "btn btn-sm btn-link text-danger ms-2 p-0"; |
| deleteRowBtn.innerHTML = '<i class="bi bi-x"></i>'; |
| deleteRowBtn.addEventListener("click", () => deleteRow(rowIndex)); |
| |
| rowNumCell.appendChild(deleteRowBtn); |
| row.appendChild(rowNumCell); |
| |
| |
| for (let colIndex = 0; colIndex < headers.length; colIndex++) { |
| const cell = document.createElement("td"); |
| cell.className = "editable-cell"; |
| cell.contentEditable = true; |
| |
| |
| const cellValue = |
| sheetData[rowIndex][colIndex] !== undefined |
| ? sheetData[rowIndex][colIndex] |
| : ""; |
| cell.textContent = cellValue; |
| |
| |
| cell.addEventListener("blur", () => { |
| if (!sheetData[rowIndex]) { |
| sheetData[rowIndex] = new Array(headers.length).fill(""); |
| } |
| sheetData[rowIndex][colIndex] = cell.textContent; |
| }); |
| |
| row.appendChild(cell); |
| } |
| |
| |
| const newColCell = document.createElement("td"); |
| newColCell.className = "text-center"; |
| row.appendChild(newColCell); |
| |
| tbody.appendChild(row); |
| } |
| |
| |
| const newRow = document.createElement("tr"); |
| const newRowCell = document.createElement("td"); |
| newRowCell.colSpan = headers.length + 2; |
| newRowCell.className = "text-center py-3"; |
| |
| const addRowBtn = document.createElement("button"); |
| addRowBtn.className = "btn btn-sm btn-primary"; |
| addRowBtn.innerHTML = '<i class="bi bi-plus me-2"></i>Add Row'; |
| addRowBtn.addEventListener("click", addNewRow); |
| |
| newRowCell.appendChild(addRowBtn); |
| newRow.appendChild(newRowCell); |
| tbody.appendChild(newRow); |
| |
| dataTable.appendChild(tbody); |
| } |
| |
| function addNewRow() { |
| if (!sheetData) return; |
| |
| const newRow = new Array(headers.length).fill(""); |
| sheetData.push(newRow); |
| rowCount.textContent = `${sheetData.length - 1} row${ |
| sheetData.length - 1 !== 1 ? "s" : "" |
| }`; |
| renderTable(); |
| } |
| |
| function addNewColumn() { |
| if (!headers) return; |
| |
| const newHeader = `Column ${headers.length + 1}`; |
| headers.push(newHeader); |
| |
| |
| for (let i = 0; i < sheetData.length; i++) { |
| sheetData[i].push(""); |
| } |
| |
| renderTable(); |
| } |
| |
| function deleteRow(rowIndex) { |
| if (!sheetData || rowIndex < 1 || rowIndex >= sheetData.length) return; |
| |
| sheetData.splice(rowIndex, 1); |
| rowCount.textContent = `${sheetData.length - 1} row${ |
| sheetData.length - 1 !== 1 ? "s" : "" |
| }`; |
| renderTable(); |
| } |
| |
| function deleteColumn(colIndex) { |
| if (!headers || colIndex < 0 || colIndex >= headers.length) return; |
| |
| headers.splice(colIndex, 1); |
| |
| |
| for (let i = 0; i < sheetData.length; i++) { |
| if (sheetData[i].length > colIndex) { |
| sheetData[i].splice(colIndex, 1); |
| } |
| } |
| |
| renderTable(); |
| } |
| |
| function exportToExcel() { |
| if (!workbook || !currentSheetName) return; |
| |
| |
| const worksheet = XLSX.utils.aoa_to_sheet(sheetData); |
| workbook.Sheets[currentSheetName] = worksheet; |
| |
| |
| const excelBuffer = XLSX.write(workbook, { |
| bookType: "xlsx", |
| type: "array" |
| }); |
| const blob = new Blob([excelBuffer], { |
| type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" |
| }); |
| |
| |
| let exportName = "edited_" + (fileName.textContent || "spreadsheet"); |
| if (!exportName.endsWith(".xlsx")) { |
| exportName += ".xlsx"; |
| } |
| |
| |
| saveAs(blob, exportName); |
| } |
| |
| function resetApp() { |
| |
| fileName.textContent = "No file selected"; |
| sheetCount.textContent = "0 sheets"; |
| rowCount.textContent = "0 rows"; |
| |
| emptyState.classList.remove("d-none"); |
| dataTable.classList.add("d-none"); |
| sheetTabs.innerHTML = ""; |
| |
| |
| workbook = null; |
| currentSheetName = ""; |
| sheetData = []; |
| headers = []; |
| |
| |
| exportBtn.disabled = true; |
| addRowBtn.disabled = true; |
| addColBtn.disabled = true; |
| |
| |
| fileInput.value = ""; |
| } |
| |
| function enableButtons() { |
| exportBtn.disabled = false; |
| addRowBtn.disabled = false; |
| addColBtn.disabled = false; |
| } |
| }); |
| |
| </script> |
| </body> |
|
|
| </html> |