Spaces:
Running
Running
| document.addEventListener('DOMContentLoaded', () => { | |
| const uploadArea = document.getElementById('upload-area'); | |
| const fileInput = document.getElementById('file-input'); | |
| const imagePreview = document.getElementById('image-preview'); | |
| const uploadContent = document.querySelector('.upload-content'); | |
| const analyzeBtn = document.getElementById('analyze-btn'); | |
| const btnText = document.querySelector('.btn-text'); | |
| const loader = document.querySelector('.loader'); | |
| const resultsPlaceholder = document.getElementById('results-placeholder'); | |
| const resultsContent = document.getElementById('results-content'); | |
| const mainDisease = document.getElementById('main-disease'); | |
| const chartCanvas = document.getElementById('resultsChart'); | |
| const cropsGallery = document.getElementById('crops-gallery'); | |
| let selectedFile = null; | |
| let chartInstance = null; | |
| // Tabs | |
| const tabIndividual = document.getElementById('tab-individual'); | |
| const tabBatch = document.getElementById('tab-batch'); | |
| const wrapperIndividual = document.getElementById('wrapper-individual'); | |
| const wrapperBatch = document.getElementById('wrapper-batch'); | |
| const tabAppCafe = document.getElementById('tab-appcafe'); | |
| const wrapperAppCafe = document.getElementById('wrapper-appcafe'); | |
| // Supabase Auth Elements | |
| const appcafeLoginForm = document.getElementById('appcafe-login-form'); | |
| const appcafeEmailInput = document.getElementById('appcafe-email'); | |
| const appcafePasswordInput = document.getElementById('appcafe-password'); | |
| const appcafeLoginBtn = document.getElementById('appcafe-login-btn'); | |
| const appcafeBtnText = appcafeLoginBtn?.querySelector('.btn-text'); | |
| const appcafeLoader = document.getElementById('appcafe-loader'); | |
| const appcafeError = document.getElementById('appcafe-error'); | |
| const appcafeLoginContainer = document.getElementById('appcafe-login-container'); | |
| const appcafeLoggedIn = document.getElementById('appcafe-logged-in'); | |
| const appcafeLogoutBtn = document.getElementById('appcafe-logout-btn'); | |
| const appcafePromo = document.querySelector('.appcafe-promo'); | |
| // Dashboard Buttons | |
| const btnOlharFotos = document.getElementById('btn-olhar-fotos'); | |
| const btnMapaCalor = document.getElementById('btn-mapa-calor'); | |
| const btnDashboardIndividual = document.getElementById('btn-dashboard-individual'); | |
| const btnDashboardLote = document.getElementById('btn-dashboard-lote'); | |
| // Gallery Modal | |
| const galleryModal = document.getElementById('gallery-modal'); | |
| const closeGalleryBtn = document.getElementById('close-gallery-btn'); | |
| const galleryContainer = document.getElementById('gallery-container'); | |
| const galleryLoader = document.getElementById('gallery-loader'); | |
| // Gallery Filters | |
| const filterDesc = document.getElementById('filter-desc'); | |
| const filterDateStart = document.getElementById('filter-date-start'); | |
| const filterDateEnd = document.getElementById('filter-date-end'); | |
| const clearFiltersBtn = document.getElementById('clear-filters-btn'); | |
| const galleryFilters = document.getElementById('gallery-filters'); | |
| let currentGalleryPictures = []; | |
| let selectedGalleryPictures = new Map(); | |
| // Gallery Actions | |
| const galleryActionBar = document.getElementById('gallery-action-bar'); | |
| const selectedCountText = document.getElementById('selected-count'); | |
| const btnCancelSelection = document.getElementById('btn-cancel-selection'); | |
| const btnAnalyzeSelected = document.getElementById('btn-analyze-selected'); | |
| const btnDeleteSelected = document.getElementById('btn-delete-selected'); | |
| const btnSelectAll = document.getElementById('select-all-btn'); | |
| const analyzeActionText = document.getElementById('analyze-action-text'); | |
| const analyzeActionLoader = document.getElementById('analyze-action-loader'); | |
| const analyzeActionHint = document.getElementById('analyze-action-hint'); | |
| // Heatmap | |
| let isHeatmapMode = false; | |
| let diseaseDictionary = {}; // id_disease -> name | |
| const heatmapModal = document.getElementById('heatmap-modal'); | |
| const closeHeatmapBtn = document.getElementById('close-heatmap-btn'); | |
| const heatmapDiseaseFilter = document.getElementById('heatmap-disease-filter'); | |
| const heatmapStyleFilter = document.getElementById('heatmap-style-filter'); | |
| let leafletMapInstance = null; | |
| let currentHeatLayer = null; | |
| let supabaseClient = null; | |
| if (window.supabase && window.SUPABASE_URL && window.SUPABASE_ANON_KEY) { | |
| supabaseClient = window.supabase.createClient(window.SUPABASE_URL, window.SUPABASE_ANON_KEY); | |
| } | |
| // Batch UI Elements | |
| const batchUploadArea = document.getElementById('batch-upload-area'); | |
| const batchFileInput = document.getElementById('batch-file-input'); | |
| const batchImagePreview = document.getElementById('batch-image-preview'); | |
| const batchUploadContent = document.getElementById('batch-upload-content'); | |
| const batchAnalyzeBtn = document.getElementById('batch-analyze-btn'); | |
| const batchBtnText = document.getElementById('batch-btn-text'); | |
| const batchLoader = document.getElementById('batch-loader'); | |
| const batchResultsPlaceholder = document.getElementById('batch-results-placeholder'); | |
| const batchResultsContent = document.getElementById('batch-results-content'); | |
| const batchTotalLeaves = document.getElementById('batch-total-leaves'); | |
| const batchMainDisease = document.getElementById('batch-main-disease'); | |
| const batchChartCanvas = document.getElementById('batchResultsChart'); | |
| let selectedBatchFiles = []; | |
| let batchChartInstance = null; | |
| // Theming Colors for the Chart | |
| const chartColors = [ | |
| '#2f855a', // Green | |
| '#8b5a2b', // Brown | |
| '#48bb78', // Light Green | |
| '#b7791f', // Yellow-Brown | |
| '#276749' // Dark Green | |
| ]; | |
| // Drag and Drop Events | |
| ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { | |
| uploadArea.addEventListener(eventName, preventDefaults, false); | |
| }); | |
| function preventDefaults(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| } | |
| ['dragenter', 'dragover'].forEach(eventName => { | |
| uploadArea.addEventListener(eventName, () => uploadArea.classList.add('dragover'), false); | |
| }); | |
| ['dragleave', 'drop'].forEach(eventName => { | |
| uploadArea.addEventListener(eventName, () => uploadArea.classList.remove('dragover'), false); | |
| }); | |
| uploadArea.addEventListener('drop', handleDrop, false); | |
| uploadArea.addEventListener('click', () => fileInput.click()); | |
| fileInput.addEventListener('change', handleFileSelect); | |
| function handleDrop(e) { | |
| const dt = e.dataTransfer; | |
| const file = dt.files[0]; | |
| handleFile(file); | |
| } | |
| function handleFileSelect(e) { | |
| const file = e.target.files[0]; | |
| handleFile(file); | |
| } | |
| function handleFile(file) { | |
| if (file && file.type.startsWith('image/')) { | |
| selectedFile = file; | |
| // Show preview | |
| const reader = new FileReader(); | |
| reader.onload = (e) => { | |
| imagePreview.src = e.target.result; | |
| imagePreview.classList.remove('hidden'); | |
| uploadContent.classList.add('hidden'); | |
| analyzeBtn.disabled = false; | |
| } | |
| reader.readAsDataURL(file); | |
| } else { | |
| alert('Por favor, selecione um arquivo de imagem válido.'); | |
| } | |
| } | |
| analyzeBtn.addEventListener('click', async () => { | |
| if (!selectedFile) return; | |
| // UI Loading State | |
| analyzeBtn.disabled = true; | |
| btnText.innerHTML = '<i class="ph-bold ph-scan"></i> Analisando...'; | |
| loader.classList.remove('hidden'); | |
| resultsPlaceholder.querySelector('p').textContent = 'Processando IA (pode levar alguns segundos)...'; | |
| resultsContent.classList.add('hidden'); | |
| resultsPlaceholder.classList.remove('hidden'); | |
| const formData = new FormData(); | |
| formData.append('file', selectedFile); | |
| try { | |
| const response = await fetch('/predict', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| if (!response.ok) { | |
| const errData = await response.json(); | |
| throw new Error(errData.error || 'Erro na análise da imagem.'); | |
| } | |
| const data = await response.json(); | |
| updateResults(data); | |
| } catch (error) { | |
| alert(error.message); | |
| resultsPlaceholder.querySelector('p').textContent = 'Falha na análise. Tente novamente.'; | |
| } finally { | |
| analyzeBtn.disabled = false; | |
| btnText.innerHTML = '<i class="ph-bold ph-scan"></i> Analisar Folha'; | |
| loader.classList.add('hidden'); | |
| } | |
| }); | |
| function updateResults(data) { | |
| // Hide placeholder, show results | |
| resultsPlaceholder.classList.add('hidden'); | |
| resultsContent.classList.remove('hidden'); | |
| // Update Text | |
| mainDisease.textContent = data.mais_frequente; | |
| // --- Save Result Logic --- | |
| const saveContainer = document.getElementById('save-result-container-individual'); | |
| const saveBtn = document.getElementById('btn-save-result-individual'); | |
| const saveMsg = document.getElementById('save-result-msg-individual'); | |
| if (selectedFile && selectedFile.supabasePictureId && data.mais_frequente !== "Nenhuma detecção encontrada" && supabaseClient) { | |
| saveContainer.classList.remove('hidden'); | |
| saveBtn.style.display = 'inline-flex'; | |
| saveMsg.style.display = 'none'; | |
| const newSaveBtn = saveBtn.cloneNode(true); | |
| saveBtn.parentNode.replaceChild(newSaveBtn, saveBtn); | |
| newSaveBtn.addEventListener('click', async () => { | |
| newSaveBtn.disabled = true; | |
| newSaveBtn.innerHTML = '<i class="ph-duotone ph-spinner" style="animation: spin 1s linear infinite;"></i> Salvando...'; | |
| try { | |
| const diseaseName = data.mais_frequente; | |
| // Find disease ID | |
| const { data: diseaseData, error: dError } = await supabaseClient.from('diseases').select('id').eq('name', diseaseName).single(); | |
| if (dError || !diseaseData) throw new Error("Doença não encontrada no banco de dados."); | |
| // Update picture | |
| const { error: pError } = await supabaseClient.from('pictures').update({ id_disease: diseaseData.id }).eq('id', selectedFile.supabasePictureId); | |
| if (pError) throw new Error("Erro ao atualizar a foto."); | |
| newSaveBtn.style.display = 'none'; | |
| saveMsg.innerHTML = '<i class="ph-fill ph-check-circle"></i> Resultado salvo com sucesso no banco de dados!'; | |
| saveMsg.style.color = "#2f855a"; | |
| saveMsg.style.display = 'block'; | |
| } catch (e) { | |
| newSaveBtn.disabled = false; | |
| newSaveBtn.innerHTML = '<i class="ph-bold ph-floppy-disk"></i> Tentar Novamente'; | |
| saveMsg.textContent = e.message; | |
| saveMsg.style.color = "#e53e3e"; | |
| saveMsg.style.display = 'block'; | |
| } | |
| }); | |
| } else { | |
| if (saveContainer) saveContainer.classList.add('hidden'); | |
| } | |
| // Render Chart | |
| renderChart(data.contagem); | |
| // Render Crops | |
| renderCrops(data.imagens || []); | |
| } | |
| function renderCrops(imagens) { | |
| cropsGallery.innerHTML = ''; | |
| if (imagens.length === 0) { | |
| cropsGallery.innerHTML = '<p style="color:var(--text-secondary); width: 100%;">Nenhum recorte gerado.</p>'; | |
| return; | |
| } | |
| const timestamp = Date.now(); | |
| imagens.forEach(imgData => { | |
| const card = document.createElement('div'); | |
| card.className = 'crop-card'; | |
| const img = document.createElement('img'); | |
| // Cache buster for new analyses | |
| img.src = `${imgData.url}?t=${timestamp}`; | |
| img.alt = 'Recorte detectado'; | |
| const label = document.createElement('div'); | |
| label.className = 'crop-card-label'; | |
| label.textContent = imgData.classe; | |
| card.appendChild(img); | |
| card.appendChild(label); | |
| cropsGallery.appendChild(card); | |
| }); | |
| } | |
| function renderChart(counts) { | |
| const labels = Object.keys(counts); | |
| const data = Object.values(counts); | |
| if (chartInstance) { | |
| chartInstance.destroy(); | |
| } | |
| chartInstance = new Chart(chartCanvas, { | |
| type: 'doughnut', | |
| data: { | |
| labels: labels, | |
| datasets: [{ | |
| data: data, | |
| backgroundColor: chartColors, | |
| borderWidth: 0, | |
| hoverOffset: 4 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { | |
| legend: { | |
| position: 'right', | |
| labels: { | |
| color: '#4a5568', | |
| font: { family: "'Outfit', sans-serif", size: 10 }, | |
| padding: 10, | |
| boxWidth: 12 | |
| } | |
| }, | |
| tooltip: { | |
| backgroundColor: 'rgba(255, 255, 255, 0.95)', | |
| titleColor: '#2d3748', | |
| bodyColor: '#2d3748', | |
| borderColor: 'rgba(47, 133, 90, 0.2)', | |
| borderWidth: 1, | |
| padding: 10, | |
| titleFont: { family: "'Outfit', sans-serif", size: 12, weight: 'bold' }, | |
| bodyFont: { family: "'Outfit', sans-serif", size: 12 } | |
| } | |
| }, | |
| cutout: '70%' | |
| } | |
| }); | |
| } | |
| // --- BATCH LOGIC --- | |
| // Tab Switching | |
| tabIndividual.addEventListener('click', () => { | |
| tabIndividual.classList.add('active'); | |
| tabBatch.classList.remove('active'); | |
| if (tabAppCafe) tabAppCafe.classList.remove('active'); | |
| wrapperIndividual.classList.remove('hidden'); | |
| wrapperBatch.classList.add('hidden'); | |
| if (wrapperAppCafe) wrapperAppCafe.classList.add('hidden'); | |
| }); | |
| tabBatch.addEventListener('click', () => { | |
| tabBatch.classList.add('active'); | |
| tabIndividual.classList.remove('active'); | |
| if (tabAppCafe) tabAppCafe.classList.remove('active'); | |
| wrapperBatch.classList.remove('hidden'); | |
| wrapperIndividual.classList.add('hidden'); | |
| if (wrapperAppCafe) wrapperAppCafe.classList.add('hidden'); | |
| }); | |
| if (tabAppCafe) { | |
| tabAppCafe.addEventListener('click', () => { | |
| tabAppCafe.classList.add('active'); | |
| tabIndividual.classList.remove('active'); | |
| tabBatch.classList.remove('active'); | |
| wrapperAppCafe.classList.remove('hidden'); | |
| wrapperIndividual.classList.add('hidden'); | |
| wrapperBatch.classList.add('hidden'); | |
| checkSession(); | |
| }); | |
| } | |
| // Drag and Drop for Batch | |
| ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { | |
| batchUploadArea.addEventListener(eventName, preventDefaults, false); | |
| }); | |
| ['dragenter', 'dragover'].forEach(eventName => { | |
| batchUploadArea.addEventListener(eventName, () => batchUploadArea.classList.add('dragover'), false); | |
| }); | |
| ['dragleave', 'drop'].forEach(eventName => { | |
| batchUploadArea.addEventListener(eventName, () => batchUploadArea.classList.remove('dragover'), false); | |
| }); | |
| batchUploadArea.addEventListener('drop', handleBatchDrop, false); | |
| batchUploadArea.addEventListener('click', () => batchFileInput.click()); | |
| batchFileInput.addEventListener('change', handleBatchFileSelect); | |
| function handleBatchDrop(e) { | |
| const dt = e.dataTransfer; | |
| handleBatchFiles(dt.files); | |
| } | |
| function handleBatchFileSelect(e) { | |
| handleBatchFiles(e.target.files); | |
| } | |
| function handleBatchFiles(files) { | |
| const validFiles = Array.from(files).filter(file => file.type.startsWith('image/')); | |
| if (validFiles.length > 0) { | |
| selectedBatchFiles = validFiles; | |
| batchImagePreview.innerHTML = ''; | |
| validFiles.forEach(file => { | |
| const reader = new FileReader(); | |
| reader.onload = (e) => { | |
| const img = document.createElement('img'); | |
| img.src = e.target.result; | |
| batchImagePreview.appendChild(img); | |
| }; | |
| reader.readAsDataURL(file); | |
| }); | |
| batchImagePreview.classList.remove('hidden'); | |
| batchUploadContent.classList.add('hidden'); | |
| batchAnalyzeBtn.disabled = false; | |
| } else { | |
| alert('Por favor, selecione arquivos de imagem válidos.'); | |
| } | |
| } | |
| batchAnalyzeBtn.addEventListener('click', async () => { | |
| if (selectedBatchFiles.length === 0) return; | |
| // UI Loading State | |
| batchAnalyzeBtn.disabled = true; | |
| batchBtnText.innerHTML = '<i class="ph-bold ph-scan"></i> Analisando Lote...'; | |
| batchLoader.classList.remove('hidden'); | |
| batchResultsPlaceholder.querySelector('p').textContent = `Processando ${selectedBatchFiles.length} imagens (isso vai levar um tempo)...`; | |
| batchResultsContent.classList.add('hidden'); | |
| batchResultsPlaceholder.classList.remove('hidden'); | |
| const formData = new FormData(); | |
| selectedBatchFiles.forEach(file => { | |
| formData.append('files', file); | |
| }); | |
| try { | |
| const response = await fetch('/predict_batch', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| if (!response.ok) { | |
| const errData = await response.json(); | |
| throw new Error(errData.error || 'Erro na análise em lote.'); | |
| } | |
| const data = await response.json(); | |
| updateBatchResults(data); | |
| } catch (error) { | |
| alert(error.message); | |
| batchResultsPlaceholder.querySelector('p').textContent = 'Falha na análise em lote. Tente novamente.'; | |
| } finally { | |
| batchAnalyzeBtn.disabled = false; | |
| batchBtnText.innerHTML = '<i class="ph-bold ph-scan"></i> Analisar Lote'; | |
| batchLoader.classList.add('hidden'); | |
| } | |
| }); | |
| function updateBatchResults(data) { | |
| batchResultsPlaceholder.classList.add('hidden'); | |
| batchResultsContent.classList.remove('hidden'); | |
| // --- Save Result Logic para Lote --- | |
| const saveContainer = document.getElementById('save-result-container-batch'); | |
| const saveBtn = document.getElementById('btn-save-result-batch'); | |
| const saveMsg = document.getElementById('save-result-msg-batch'); | |
| const hasSupabaseFiles = selectedBatchFiles && selectedBatchFiles.some(f => f.supabasePictureId); | |
| if (hasSupabaseFiles && data.detalhes && data.detalhes.length > 0 && supabaseClient) { | |
| saveContainer.classList.remove('hidden'); | |
| saveBtn.style.display = 'inline-flex'; | |
| saveMsg.style.display = 'none'; | |
| const newSaveBtn = saveBtn.cloneNode(true); | |
| saveBtn.parentNode.replaceChild(newSaveBtn, saveBtn); | |
| newSaveBtn.addEventListener('click', async () => { | |
| newSaveBtn.disabled = true; | |
| newSaveBtn.innerHTML = '<i class="ph-duotone ph-spinner" style="animation: spin 1s linear infinite;"></i> Salvando...'; | |
| try { | |
| const { data: allDiseases, error: dError } = await supabaseClient.from('diseases').select('id, name'); | |
| if (dError) throw new Error("Erro ao buscar doenças."); | |
| const diseaseMap = {}; | |
| allDiseases.forEach(d => { diseaseMap[d.name] = d.id; }); | |
| let savedCount = 0; | |
| for (const result of data.detalhes) { | |
| if (result.mais_frequente === "Nenhuma detecção encontrada") continue; | |
| const diseaseId = diseaseMap[result.mais_frequente]; | |
| if (!diseaseId) continue; | |
| const matchingFile = selectedBatchFiles.find(f => f.name === result.filename); | |
| if (matchingFile && matchingFile.supabasePictureId) { | |
| await supabaseClient.from('pictures').update({ id_disease: diseaseId }).eq('id', matchingFile.supabasePictureId); | |
| savedCount++; | |
| } | |
| } | |
| newSaveBtn.style.display = 'none'; | |
| saveMsg.innerHTML = `<i class="ph-fill ph-check-circle"></i> ${savedCount} resultado(s) salvo(s) com sucesso!`; | |
| saveMsg.style.color = "#2f855a"; | |
| saveMsg.style.display = 'block'; | |
| } catch (e) { | |
| newSaveBtn.disabled = false; | |
| newSaveBtn.innerHTML = '<i class="ph-bold ph-floppy-disk-back"></i> Tentar Novamente'; | |
| saveMsg.textContent = e.message; | |
| saveMsg.style.color = "#e53e3e"; | |
| saveMsg.style.display = 'block'; | |
| } | |
| }); | |
| } else { | |
| if (saveContainer) saveContainer.classList.add('hidden'); | |
| } | |
| batchTotalLeaves.textContent = data.total_folhas; | |
| if (data.folhas_com_deteccao > 0) { | |
| let mainDisease = "Nenhuma detectada"; | |
| let maxQtd = -1; | |
| let maxPct = 0; | |
| for (const [doenca, stats] of Object.entries(data.estatisticas)) { | |
| if (stats.quantidade > maxQtd) { | |
| maxQtd = stats.quantidade; | |
| maxPct = stats.porcentagem; | |
| mainDisease = doenca; | |
| } | |
| } | |
| batchMainDisease.textContent = `${mainDisease} (${maxPct}%)`; | |
| } else { | |
| batchMainDisease.textContent = "Nenhuma detectada"; | |
| } | |
| renderBatchChart(data.estatisticas); | |
| } | |
| function renderBatchChart(estatisticas) { | |
| const labels = []; | |
| const dataValues = []; | |
| // Adiciona a porcentagem direto no label (Ex: Ferrugem (60.0%)) e usa o valor para plotar | |
| for (const [doenca, stats] of Object.entries(estatisticas)) { | |
| labels.push(`${doenca} (${stats.porcentagem}%)`); | |
| dataValues.push(stats.porcentagem); | |
| } | |
| if (batchChartInstance) { | |
| batchChartInstance.destroy(); | |
| } | |
| batchChartInstance = new Chart(batchChartCanvas, { | |
| type: 'doughnut', | |
| data: { | |
| labels: labels, | |
| datasets: [{ | |
| data: dataValues, | |
| backgroundColor: chartColors, | |
| borderWidth: 0, | |
| hoverOffset: 4 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { | |
| legend: { | |
| position: 'right', | |
| labels: { | |
| color: '#4a5568', | |
| font: { family: "'Outfit', sans-serif", size: 10 }, | |
| padding: 10, | |
| boxWidth: 12 | |
| } | |
| }, | |
| tooltip: { | |
| callbacks: { | |
| label: function(context) { | |
| let label = context.label || ''; | |
| return ' ' + label; | |
| } | |
| }, | |
| backgroundColor: 'rgba(255, 255, 255, 0.95)', | |
| titleColor: '#2d3748', | |
| bodyColor: '#2d3748', | |
| borderColor: 'rgba(47, 133, 90, 0.2)', | |
| borderWidth: 1, | |
| padding: 10, | |
| titleFont: { family: "'Outfit', sans-serif", size: 12, weight: 'bold' }, | |
| bodyFont: { family: "'Outfit', sans-serif", size: 12 } | |
| } | |
| }, | |
| cutout: '70%' | |
| } | |
| }); | |
| } | |
| // --- SUPABASE AUTH LOGIC --- | |
| async function checkSession() { | |
| if (!supabaseClient) return; | |
| const { data, error } = await supabaseClient.auth.getSession(); | |
| if (data && data.session) { | |
| showLoggedInState(data.session.user); | |
| } else { | |
| showLoginState(); | |
| } | |
| } | |
| function showLoggedInState(user) { | |
| if (appcafeLoginContainer) appcafeLoginContainer.classList.add('hidden'); | |
| if (appcafePromo) appcafePromo.classList.add('hidden'); | |
| if (appcafeLoggedIn) appcafeLoggedIn.classList.remove('hidden'); | |
| const welcomeText = document.getElementById('welcome-user-text'); | |
| if (welcomeText && user) { | |
| const name = user.user_metadata?.display_name || user.email.split('@')[0]; | |
| welcomeText.textContent = `Bem-vindo(a), ${name}`; | |
| } | |
| } | |
| function showLoginState() { | |
| if (appcafeLoginContainer) appcafeLoginContainer.classList.remove('hidden'); | |
| if (appcafePromo) appcafePromo.classList.remove('hidden'); | |
| if (appcafeLoggedIn) appcafeLoggedIn.classList.add('hidden'); | |
| if (appcafeError) appcafeError.style.display = 'none'; | |
| if (appcafeEmailInput) appcafeEmailInput.value = ''; | |
| if (appcafePasswordInput) appcafePasswordInput.value = ''; | |
| } | |
| if (appcafeLoginForm) { | |
| appcafeLoginForm.addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| if (!supabaseClient) return; | |
| const email = appcafeEmailInput.value; | |
| const password = appcafePasswordInput.value; | |
| // UI Loading State | |
| appcafeLoginBtn.disabled = true; | |
| if (appcafeBtnText) appcafeBtnText.textContent = 'Entrando...'; | |
| if (appcafeLoader) appcafeLoader.classList.remove('hidden'); | |
| if (appcafeError) appcafeError.style.display = 'none'; | |
| const { data, error } = await supabaseClient.auth.signInWithPassword({ | |
| email: email, | |
| password: password, | |
| }); | |
| appcafeLoginBtn.disabled = false; | |
| if (appcafeBtnText) appcafeBtnText.textContent = 'Entrar'; | |
| if (appcafeLoader) appcafeLoader.classList.add('hidden'); | |
| if (error) { | |
| if (appcafeError) { | |
| appcafeError.textContent = error.message === 'Invalid login credentials' ? 'E-mail ou senha incorretos.' : error.message; | |
| appcafeError.style.display = 'block'; | |
| } | |
| } else { | |
| showLoggedInState(); | |
| } | |
| }); | |
| } | |
| const appcafeForgotPassword = document.getElementById('appcafe-forgot-password'); | |
| if (appcafeForgotPassword) { | |
| appcafeForgotPassword.addEventListener('click', async (e) => { | |
| e.preventDefault(); | |
| if (!supabaseClient) return; | |
| const email = appcafeEmailInput.value; | |
| if (!email) { | |
| alert('Por favor, preencha o campo de e-mail para recuperar a senha.'); | |
| return; | |
| } | |
| try { | |
| appcafeLoginBtn.disabled = true; | |
| if (appcafeBtnText) appcafeBtnText.textContent = 'Enviando...'; | |
| const { error } = await supabaseClient.auth.resetPasswordForEmail(email, { | |
| redirectTo: window.location.origin + '/redefinir-senha' | |
| }); | |
| if (error) throw error; | |
| alert('E-mail de recuperação enviado! Verifique sua caixa de entrada.'); | |
| } catch (err) { | |
| alert('Erro ao enviar e-mail: ' + err.message); | |
| } finally { | |
| appcafeLoginBtn.disabled = false; | |
| if (appcafeBtnText) appcafeBtnText.textContent = 'Entrar'; | |
| } | |
| }); | |
| } | |
| if (appcafeLogoutBtn) { | |
| appcafeLogoutBtn.addEventListener('click', async () => { | |
| if (!supabaseClient) return; | |
| await supabaseClient.auth.signOut(); | |
| showLoginState(); | |
| }); | |
| } | |
| // Inicializa verificando a sessão caso inicie na tab (embora comece escondida) | |
| checkSession(); | |
| // --- FILTER LOGIC --- | |
| function renderGalleryCards(picturesList) { | |
| galleryContainer.innerHTML = ''; | |
| if (picturesList && picturesList.length > 0) { | |
| picturesList.forEach(pic => { | |
| const isSelected = selectedGalleryPictures.has(pic.picture); | |
| const card = document.createElement('div'); | |
| card.style = `background: #1e293b; border-radius: 12px; overflow: hidden; box-shadow: 0 10px 15px -3px rgba(0,0,0,0.5); display: flex; flex-direction: column; border: 2px solid ${isSelected ? 'var(--accent-primary)' : 'rgba(255,255,255,0.1)'}; transition: all 0.2s; cursor: pointer; position: relative; transform: ${isSelected ? 'scale(0.98)' : 'scale(1)'};`; | |
| // Overlay for selection checkmark | |
| const checkOverlay = document.createElement('div'); | |
| checkOverlay.style = `position: absolute; top: 10px; right: 10px; width: 30px; height: 30px; border-radius: 50%; background: ${isSelected ? 'var(--accent-primary)' : 'rgba(0,0,0,0.5)'}; border: 2px solid white; display: flex; align-items: center; justify-content: center; z-index: 5; transition: background 0.2s;`; | |
| checkOverlay.innerHTML = `<i class="ph-bold ph-check" style="color: white; font-size: 1.2rem; display: ${isSelected ? 'block' : 'none'};"></i>`; | |
| card.onmouseover = () => { if (!selectedGalleryPictures.has(pic.picture)) card.style.transform = "translateY(-5px)"; }; | |
| card.onmouseout = () => { if (!selectedGalleryPictures.has(pic.picture)) card.style.transform = "translateY(0)"; }; | |
| card.addEventListener('click', () => { | |
| if (selectedGalleryPictures.has(pic.picture)) { | |
| selectedGalleryPictures.delete(pic.picture); | |
| card.style.border = "2px solid rgba(255,255,255,0.1)"; | |
| card.style.transform = "translateY(0)"; | |
| checkOverlay.style.background = "rgba(0,0,0,0.5)"; | |
| checkOverlay.querySelector('i').style.display = "none"; | |
| } else { | |
| selectedGalleryPictures.set(pic.picture, pic.id); | |
| card.style.border = "2px solid var(--accent-primary)"; | |
| card.style.transform = "scale(0.98)"; | |
| checkOverlay.style.background = "var(--accent-primary)"; | |
| checkOverlay.querySelector('i').style.display = "block"; | |
| } | |
| updateGalleryActionBar(); | |
| }); | |
| const imgContainer = document.createElement('div'); | |
| imgContainer.style = "width: 100%; height: 220px; background: #0f172a; overflow: hidden; position: relative;"; | |
| const img = document.createElement('img'); | |
| img.src = pic.picture; | |
| img.alt = "Foto do Café"; | |
| img.style = "width: 100%; height: 100%; object-fit: cover;"; | |
| const info = document.createElement('div'); | |
| info.style = "padding: 20px; display: flex; flex-direction: column; flex-grow: 1; justify-content: space-between;"; | |
| const dateText = document.createElement('p'); | |
| const dataFormatada = new Date(pic.data).toLocaleDateString('pt-BR', { day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' }); | |
| dateText.innerHTML = `<i class="ph-bold ph-calendar-blank"></i> ${dataFormatada}`; | |
| dateText.style = "margin: 0 0 8px 0; font-size: 0.85rem; color: #94a3b8; display: flex; align-items: center; gap: 6px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;"; | |
| const descText = document.createElement('p'); | |
| descText.innerHTML = pic.description ? `"${pic.description}"` : '<em>Sem descrição fornecida.</em>'; | |
| descText.style = "margin: 0 0 10px 0; font-size: 1rem; color: #f8fafc; font-style: italic; line-height: 1.5;"; | |
| let diseaseHtml = ''; | |
| if (pic.id_disease && diseaseDictionary[pic.id_disease]) { | |
| diseaseHtml = `<span style="background: rgba(47, 133, 90, 0.2); color: #48bb78; padding: 4px 8px; border-radius: 4px; font-size: 0.8rem; font-weight: bold; border: 1px solid rgba(47, 133, 90, 0.5); display: inline-block;"><i class="ph-bold ph-virus"></i> ${diseaseDictionary[pic.id_disease]}</span>`; | |
| } | |
| imgContainer.appendChild(img); | |
| card.appendChild(checkOverlay); | |
| info.appendChild(dateText); | |
| info.appendChild(descText); | |
| if (diseaseHtml) { | |
| const dWrap = document.createElement('div'); | |
| dWrap.innerHTML = diseaseHtml; | |
| info.appendChild(dWrap); | |
| } | |
| card.appendChild(imgContainer); | |
| card.appendChild(info); | |
| galleryContainer.appendChild(card); | |
| }); | |
| } else { | |
| galleryContainer.innerHTML = '<div style="grid-column: 1 / -1; text-align: center; padding: 60px 20px; background: rgba(255,255,255,0.05); border-radius: 12px; border: 1px dashed rgba(255,255,255,0.2);"><i class="ph-duotone ph-image-broken" style="font-size: 4rem; color: #64748b; margin-bottom: 15px;"></i><p style="color: #cbd5e1; font-size: 1.2rem; margin: 0;">Nenhuma foto encontrada.</p></div>'; | |
| } | |
| } | |
| function updateGalleryActionBar() { | |
| const count = selectedGalleryPictures.size; | |
| selectedCountText.textContent = count; | |
| if (count > 0) { | |
| galleryActionBar.classList.remove('hidden'); | |
| btnAnalyzeSelected.disabled = false; | |
| if (isHeatmapMode) { | |
| analyzeActionText.innerHTML = '<i class="ph-bold ph-map-trifold"></i> Gerar Mapa de Calor'; | |
| analyzeActionHint.textContent = `As ${count} imagens serão usadas no mapa.`; | |
| if (btnDeleteSelected) btnDeleteSelected.style.display = 'none'; | |
| } else { | |
| if (btnDeleteSelected) btnDeleteSelected.style.display = 'flex'; | |
| if (count === 1) { | |
| analyzeActionText.innerHTML = '<i class="ph-bold ph-image"></i> Análise Individual'; | |
| analyzeActionHint.textContent = 'A imagem será enviada para a Análise Individual.'; | |
| } else { | |
| analyzeActionText.innerHTML = '<i class="ph-bold ph-images"></i> Análise em Lote'; | |
| analyzeActionHint.textContent = `As ${count} imagens serão enviadas para Análise em Lote.`; | |
| } | |
| } | |
| } else { | |
| galleryActionBar.classList.add('hidden'); | |
| btnAnalyzeSelected.disabled = true; | |
| } | |
| } | |
| // --- HEATMAP LOGIC --- | |
| const baseMaps = { | |
| dark: L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png', { | |
| attribution: '© OpenStreetMap contributors © CARTO', | |
| subdomains: 'abcd', | |
| maxZoom: 20 | |
| }), | |
| osm: L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { | |
| maxZoom: 19, | |
| attribution: '© OpenStreetMap' | |
| }) | |
| }; | |
| function openHeatmapModal() { | |
| galleryModal.classList.add('hidden'); | |
| galleryModal.style.display = 'none'; | |
| heatmapModal.classList.remove('hidden'); | |
| heatmapModal.style.display = 'flex'; | |
| // Obter as imagens selecionadas | |
| const selectedIds = Array.from(selectedGalleryPictures.values()); | |
| const selectedPics = currentGalleryPictures.filter(p => selectedIds.includes(p.id)); | |
| if (!leafletMapInstance) { | |
| leafletMapInstance = L.map('leaflet-map').setView([-14.235, -51.925], 4); | |
| baseMaps.dark.addTo(leafletMapInstance); | |
| heatmapStyleFilter.addEventListener('change', (e) => { | |
| if (e.target.value === 'dark') { | |
| leafletMapInstance.removeLayer(baseMaps.osm); | |
| baseMaps.dark.addTo(leafletMapInstance); | |
| } else { | |
| leafletMapInstance.removeLayer(baseMaps.dark); | |
| baseMaps.osm.addTo(leafletMapInstance); | |
| } | |
| }); | |
| heatmapDiseaseFilter.addEventListener('change', () => { | |
| updateHeatmapLayer(selectedPics); | |
| }); | |
| // Adicionar Legenda | |
| const legend = L.control({position: 'bottomright'}); | |
| legend.onAdd = function (map) { | |
| const div = L.DomUtil.create('div', 'info legend'); | |
| div.style.backgroundColor = 'rgba(255, 255, 255, 0.9)'; | |
| div.style.padding = '10px'; | |
| div.style.borderRadius = '8px'; | |
| div.style.boxShadow = '0 0 15px rgba(0,0,0,0.2)'; | |
| div.style.color = '#333'; | |
| div.style.fontFamily = 'inherit'; | |
| div.innerHTML += '<strong style="display:block; margin-bottom: 5px;">Densidade de Focos</strong>'; | |
| div.innerHTML += '<i style="background: blue; width: 12px; height: 12px; display: inline-block; margin-right: 5px;"></i> Baixa<br>'; | |
| div.innerHTML += '<i style="background: lime; width: 12px; height: 12px; display: inline-block; margin-right: 5px;"></i> Média<br>'; | |
| div.innerHTML += '<i style="background: red; width: 12px; height: 12px; display: inline-block; margin-right: 5px;"></i> Alta<br>'; | |
| return div; | |
| }; | |
| legend.addTo(leafletMapInstance); | |
| } | |
| setTimeout(() => { | |
| leafletMapInstance.invalidateSize(); | |
| updateHeatmapLayer(selectedPics); | |
| if (selectedPics.length > 0) { | |
| const lats = selectedPics.map(p => p.lat).filter(Boolean); | |
| const lngs = selectedPics.map(p => p.long).filter(Boolean); | |
| if (lats.length > 0 && lngs.length > 0) { | |
| const bounds = [ | |
| [Math.min(...lats), Math.min(...lngs)], | |
| [Math.max(...lats), Math.max(...lngs)] | |
| ]; | |
| leafletMapInstance.fitBounds(bounds, { padding: [50, 50] }); | |
| } | |
| } | |
| }, 300); | |
| } | |
| function updateHeatmapLayer(pics) { | |
| if (currentHeatLayer) { | |
| leafletMapInstance.removeLayer(currentHeatLayer); | |
| } | |
| const filterVal = heatmapDiseaseFilter.value; | |
| const heatData = []; | |
| pics.forEach(pic => { | |
| if (pic.lat && pic.long) { | |
| if (filterVal === 'all' || String(pic.id_disease) === filterVal) { | |
| heatData.push([pic.lat, pic.long, 1]); | |
| } | |
| } | |
| }); | |
| currentHeatLayer = L.heatLayer(heatData, { | |
| radius: 25, | |
| blur: 15, | |
| maxZoom: 10, | |
| gradient: {0.4: 'blue', 0.6: 'cyan', 0.7: 'lime', 0.8: 'yellow', 1.0: 'red'} | |
| }).addTo(leafletMapInstance); | |
| } | |
| if (closeHeatmapBtn) { | |
| closeHeatmapBtn.addEventListener('click', () => { | |
| heatmapModal.classList.add('hidden'); | |
| heatmapModal.style.display = 'none'; | |
| }); | |
| } | |
| async function urlToFile(url, filename, mimeType) { | |
| const res = await fetch(url); | |
| const buf = await res.arrayBuffer(); | |
| return new File([buf], filename, {type: mimeType}); | |
| } | |
| if (btnSelectAll) { | |
| btnSelectAll.addEventListener('click', () => { | |
| // Select only what is currently rendered/filtered | |
| const cards = galleryContainer.querySelectorAll('div > img'); // Hack to know there are cards | |
| if (cards.length > 0) { | |
| // Actually we just use currentGalleryPictures, but let's respect filters if we want. | |
| // Re-run filter logic to get currently visible: | |
| const descVal = filterDesc ? filterDesc.value.toLowerCase() : ''; | |
| const startVal = filterDateStart ? filterDateStart.value : ''; | |
| const endVal = filterDateEnd ? filterDateEnd.value : ''; | |
| const filtered = currentGalleryPictures.filter(pic => { | |
| const picDesc = (pic.description || '').toLowerCase(); | |
| if (descVal && !picDesc.includes(descVal)) return false; | |
| if (startVal || endVal) { | |
| const picDateObj = new Date(pic.data); | |
| const year = picDateObj.getFullYear(); | |
| const month = String(picDateObj.getMonth() + 1).padStart(2, '0'); | |
| const day = String(picDateObj.getDate()).padStart(2, '0'); | |
| const picDateStr = `${year}-${month}-${day}`; | |
| if (startVal && picDateStr < startVal) return false; | |
| if (endVal && picDateStr > endVal) return false; | |
| } | |
| return true; | |
| }); | |
| filtered.forEach(pic => selectedGalleryPictures.set(pic.picture, pic.id)); | |
| applyGalleryFilters(); // Re-render to show selection styling | |
| updateGalleryActionBar(); | |
| } | |
| }); | |
| } | |
| if (btnDeleteSelected) { | |
| btnDeleteSelected.addEventListener('click', async () => { | |
| if (selectedGalleryPictures.size === 0) return; | |
| const confirmDelete = confirm(`Tem certeza que deseja apagar ${selectedGalleryPictures.size} foto(s)? Isso não pode ser desfeito.`); | |
| if (!confirmDelete) return; | |
| btnDeleteSelected.disabled = true; | |
| btnDeleteSelected.innerHTML = '<i class="ph-duotone ph-spinner spin"></i> Apagando...'; | |
| try { | |
| const idsToDelete = Array.from(selectedGalleryPictures.values()); | |
| const urlsToDelete = Array.from(selectedGalleryPictures.keys()); | |
| // 1. Delete from DB | |
| const { error: dbError } = await supabaseClient.from('pictures').delete().in('id', idsToDelete); | |
| if (dbError) throw new Error("Erro ao apagar do banco de dados: " + dbError.message); | |
| // 2. Delete from Storage | |
| const storagePaths = urlsToDelete.map(url => { | |
| // Extract filename from the end of publicUrl | |
| return url.split('/').pop(); | |
| }); | |
| const { error: stError } = await supabaseClient.storage.from('FotosCafe').remove(storagePaths); | |
| if (stError) console.warn("Aviso: Falha ao apagar arquivos do storage", stError); | |
| // Update UI state | |
| currentGalleryPictures = currentGalleryPictures.filter(p => !idsToDelete.includes(p.id)); | |
| selectedGalleryPictures.clear(); | |
| alert('Fotos apagadas com sucesso!'); | |
| applyGalleryFilters(); | |
| updateGalleryActionBar(); | |
| } catch (err) { | |
| alert(err.message); | |
| } finally { | |
| btnDeleteSelected.disabled = false; | |
| btnDeleteSelected.innerHTML = '<i class="ph-bold ph-trash"></i> Excluir'; | |
| } | |
| }); | |
| } | |
| if (btnAnalyzeSelected) { | |
| btnAnalyzeSelected.addEventListener('click', async () => { | |
| if (isHeatmapMode) { | |
| openHeatmapModal(); | |
| return; | |
| } | |
| const items = Array.from(selectedGalleryPictures.entries()); // [url, id] | |
| if (items.length === 0) return; | |
| btnAnalyzeSelected.disabled = true; | |
| analyzeActionText.textContent = 'Baixando imagens...'; | |
| analyzeActionLoader.classList.remove('hidden'); | |
| try { | |
| // Fechar modal | |
| galleryModal.classList.add('hidden'); | |
| galleryModal.style.display = 'none'; | |
| // Baixar arquivos | |
| const files = []; | |
| for (let i = 0; i < items.length; i++) { | |
| const [url, picId] = items[i]; | |
| const filename = `foto_appcafe_${picId}.jpg`; | |
| const file = await urlToFile(url, filename, 'image/jpeg'); | |
| file.supabasePictureId = picId; | |
| files.push(file); | |
| } | |
| if (files.length === 1) { | |
| // Send to Individual Analysis | |
| tabIndividual.click(); | |
| handleFile(files[0]); | |
| // Auto click analyze | |
| setTimeout(() => analyzeBtn.click(), 500); | |
| } else { | |
| // Send to Batch Analysis | |
| tabBatch.click(); | |
| handleBatchFiles(files); | |
| // Auto click analyze | |
| setTimeout(() => batchAnalyzeBtn.click(), 500); | |
| } | |
| // Limpar seleção apos enviar | |
| selectedGalleryPictures.clear(); | |
| updateGalleryActionBar(); | |
| } catch (error) { | |
| console.error("Erro ao baixar imagens:", error); | |
| alert("Ocorreu um erro ao preparar as imagens para análise. Tente novamente."); | |
| galleryModal.classList.remove('hidden'); | |
| galleryModal.style.display = 'flex'; | |
| } finally { | |
| btnAnalyzeSelected.disabled = false; | |
| analyzeActionLoader.classList.add('hidden'); | |
| } | |
| }); | |
| } | |
| function applyGalleryFilters() { | |
| if (!currentGalleryPictures) return; | |
| const descVal = filterDesc ? filterDesc.value.toLowerCase() : ''; | |
| const startVal = filterDateStart ? filterDateStart.value : ''; | |
| const endVal = filterDateEnd ? filterDateEnd.value : ''; | |
| const filterDiseaseSelect = document.getElementById('filter-disease-select'); | |
| const diseaseVal = filterDiseaseSelect && !filterDiseaseSelect.parentElement.classList.contains('hidden') ? filterDiseaseSelect.value : 'all'; | |
| const filtered = currentGalleryPictures.filter(pic => { | |
| const picDesc = (pic.description || '').toLowerCase(); | |
| if (descVal && !picDesc.includes(descVal)) return false; | |
| if (startVal || endVal) { | |
| const picDateObj = new Date(pic.data); | |
| const year = picDateObj.getFullYear(); | |
| const month = String(picDateObj.getMonth() + 1).padStart(2, '0'); | |
| const day = String(picDateObj.getDate()).padStart(2, '0'); | |
| const picDateStr = `${year}-${month}-${day}`; | |
| if (startVal && picDateStr < startVal) return false; | |
| if (endVal && picDateStr > endVal) return false; | |
| } | |
| if (diseaseVal !== 'all' && String(pic.id_disease) !== diseaseVal) { | |
| return false; | |
| } | |
| return true; | |
| }); | |
| renderGalleryCards(filtered); | |
| } | |
| if (filterDesc) filterDesc.addEventListener('input', applyGalleryFilters); | |
| if (filterDateStart) filterDateStart.addEventListener('change', applyGalleryFilters); | |
| if (filterDateEnd) filterDateEnd.addEventListener('change', applyGalleryFilters); | |
| const filterDiseaseSelect = document.getElementById('filter-disease-select'); | |
| if (filterDiseaseSelect) filterDiseaseSelect.addEventListener('change', applyGalleryFilters); | |
| if (clearFiltersBtn) { | |
| clearFiltersBtn.addEventListener('click', () => { | |
| if (filterDesc) filterDesc.value = ''; | |
| if (filterDateStart) filterDateStart.value = ''; | |
| if (filterDateEnd) filterDateEnd.value = ''; | |
| if (filterDiseaseSelect) filterDiseaseSelect.value = 'all'; | |
| applyGalleryFilters(); | |
| }); | |
| } | |
| // --- DASHBOARD ACTIONS --- | |
| if (btnDashboardIndividual) { | |
| btnDashboardIndividual.addEventListener('click', () => { | |
| tabIndividual.click(); | |
| }); | |
| } | |
| if (btnDashboardLote) { | |
| btnDashboardLote.addEventListener('click', () => { | |
| tabBatch.click(); | |
| }); | |
| } | |
| if (btnMapaCalor) { | |
| btnMapaCalor.addEventListener('click', async () => { | |
| isHeatmapMode = true; | |
| btnOlharFotos.click(); // Reuse gallery open logic but in heatmap mode | |
| }); | |
| } | |
| if (btnOlharFotos) { | |
| btnOlharFotos.addEventListener('click', async (e) => { | |
| if (!supabaseClient) return; | |
| // If click was explicitly from "Olhar Fotos", disable heatmap mode | |
| if (e && e.isTrusted) { | |
| isHeatmapMode = false; | |
| } | |
| // Abre o modal | |
| galleryModal.classList.remove('hidden'); | |
| galleryModal.style.display = 'flex'; | |
| galleryContainer.innerHTML = ''; | |
| galleryLoader.classList.remove('hidden'); | |
| // Set gallery title | |
| const galleryTitle = galleryModal.querySelector('h2'); | |
| if (isHeatmapMode) { | |
| galleryTitle.innerHTML = '<i class="ph-bold ph-map-trifold"></i> Selecionar Fotos para Mapa de Calor'; | |
| } else { | |
| galleryTitle.innerHTML = '<i class="ph-bold ph-images"></i> Suas Fotos'; | |
| } | |
| try { | |
| // Preload diseases if not loaded | |
| if (Object.keys(diseaseDictionary).length === 0) { | |
| const { data: dData, error: dErr } = await supabaseClient.from('diseases').select('*'); | |
| if (!dErr && dData) { | |
| dData.forEach(d => { diseaseDictionary[d.id] = d.name; }); | |
| // Populate heatmap dropdown | |
| heatmapDiseaseFilter.innerHTML = '<option value="all" style="color: black;">Todas as Doenças</option>'; | |
| dData.forEach(d => { | |
| heatmapDiseaseFilter.innerHTML += `<option value="${d.id}" style="color: black;">Apenas ${d.name}</option>`; | |
| }); | |
| } | |
| } | |
| // Pega o ID do usuario logado | |
| const { data: userData, error: userError } = await supabaseClient.auth.getUser(); | |
| if (userError || !userData?.user) throw new Error('Erro ao identificar usuário logado.'); | |
| const userId = userData.user.id; | |
| // Busca as fotos na tabela pictures | |
| let query = supabaseClient.from('pictures').select('*').eq('id_user', userId); | |
| if (isHeatmapMode) { | |
| query = query.not('id_disease', 'is', null); | |
| } | |
| const { data: pictures, error: picError } = await query.order('data', { ascending: false }); | |
| if (picError) throw new Error(picError.message); | |
| currentGalleryPictures = pictures || []; | |
| galleryLoader.classList.add('hidden'); | |
| if (currentGalleryPictures.length > 0) { | |
| if (galleryFilters) galleryFilters.classList.remove('hidden'); | |
| const filterDiseaseContainer = document.getElementById('filter-disease-container'); | |
| const filterDiseaseSelectEl = document.getElementById('filter-disease-select'); | |
| if (isHeatmapMode && filterDiseaseContainer && filterDiseaseSelectEl) { | |
| filterDiseaseContainer.classList.remove('hidden'); | |
| filterDiseaseSelectEl.innerHTML = '<option value="all" style="color: black;">Todas as Doenças</option>'; | |
| Object.entries(diseaseDictionary).forEach(([id, name]) => { | |
| filterDiseaseSelectEl.innerHTML += `<option value="${id}" style="color: black;">${name}</option>`; | |
| }); | |
| } else if (filterDiseaseContainer) { | |
| filterDiseaseContainer.classList.add('hidden'); | |
| } | |
| // Reset filters on fresh open | |
| if (filterDesc) filterDesc.value = ''; | |
| if (filterDateStart) filterDateStart.value = ''; | |
| if (filterDateEnd) filterDateEnd.value = ''; | |
| if (filterDiseaseSelectEl) filterDiseaseSelectEl.value = 'all'; | |
| // Reset selection | |
| selectedGalleryPictures.clear(); | |
| updateGalleryActionBar(); | |
| applyGalleryFilters(); | |
| } else { | |
| if (galleryFilters) galleryFilters.classList.add('hidden'); | |
| selectedGalleryPictures.clear(); | |
| updateGalleryActionBar(); | |
| renderGalleryCards([]); | |
| } | |
| } catch (error) { | |
| console.error("Erro ao buscar fotos:", error); | |
| galleryLoader.classList.add('hidden'); | |
| galleryContainer.innerHTML = `<p style="color: #fc8181; grid-column: 1 / -1; text-align: center;">Erro ao carregar fotos: ${error.message}</p>`; | |
| } | |
| }); | |
| } | |
| if (closeGalleryBtn) { | |
| closeGalleryBtn.addEventListener('click', () => { | |
| galleryModal.classList.add('hidden'); | |
| galleryModal.style.display = 'none'; | |
| }); | |
| } | |
| }); | |