athina-excel-wizard / script.js
maralvic's picture
Está no caminho certo. Porém, todos os dados a serem inseridos que estão atualmente nos "Relatório" de n. 2 a 5, devem ser imputados através de uma janela txt para que sejam interpretados e alocados nas posições corretas, sendo uma janela para cada. Se for o caso, solicitar que sejam imputados no formato CSV separados por |. Uma vez inseridos os dados, todos os cálculos deverão ser efetuados pelo sistema, com base nas fórmulas presentes no arquivo xls por mim enviado. Os relatórios preparados como resultado das operações devem ser mostrados na tela.
776de63 verified
let workbook = null;
let processedSheets = {};
document.addEventListener('DOMContentLoaded', () => {
const fileInput = document.getElementById('fileInput');
const loadBtn = document.getElementById('loadBtn');
const updateBtn = document.getElementById('updateBtn');
const generateReportBtn = document.getElementById('generateReportBtn');
const exportExcelBtn = document.getElementById('exportExcelBtn');
const exportCSVBtn = document.getElementById('exportCSVBtn');
const inputFields = document.getElementById('inputFields');
const reportContainer = document.getElementById('reportContainer');
// Load Excel file
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
const data = new Uint8Array(e.target.result);
workbook = XLSX.read(data, { type: 'array' });
loadBtn.disabled = false;
};
reader.readAsArrayBuffer(file);
});
// Process Excel file
loadBtn.addEventListener('click', () => {
if (!workbook) return;
try {
// Process each sheet
workbook.SheetNames.forEach(sheetName => {
const worksheet = workbook.Sheets[sheetName];
const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
processedSheets[sheetName] = {
data: jsonData,
formulas: detectFormulas(worksheet),
editableCells: detectEditableCells(jsonData, worksheet)
};
});
// Generate input fields for editable cells
generateInputFields();
// Enable buttons
updateBtn.disabled = false;
generateReportBtn.disabled = false;
exportExcelBtn.disabled = false;
exportCSVBtn.disabled = false;
// Show success message
showToast('Planilha processada com sucesso!', 'success');
} catch (error) {
console.error('Error processing workbook:', error);
showToast('Erro ao processar a planilha', 'error');
}
});
// Update values
updateBtn.addEventListener('click', () => {
if (!workbook) return;
try {
// Update values in processedSheets
updateWorkbookValues();
// Recalculate formulas
recalculateFormulas();
// Generate reports
generateReports();
showToast('Valores atualizados com sucesso!', 'success');
} catch (error) {
console.error('Error updating values:', error);
showToast('Erro ao atualizar valores', 'error');
}
});
// Generate reports
generateReportBtn.addEventListener('click', () => {
generateReports();
showToast('Relatório gerado com sucesso!', 'success');
});
// Export to Excel
exportExcelBtn.addEventListener('click', () => {
exportToExcel();
});
// Export to CSV
exportCSVBtn.addEventListener('click', () => {
exportToCSV();
});
// Helper functions
function detectFormulas(worksheet) {
const formulas = {};
for (const cell in worksheet) {
if (cell[0] === '!') continue;
if (worksheet[cell].f) {
formulas[cell] = worksheet[cell].f;
}
}
return formulas;
}
function detectEditableCells(jsonData, worksheet) {
const editableCells = [];
// Find cells without formulas and not empty
for (let row = 0; row < jsonData.length; row++) {
for (let col = 0; col < (jsonData[row]?.length || 0); col++) {
const cellAddress = XLSX.utils.encode_cell({ r: row, c: col });
// Skip if cell has formula
if (worksheet[cellAddress]?.f) continue;
// Check if cell is a header (first row)
if (row === 0) continue;
// Consider cells with values (including empty strings)
editableCells.push({
address: cellAddress,
value: jsonData[row][col] || '',
row,
col,
sheet: worksheet.name,
header: jsonData[0][col] || `Coluna ${col+1}`
});
}
}
return editableCells;
}
function generateInputFields() {
inputFields.innerHTML = '';
// Group editable cells by sheet
const sheetsWithEditableCells = {};
for (const sheetName in processedSheets) {
if (processedSheets[sheetName].editableCells.length > 0) {
sheetsWithEditableCells[sheetName] = processedSheets[sheetName].editableCells;
}
}
// Create input fields
for (const sheetName in sheetsWithEditableCells) {
const sheetDiv = document.createElement('div');
sheetDiv.className = 'mb-6';
const sheetTitle = document.createElement('h3');
sheetTitle.className = 'text-lg font-medium text-gray-800 mb-2';
sheetTitle.textContent = `Planilha: ${sheetName}`;
sheetDiv.appendChild(sheetTitle);
const fieldsContainer = document.createElement('div');
fieldsContainer.className = 'space-y-3';
sheetsWithEditableCells[sheetName].forEach((cell, index) => {
const groupDiv = document.createElement('div');
groupDiv.className = 'input-group';
const label = document.createElement('label');
label.htmlFor = `input-${sheetName}-${index}`;
label.className = 'text-sm font-medium text-gray-700';
label.textContent = `${cell.header}:`;
label.title = `Célula ${cell.address}`;
let input;
if (typeof cell.value === 'string' && cell.value.includes('|')) {
// CSV-like input
input = document.createElement('textarea');
input.id = `input-${sheetName}-${index}`;
input.className = 'w-full';
input.rows = 3;
input.dataset.type = 'csv';
input.value = cell.value;
} else {
// Regular input
input = document.createElement('input');
input.id = `input-${sheetName}-${index}`;
input.type = typeof cell.value === 'number' ? 'number' : 'text';
input.value = cell.value;
}
input.dataset.sheet = sheetName;
input.dataset.address = cell.address;
groupDiv.appendChild(label);
groupDiv.appendChild(input);
fieldsContainer.appendChild(groupDiv);
});
sheetDiv.appendChild(fieldsContainer);
inputFields.appendChild(sheetDiv);
}
if (inputFields.children.length === 0) {
inputFields.innerHTML = '<p class="text-sm text-gray-500">Nenhum campo editável encontrado na planilha.</p>';
}
}
function updateWorkbookValues() {
const inputs = inputFields.querySelectorAll('input, textarea');
inputs.forEach(input => {
const sheetName = input.dataset.sheet;
const cellAddress = input.dataset.address;
const value = input.dataset.type === 'csv' ? input.value : input.type === 'number' ? Number(input.value) : input.value;
// Update in processedSheets
const sheet = processedSheets[sheetName];
const cellPos = XLSX.utils.decode_cell(cellAddress);
// Ensure row exists
while (sheet.data.length <= cellPos.r) {
sheet.data.push([]);
}
// Ensure column exists in row
while (sheet.data[cellPos.r].length <= cellPos.c) {
sheet.data[cellPos.r].push('');
}
sheet.data[cellPos.r][cellPos.c] = value;
});
}
function showDataInputModal(sheetName) {
const modal = document.createElement('div');
modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50';
modal.innerHTML = `
<div class="bg-white rounded-lg p-6 w-full max-w-md">
<h3 class="text-xl font-bold mb-4">Inserir Dados para ${sheetName}</h3>
<p class="mb-3 text-sm text-gray-600">
Insira os dados no formato CSV, separados por | (pipe).<br>
Exemplo: Valor1|Valor2|Valor3
</p>
<textarea id="dataInputText" class="w-full h-40 p-3 border rounded mb-4" placeholder="Cole ou digite os dados aqui..."></textarea>
<div class="flex justify-end space-x-3">
<button id="cancelInput" class="px-4 py-2 border rounded text-gray-700 hover:bg-gray-100">Cancelar</button>
<button id="confirmInput" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700">Confirmar</button>
</div>
</div>
`;
document.body.appendChild(modal);
modal.querySelector('#cancelInput').addEventListener('click', () => {
modal.remove();
});
modal.querySelector('#confirmInput').addEventListener('click', () => {
const inputText = modal.querySelector('#dataInputText').value;
if (inputText) {
processInputData(sheetName, inputText);
}
modal.remove();
});
}
function processInputData(sheetName, inputText) {
try {
const sheet = processedSheets[sheetName];
const rows = inputText.split('\n');
// Clear existing data (keep header row)
sheet.data = [sheet.data[0]];
// Parse each row
rows.forEach(row => {
if (row.trim() === '') return;
const values = row.split('|').map(v => v.trim());
sheet.data.push(values);
});
// Recalculate all formulas
recalculateFormulas();
// Regenerate reports
generateReports();
showToast('Dados processados com sucesso!', 'success');
} catch (error) {
console.error('Error processing input data:', error);
showToast('Erro ao processar dados. Verifique o formato.', 'error');
}
}
function recalculateFormulas() {
for (const sheetName in processedSheets) {
const sheet = processedSheets[sheetName];
// Simple formula calculation (would need a proper formula parser in production)
for (const cellAddress in sheet.formulas) {
const formula = sheet.formulas[cellAddress];
const pos = XLSX.utils.decode_cell(cellAddress);
// Basic SUM formula example
if (formula.startsWith('SUM(')) {
const range = formula.match(/SUM\((.*?)\)/)[1];
const [start, end] = range.split(':').map(XLSX.utils.decode_cell);
let sum = 0;
for (let r = start.r; r <= end.r; r++) {
for (let c = start.c; c <= end.c; c++) {
const val = sheet.data[r]?.[c];
if (typeof val === 'number') {
sum += val;
}
}
}
// Ensure row exists
while (sheet.data.length <= pos.r) {
sheet.data.push([]);
}
// Ensure column exists in row
while (sheet.data[pos.r].length <= pos.c) {
sheet.data[pos.r].push('');
}
sheet.data[pos.r][pos.c] = sum;
}
// TODO: Add more formula types as needed
}
}
}
function generateReports() {
reportContainer.innerHTML = '';
for (const sheetName in processedSheets) {
const sheet = processedSheets[sheetName];
if (sheet.data.length === 0) continue;
const reportDiv = document.createElement('div');
reportDiv.className = 'p-4 bg-white rounded-lg shadow mb-4';
const title = document.createElement('h3');
title.className = 'text-lg font-semibold text-gray-800 mb-3';
title.textContent = `Relatório: ${sheetName}`;
reportDiv.appendChild(title);
// Add data input button for reports 2-5
if (['Relatório 2', 'Relatório 3', 'Relatório 4', 'Relatório 5'].includes(sheetName)) {
const inputBtn = document.createElement('button');
inputBtn.className = 'mb-4 bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded';
inputBtn.textContent = 'Inserir Dados para ' + sheetName;
inputBtn.onclick = () => showDataInputModal(sheetName);
reportDiv.appendChild(inputBtn);
}
// Create table
const table = document.createElement('table');
table.className = 'min-w-full divide-y divide-gray-200';
// Create header
const thead = document.createElement('thead');
const headerRow = document.createElement('tr');
sheet.data[0].forEach((header, colIndex) => {
const th = document.createElement('th');
th.className = 'px-4 py-2 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider';
th.textContent = header || `Coluna ${colIndex+1}`;
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
table.appendChild(thead);
// Create body with first 5 data rows
const tbody = document.createElement('tbody');
tbody.className = 'bg-white divide-y divide-gray-200';
for (let row = 1; row < Math.min(6, sheet.data.length); row++) {
const tr = document.createElement('tr');
tr.className = row % 2 === 0 ? 'bg-gray-50' : 'bg-white';
sheet.data[row].forEach((cell, colIndex) => {
const td = document.createElement('td');
td.className = 'px-4 py-2 whitespace-nowrap text-sm text-gray-700';
td.textContent = cell || '-';
tr.appendChild(td);
});
tbody.appendChild(tr);
}
table.appendChild(tbody);
// Add table to report
const tableContainer = document.createElement('div');
tableContainer.className = 'overflow-x-auto';
tableContainer.appendChild(table);
// Add stats
const stats = document.createElement('div');
stats.className = 'mt-3 text-xs text-gray-500';
stats.innerHTML = `
<p>Total de linhas: ${sheet.data.length-1}</p>
<p>Colunas: ${sheet.data[0].length}</p>
<p>Fórmulas: ${Object.keys(sheet.formulas).length}</p>
<p>Campos editáveis: ${sheet.editableCells.length}</p>
`;
reportDiv.appendChild(tableContainer);
reportDiv.appendChild(stats);
reportContainer.appendChild(reportDiv);
}
}
function exportToExcel() {
try {
// Create a new workbook with updated data
const newWorkbook = XLSX.utils.book_new();
for (const sheetName in processedSheets) {
const sheet = processedSheets[sheetName];
const worksheet = XLSX.utils.aoa_to_sheet(sheet.data);
// Reapply formulas (simplified - in real app would need proper handling)
for (const cell in sheet.formulas) {
if (worksheet[cell]) {
worksheet[cell].f = sheet.formulas[cell];
}
}
XLSX.utils.book_append_sheet(newWorkbook, worksheet, sheetName);
}
// Export
XLSX.writeFile(newWorkbook, 'athina_processed.xlsx');
showToast('Exportado para Excel com sucesso!', 'success');
} catch (error) {
console.error('Error exporting to Excel:', error);
showToast('Erro ao exportar para Excel', 'error');
}
}
function exportToCSV() {
try {
// For simplicity, we'll export the first sheet
const firstSheetName = workbook.SheetNames[0];
const sheet = processedSheets[firstSheetName];
// Convert to CSV
const csv = XLSX.utils.sheet_to_csv(XLSX.utils.aoa_to_sheet(sheet.data), {
FS: '|' // Use | as delimiter
});
// Download
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
saveAs(blob, 'athina_data.csv');
showToast('Exportado para CSV com sucesso!', 'success');
} catch (error) {
console.error('Error exporting to CSV:', error);
showToast('Erro ao exportar para CSV', 'error');
}
}
function showToast(message, type = 'info') {
const toast = document.createElement('div');
toast.className = `fixed bottom-4 right-4 px-4 py-2 rounded-md shadow-lg text-white ${
type === 'success' ? 'bg-green-500' :
type === 'error' ? 'bg-red-500' : 'bg-blue-500'
}`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.classList.add('opacity-0', 'transition', 'duration-300');
setTimeout(() => toast.remove(), 300);
}, 3000);
}
});