Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save dantetesta/6230af964f9f3956f72cbe4188eb2b59 to your computer and use it in GitHub Desktop.
Save dantetesta/6230af964f9f3956f72cbe4188eb2b59 to your computer and use it in GitHub Desktop.
Cocôladora 1.0 - Dante Testa - Calcula suas cagadas!
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cocôladora - by Dante Testa</title>
<!-- Incluindo o Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Incluindo o Google Fonts -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap">
<!-- Incluindo a biblioteca Font Awesome para os ícones -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" crossorigin="anonymous" />
<style>
/* Estilos Gerais */
body {
font-family: 'Roboto', sans-serif;
background-color: #f4f1ea;
color: #333;
margin: 0;
padding: 0;
}
header {
background-color: #8B4513;
padding: 20px;
color: #fff;
display: flex;
align-items: center;
justify-content: space-between;
}
header h1 {
margin: 0;
font-weight: 700;
display: flex;
align-items: center;
}
header h1 i {
margin-right: 10px;
}
/* Seção de Perfil */
.profile-section {
display: flex;
align-items: center;
}
.profile-section span {
margin-right: 10px;
font-size: 0.9em;
font-weight: 500;
}
/* Estilo para a imagem de perfil */
.profile-pic {
width: 50px;
height: 50px;
border-radius: 50%;
object-fit: cover;
border: 2px solid #fff;
}
/* Totalizadores Estilizados */
.totals .card {
background-color: #fff;
border: none;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.totals .card-body {
display: flex;
align-items: center;
}
.totals .card i {
font-size: 2.0em;
margin-right: 15px;
color: #8B4513;
}
.totals .card span {
font-size: 1.75em;
font-weight: 700;
}
/* Botões de Ação */
.action-buttons .btn {
width: 48%;
font-weight: 500;
}
.btn-primary {
background-color: #A0522D;
border-color: #A0522D;
}
.btn-primary:hover {
background-color: #8B4513;
border-color: #8B4513;
}
.btn-success {
background-color: #CD853F;
border-color: #CD853F;
}
.btn-success:hover {
background-color: #A0522D;
border-color: #A0522D;
}
/* Estilização do Modal */
.modal-header {
background-color: #8B4513;
color: #fff;
border-top-left-radius: 12px;
border-top-right-radius: 12px;
}
.modal-content {
border-radius: 12px;
}
.modal-footer .btn {
width: 100%;
font-weight: 500;
}
/* Tabs Personalizadas */
.nav-tabs .nav-link.active {
background-color: #fff;
border-color: #dee2e6 #dee2e6 #fff;
color: #8B4513;
font-weight: 500;
}
.nav-tabs .nav-link {
color: #8B4513;
font-weight: 500;
}
/* Tabela Estilizada */
table {
margin-top: 20px;
}
table th {
background-color: #e0d7c6;
color: #8B4513;
font-weight: 500;
}
table tbody tr:hover {
background-color: #f1f1f1;
}
/* Responsividade da Tabela */
@media (max-width: 768px) {
.table thead th:nth-child(2),
.table thead th:nth-child(3),
.table tbody td:nth-child(2),
.table tbody td:nth-child(3) {
display: none;
}
}
/* Rodapé */
footer {
background-color: #8B4513;
color: #fff;
text-align: center;
padding: 15px;
margin-top: 30px;
}
footer a {
color: #fff;
text-decoration: underline;
}
/* Botões no Rodapé */
.footer-buttons {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 10px;
margin-top: 15px;
}
.footer-buttons .btn {
font-size: 0.9em;
padding: 10px 20px;
background-color: #A0522D;
border-color: #A0522D;
color: #fff;
flex: 1 1 200px;
max-width: 300px;
}
.footer-buttons .btn:hover {
background-color: #8B4513;
border-color: #8B4513;
}
/* Estilo para mensagens de importação/exportação */
.import-export-message {
margin-top: 10px;
}
</style>
</head>
<body>
<header>
<h1>
<i class="fas fa-poop"></i> Cocôladora
</h1>
<!-- Seção de Perfil -->
<div class="profile-section">
<span>By: Dante Testa</span>
<a href="https://www.dantetesta.com.br" target="_blank">
<img src="https://scontent.fcpq7-1.fna.fbcdn.net/v/t39.30808-6/316180582_10231057886298414_6381379649509653345_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=6ee11a&_nc_eui2=AeGmVRO4jXRHuRlvwwBXAGhGTdMuPWkADWBN0y49aQANYDjh0tXYpk1RqX0I-ILFNkc&_nc_ohc=yXUQlpx2hAwQ7kNvgEpZOoL&_nc_ht=scontent.fcpq7-1.fna&oh=00_AYC5QYszFmSzYc5PEpmFcCN75KQ4amhBvmn1PxJhC2p0Rg&oe=66F42D3D" alt="Dante Testa" class="profile-pic">
</a>
</div>
</header>
<div class="container my-4">
<!-- Totalizadores Estilizados -->
<div class="row totals">
<div class="col-md-6">
<div class="card">
<div class="card-body">
<i class="fas fa-calendar-alt"></i>
<div>
<span id="monthTotal">Total no Mês: R$ 0,00</span>
<p id="currentMonthYear" class="mb-0"></p>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-body">
<i class="fas fa-coins"></i>
<div>
<span id="overallTotal">Total Geral: R$ 0,00</span>
<p class="mb-0">Acumulado</p>
</div>
</div>
</div>
</div>
</div>
<!-- Botões de Ação -->
<div class="d-flex justify-content-between action-buttons mb-4">
<button class="btn btn-primary" onclick="openModal('entryModal')"><i class="fas fa-plus"></i> Adicionar uma Cagada</button>
<button class="btn btn-success" onclick="openModal('chartModal')"><i class="fas fa-chart-bar"></i> Mostrar Gráfico</button>
</div>
<!-- Abas dos Meses -->
<ul class="nav nav-tabs" id="monthTabs">
<!-- Abas serão geradas dinamicamente -->
</ul>
<!-- Contador de Lançamentos -->
<div id="entryCount" class="mt-3">
<!-- Contador será gerado dinamicamente -->
</div>
<!-- Tabela de Registros -->
<div id="entriesTable">
<!-- Tabela será gerada dinamicamente -->
</div>
</div>
<!-- Modal do Formulário -->
<div class="modal fade" id="entryModal" tabindex="-1" aria-labelledby="entryModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Registrar Ida ao Banheiro</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<!-- Formulário -->
<div class="mb-3">
<label for="salary" class="form-label">Salário Mensal (R$)</label>
<input type="text" class="form-control" id="salary" placeholder="Ex: R$ 3.000,00">
<div class="text-danger" id="salaryError"></div>
</div>
<div class="mb-3">
<label for="entryTime" class="form-label">Horário de Entrada</label>
<input type="time" class="form-control" id="entryTime">
<div class="text-danger" id="entryTimeError"></div>
</div>
<div class="mb-3">
<label for="exitTime" class="form-label">Horário de Saída</label>
<input type="time" class="form-control" id="exitTime">
<div class="text-danger" id="exitTimeError"></div>
</div>
</div>
<div class="modal-footer">
<button onclick="addEntry()" class="btn btn-primary">Adicionar Registro</button>
</div>
</div>
</div>
</div>
<!-- Modal do Gráfico -->
<div class="modal fade" id="chartModal" tabindex="-1" aria-labelledby="chartModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Ganhos Mensais</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<canvas id="earningsChart" width="400" height="200"></canvas>
</div>
</div>
</div>
</div>
<!-- Modal de Importação/Exportação -->
<div class="modal fade" id="importExportModal" tabindex="-1" aria-labelledby="importExportModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Importar/Exportar Dados</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
<!-- Importar -->
<div class="col-md-6 mb-4">
<h6>Importar CSV</h6>
<input type="file" id="importCSV" accept=".csv" class="form-control">
<button class="btn btn-primary mt-2" onclick="handleImportCSV()">Importar</button>
<div id="importMessage" class="import-export-message"></div>
</div>
<!-- Exportar -->
<div class="col-md-6 mb-4">
<h6>Exportar CSV</h6>
<button class="btn btn-success mt-2" onclick="exportToCSV()">Exportar</button>
<div id="exportMessage" class="import-export-message"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Rodapé -->
<footer>
<p>Cocôladora - by Dante Testa</p>
<p>Dante Testa - <a href="https://www.dantetesta.com.br" target="_blank">www.dantetesta.com.br</a></p>
<p>Feito com ChatGPT o1-preview - 21/09/2024</p>
<!-- Botões do Rodapé -->
<div class="footer-buttons">
<button class="btn btn-secondary" onclick="generateDemoData()">Generate Demo</button>
<button class="btn btn-info" onclick="openModal('importExportModal')"><i class="fas fa-file-import"></i> Importar/Exportar</button>
<button class="btn btn-danger" onclick="resetData()">Reset All Data</button>
</div>
</footer>
<!-- Incluindo as bibliotecas necessárias -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/cleave.js@1/dist/cleave.min.js"></script>
<script>
let entries = JSON.parse(localStorage.getItem('entries')) || [];
let selectedMonth = '';
let selectedYear = new Date().getFullYear();
// Variável para armazenar o salário
let savedSalary = localStorage.getItem('salary') || '';
// Instância dos Modais
const entryModal = new bootstrap.Modal(document.getElementById('entryModal'));
const chartModal = new bootstrap.Modal(document.getElementById('chartModal'));
const importExportModal = new bootstrap.Modal(document.getElementById('importExportModal'));
function openModal(modalId) {
if (modalId === 'entryModal') {
if (savedSalary) {
salaryInput.setRawValue(savedSalary);
}
entryModal.show();
}
if (modalId === 'chartModal') {
updateChart();
chartModal.show();
}
if (modalId === 'importExportModal') {
// Limpar mensagens anteriores
document.getElementById('importMessage').innerText = '';
document.getElementById('exportMessage').innerText = '';
importExportModal.show();
}
}
function closeModal(modalId) {
if (modalId === 'entryModal') {
entryModal.hide();
clearErrors();
}
if (modalId === 'chartModal') {
chartModal.hide();
}
if (modalId === 'importExportModal') {
importExportModal.hide();
}
}
// Aplicando máscara monetária ao campo de salário
const salaryInputElement = document.getElementById('salary');
const salaryInput = new Cleave(salaryInputElement, {
numeral: true,
numeralThousandsGroupStyle: 'thousand',
numeralDecimalMark: ',',
delimiter: '.',
numeralDecimalScale: 2,
prefix: 'R$ ',
noImmediatePrefix: true,
rawValueTrimPrefix: true
});
function addEntry() {
// Limpar mensagens de erro
clearErrors();
const entryTimeInput = document.getElementById('entryTime');
const exitTimeInput = document.getElementById('exitTime');
const salary = parseFloat(salaryInput.getRawValue().replace(',', '.'));
const entryTime = entryTimeInput.value;
const exitTime = exitTimeInput.value;
const date = new Date();
let hasError = false;
if (!salary || salary <= 0) {
showError('salaryError', 'Informe um salário válido.');
hasError = true;
}
if (!entryTime) {
showError('entryTimeError', 'Informe o horário de entrada.');
hasError = true;
}
if (!exitTime) {
showError('exitTimeError', 'Informe o horário de saída.');
hasError = true;
}
if (hasError) return;
// Salvar o salário no localStorage
savedSalary = salaryInput.getRawValue();
localStorage.setItem('salary', savedSalary);
const start = new Date(`1970-01-01T${entryTime}:00`);
const end = new Date(`1970-01-01T${exitTime}:00`);
let diff = (end - start) / (1000 * 60); // Diferença em minutos
if (diff < 0) {
diff += 1440; // Ajuste para horários após meia-noite
}
const minutesPerMonth = 160 * 60; // Considerando 160 horas mensais de trabalho
const valuePerMinute = salary / minutesPerMonth;
const earned = (diff * valuePerMinute);
const entry = {
dateTime: date.toISOString(), // Armazenar data e hora completa
entryTime,
exitTime,
duration: diff,
earned,
month: date.getMonth(),
year: date.getFullYear()
};
entries.push(entry);
localStorage.setItem('entries', JSON.stringify(entries));
// Limpar campos após adicionar (mantendo o salário)
entryTimeInput.value = '';
exitTimeInput.value = '';
closeModal('entryModal');
generateTabs();
}
function clearErrors() {
document.getElementById('salaryError').innerText = '';
document.getElementById('entryTimeError').innerText = '';
document.getElementById('exitTimeError').innerText = '';
}
function showError(elementId, message) {
document.getElementById(elementId).innerText = message;
}
function generateTabs() {
const tabsContainer = document.getElementById('monthTabs');
tabsContainer.innerHTML = '';
const months = [...new Set(entries
.filter(e => e.year === selectedYear)
.map(e => `${e.month}-${e.year}`))];
months.sort((a, b) => {
const [monthA, yearA] = a.split('-').map(Number);
const [monthB, yearB] = b.split('-').map(Number);
return new Date(yearB, monthB) - new Date(yearA, monthA);
});
if (months.length === 0) {
document.getElementById('entriesTable').innerHTML = '<p class="text-center mt-4">Nenhum registro disponível.</p>';
document.getElementById('monthTotal').innerText = 'Total no Mês: R$ 0,00';
document.getElementById('overallTotal').innerText = 'Total Geral: R$ 0,00';
document.getElementById('entryCount').innerText = '';
return;
}
months.forEach((monthYear, index) => {
const [month, year] = monthYear.split('-').map(Number);
const monthEntries = entries.filter(e => e.month === month && e.year === year);
if (monthEntries.length === 0) return; // Não exibe abas vazias
const tab = document.createElement('li');
tab.className = 'nav-item';
const tabLink = document.createElement('a');
tabLink.className = 'nav-link';
tabLink.innerText = getMonthName(monthYear);
tabLink.href = '#';
tabLink.onclick = () => selectMonth(tabLink, monthYear);
if (selectedMonth === '' || selectedMonth === monthYear) {
selectedMonth = monthYear;
tabLink.classList.add('active');
}
tab.appendChild(tabLink);
tabsContainer.appendChild(tab);
});
showEntries(selectedMonth);
generateOverallTotal();
}
function selectMonth(tabElement, monthYear) {
selectedMonth = monthYear;
document.querySelectorAll('.nav-link').forEach(tab => tab.classList.remove('active'));
tabElement.classList.add('active');
showEntries(monthYear);
}
function getMonthName(monthYear) {
const [month, year] = monthYear.split('-').map(Number);
const date = new Date(year, month);
const shortMonth = date.toLocaleString('pt-BR', { month: 'short' }).replace('.', '').toUpperCase();
const shortYear = year.toString().slice(-2);
return `${shortMonth} ${shortYear}`;
}
function showEntries(monthYear) {
const [month, year] = monthYear.split('-').map(Number);
const filteredEntries = entries
.filter(e => e.month === month && e.year === year)
.sort((a, b) => new Date(b.dateTime) - new Date(a.dateTime)); // Ordenação decrescente
const tableContainer = document.getElementById('entriesTable');
tableContainer.innerHTML = '';
const entryCountContainer = document.getElementById('entryCount');
if (filteredEntries.length === 0) {
tableContainer.innerHTML = '<p class="text-center mt-4">Nenhum registro para este mês.</p>';
document.getElementById('monthTotal').innerText = 'Total no Mês: R$ 0,00';
entryCountContainer.innerText = '';
return;
}
// Mostrar contador de lançamentos
entryCountContainer.innerHTML = `<p><strong>Total de Lançamentos no Mês:</strong> ${filteredEntries.length}</p>`;
const table = document.createElement('table');
table.className = 'table table-striped';
const header = table.createTHead();
const headerRow = header.insertRow();
headerRow.innerHTML = `
<th>Data e Hora</th>
<th class="d-none d-md-table-cell">Entrada</th>
<th class="d-none d-md-table-cell">Saída</th>
<th>Duração</th>
<th>Valor</th>
<th>Ação</th>
`;
const tbody = table.createTBody();
let monthTotal = 0;
filteredEntries.forEach((entry) => {
const row = tbody.insertRow();
row.innerHTML = `
<td>${formatDateTime(entry.dateTime)}</td>
<td class="d-none d-md-table-cell">${entry.entryTime}</td>
<td class="d-none d-md-table-cell">${entry.exitTime}</td>
<td>${entry.duration}min</td>
<td>${formatCurrency(entry.earned)}</td>
<td><button class="btn btn-sm btn-danger" onclick="removeEntry(${entries.indexOf(entry)})"><i class="fas fa-trash-alt"></i></button></td>
`;
monthTotal += entry.earned;
});
tableContainer.appendChild(table);
document.getElementById('monthTotal').innerText = `Total no Mês: ${formatCurrency(monthTotal)}`;
document.getElementById('currentMonthYear').innerText = getFullMonthYear(month, year);
}
function generateOverallTotal() {
let overallTotal = entries.reduce((sum, entry) => sum + entry.earned, 0);
document.getElementById('overallTotal').innerText = `Total Geral: ${formatCurrency(overallTotal)}`;
}
function removeEntry(index) {
if (confirm('Tem certeza de que deseja remover este registro?')) {
entries.splice(index, 1);
localStorage.setItem('entries', JSON.stringify(entries));
generateTabs();
}
}
function formatDateTime(dateTimeStr) {
const date = new Date(dateTimeStr);
return date.toLocaleString('pt-BR', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: false
});
}
function formatCurrency(value) {
return value.toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' });
}
function getFullMonthYear(month, year) {
const date = new Date(year, month);
return date.toLocaleString('pt-BR', { month: 'long', year: 'numeric' }).replace(/^\w/, c => c.toUpperCase());
}
// Função para resetar os dados
function resetData() {
if (confirm('Tem certeza de que deseja resetar todos os dados? Esta ação não pode ser desfeita.')) {
localStorage.clear();
entries = [];
savedSalary = '';
location.reload();
}
}
// Função para gerar dados de demonstração corrigida
function generateDemoData() {
if (confirm('Isso irá gerar dados de demonstração e substituir seus dados atuais. Deseja continuar?')) {
// Limpar dados existentes
localStorage.clear();
entries = [];
savedSalary = '3000,00';
localStorage.setItem('salary', savedSalary);
const salary = 3000.00; // Salário fixo para a demonstração
const minutesPerMonth = 160 * 60;
const valuePerMinute = salary / minutesPerMonth;
const currentDate = new Date();
for (let m = 0; m < 12; m++) {
let month = currentDate.getMonth() - m;
let year = currentDate.getFullYear();
if (month < 0) {
month += 12;
year -= 1;
}
const monthDate = new Date(year, month, 1);
const daysInMonth = new Date(year, month + 1, 0).getDate();
// Simulando de 1 a 3 cagadas por dia útil
for (let d = 1; d <= daysInMonth; d++) {
const dayOfWeek = new Date(year, month, d).getDay();
if (dayOfWeek === 0 || dayOfWeek === 6) continue; // Pular finais de semana
const numCagadas = Math.floor(Math.random() * 3) + 1;
for (let c = 0; c < numCagadas; c++) {
const entryHour = Math.floor(Math.random() * 8) + 8; // Entre 8h e 16h
const entryMinute = Math.floor(Math.random() * 60);
const duration = Math.floor(Math.random() * 15) + 5; // Duração entre 5 e 20 minutos
let exitHour = entryHour + Math.floor((entryMinute + duration) / 60);
let exitMinute = (entryMinute + duration) % 60;
if (exitHour >= 24) { // Ajustar caso ultrapasse 24h
exitHour -= 24;
}
const entryTime = `${String(entryHour).padStart(2, '0')}:${String(entryMinute).padStart(2, '0')}`;
const exitTime = `${String(exitHour).padStart(2, '0')}:${String(exitMinute).padStart(2, '0')}`;
const earned = duration * valuePerMinute;
const date = new Date(year, month, d, entryHour, entryMinute);
const entry = {
dateTime: date.toISOString(),
entryTime,
exitTime,
duration,
earned,
month: date.getMonth(),
year: date.getFullYear()
};
entries.push(entry);
}
}
}
localStorage.setItem('entries', JSON.stringify(entries));
generateTabs();
alert('Dados de demonstração gerados com sucesso!');
}
}
// Função para atualizar o gráfico
function updateChart() {
const ctx = document.getElementById('earningsChart').getContext('2d');
const monthlyEarnings = Array(12).fill(0);
entries.forEach(entry => {
if (entry.year === selectedYear) {
monthlyEarnings[entry.month] += entry.earned;
}
});
const data = {
labels: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'],
datasets: [{
label: 'Ganhos Mensais',
data: monthlyEarnings,
backgroundColor: '#8B4513',
}]
};
if (window.myChart instanceof Chart) {
window.myChart.destroy();
}
window.myChart = new Chart(ctx, {
type: 'bar',
data: data,
options: {
plugins: {
tooltip: {
callbacks: {
label: function(context) {
return formatCurrency(context.parsed.y);
}
}
}
},
scales: {
y: {
ticks: {
callback: function(value) {
return formatCurrency(value);
}
}
}
}
}
});
}
// Função para exportar os dados em CSV
function exportToCSV() {
if (entries.length === 0) {
document.getElementById('exportMessage').innerText = 'Nenhum dado para exportar.';
document.getElementById('exportMessage').style.color = 'red';
return;
}
const header = ['dateTime', 'entryTime', 'exitTime', 'duration', 'earned', 'month', 'year'];
const csvRows = [];
csvRows.push(header.join(','));
entries.forEach(entry => {
const values = header.map(field => {
let val = entry[field];
// Escape double quotes
if (typeof val === 'string') {
val = val.replace(/"/g, '""');
}
// Enclose in double quotes if necessary
if (typeof val === 'string' && (val.includes(',') || val.includes('"') || val.includes('\n'))) {
val = `"${val}"`;
}
return val;
});
csvRows.push(values.join(','));
});
const csvContent = csvRows.join('\n');
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.setAttribute("href", url);
const currentDate = new Date();
const timestamp = currentDate.toISOString().slice(0,10);
link.setAttribute("download", `Cocoladora_Data_${timestamp}.csv`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
document.getElementById('exportMessage').innerText = 'Dados exportados com sucesso!';
document.getElementById('exportMessage').style.color = 'green';
}
// Função para importar dados a partir de um CSV
function handleImportCSV() {
const fileInput = document.getElementById('importCSV');
const file = fileInput.files[0];
const importMessage = document.getElementById('importMessage');
if (!file) {
importMessage.innerText = 'Por favor, selecione um arquivo CSV para importar.';
importMessage.style.color = 'red';
return;
}
const reader = new FileReader();
reader.onload = function(e) {
const text = e.target.result;
const parsedEntries = parseCSV(text);
if (parsedEntries.error) {
importMessage.innerText = parsedEntries.error;
importMessage.style.color = 'red';
return;
}
// Validar e adicionar entradas
const newEntries = parsedEntries.data;
let valid = true;
newEntries.forEach(entry => {
if (!entry.dateTime || !entry.entryTime || !entry.exitTime || typeof entry.duration !== 'number' || typeof entry.earned !== 'number' || typeof entry.month !== 'number' || typeof entry.year !== 'number') {
valid = false;
}
});
if (!valid) {
importMessage.innerText = 'O arquivo CSV possui um formato inválido.';
importMessage.style.color = 'red';
return;
}
// Atualizar entries e localStorage
entries = entries.concat(newEntries);
localStorage.setItem('entries', JSON.stringify(entries));
generateTabs();
importMessage.innerText = 'Dados importados com sucesso!';
importMessage.style.color = 'green';
// Limpar o input de arquivo
fileInput.value = '';
};
reader.onerror = function() {
importMessage.innerText = 'Erro ao ler o arquivo.';
importMessage.style.color = 'red';
};
reader.readAsText(file);
}
// Função auxiliar para parsear CSV
function parseCSV(text) {
const lines = text.split('\n').filter(line => line.trim() !== '');
if (lines.length < 2) {
return { error: 'O arquivo CSV deve conter pelo menos um registro.' };
}
const header = lines[0].split(',').map(h => h.trim());
const requiredFields = ['dateTime', 'entryTime', 'exitTime', 'duration', 'earned', 'month', 'year'];
// Verificar se todos os campos necessários estão presentes
for (let field of requiredFields) {
if (!header.includes(field)) {
return { error: `Campo "${field}" ausente no cabeçalho do CSV.` };
}
}
const data = [];
for (let i = 1; i < lines.length; i++) {
const line = lines[i];
const values = line.match(/(".*?"|[^",\s]+)(?=\s*,|\s*$)/g);
if (!values || values.length !== header.length) {
return { error: `Erro na linha ${i + 1}: número de campos inválido.` };
}
const entry = {};
for (let j = 0; j < header.length; j++) {
let value = values[j];
if (value.startsWith('"') && value.endsWith('"')) {
value = value.slice(1, -1).replace(/""/g, '"');
}
if (header[j] === 'duration' || header[j] === 'earned' || header[j] === 'month' || header[j] === 'year') {
value = Number(value);
if (isNaN(value)) {
return { error: `Valor inválido para "${header[j]}" na linha ${i + 1}.` };
}
}
entry[header[j]] = value;
}
data.push(entry);
}
return { data };
}
window.onload = function() {
generateTabs();
};
</script>
<center>
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-4762717586329786"
crossorigin="anonymous"></script>
<!-- Blog-DanteTesta-News -->
<ins class="adsbygoogle"
style="display:block"
data-ad-client="ca-pub-4762717586329786"
data-ad-slot="7391598107"
data-ad-format="auto"
data-full-width-responsive="true"></ins>
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script>
</center>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment