import { S } from './state.js'; // ── Profiles (15c) ─────────────────────────────────────────────────────────── async function loadProfiles() { try { const r = await fetch('/api/profiles'); if (!r.ok) return; const d = await r.json(); S._profiles = d.profiles || []; _renderProfileSelect(); } catch(e) { /* profiles not critical */ } } function _renderProfileSelect() { const sel = document.getElementById('profileSelect'); if (!sel) return; const prev = sel.value; // Clear all except the placeholder option (first) while (sel.options.length > 1) sel.remove(1); for (const p of S._profiles) { const opt = document.createElement('option'); opt.value = p.id; const last = p.last_run ? ' — ' + p.last_run.slice(0, 10) : ''; opt.textContent = p.name + last; opt.title = p.description || ''; sel.appendChild(opt); } // Restore selection if the profile still exists; else fall back to placeholder if (prev && [...sel.options].some(o => o.value === prev)) { sel.value = prev; } else { sel.value = ''; S._activeProfileId = null; const clrBtn = document.getElementById('profileClearBtn'); if (clrBtn) clrBtn.style.display = 'none'; } } function _setProfileClearBtn(visible) { const btn = document.getElementById('profileClearBtn'); if (btn) btn.style.display = visible ? 'inline-block' : 'none'; } function onProfileChange() { const sel = document.getElementById('profileSelect'); const id = sel.value; if (!id) return; // placeholder can't be selected (disabled), guard anyway const profile = S._profiles.find(p => p.id === id); if (!profile) return; S._activeProfileId = id; _setProfileClearBtn(true); _applyProfile(profile); } // Clear the active profile label without touching sidebar settings. // The sidebar already reflects the loaded (or manually adjusted) state. function clearActiveProfile() { S._activeProfileId = null; const sel = document.getElementById('profileSelect'); if (sel) sel.value = ''; _setProfileClearBtn(false); } function _applyProfile(profile) { // ── Sources ────────────────────────────────────────────────────────────── // Restore source selections from profile — works for both M365 and file sources. // File sources may not be rendered yet (they load async), so store their IDs // in S._pendingProfileSources for renderSourcesPanel() to apply after re-render. const profileSources = profile.sources || []; // Ensure at least M365 source checkboxes are present before reading the DOM. // renderSourcesPanel() is idempotent and fast — safe to call here. if (!document.querySelector('#sourcesPanel input[data-source-id]') && typeof renderSourcesPanel === 'function') { renderSourcesPanel(); } document.querySelectorAll('#sourcesPanel input[data-source-id]').forEach(function(cb) { cb.checked = profileSources.includes(cb.dataset.sourceId); }); _updateAccountsVisibility(); // Deferred file sources — store IDs now, apply when _loadFileSources() resolves. // Don't filter against S._fileSources here — it may be empty at this point. const _knownSourceIds = new Set(['email', 'onedrive', 'sharepoint', 'teams', 'gmail', 'gdrive']); S._pendingProfileSources = (profile.file_sources && profile.file_sources.length) ? profile.file_sources.slice() : profileSources.filter(function(id) { return !_knownSourceIds.has(id); }); // Deferred Google sources — store IDs now, apply when smGoogleRefreshStatus() resolves. const googleIds = profile.google_sources || profileSources.filter(function(id) { return id === 'gmail' || id === 'gdrive'; }); S._pendingGoogleSources = googleIds.slice(); // ── Options ─────────────────────────────────────────────────────────────── const opts = profile.options || {}; if (opts.email_body !== undefined) { const el = document.getElementById('optEmailBody'); if (el) el.checked = opts.email_body; } if (opts.attachments !== undefined) { const el = document.getElementById('optAttachments'); if (el) { el.checked = opts.attachments; // Update the size row opacity directly const sizeRow = document.getElementById('attachSizeRow'); if (sizeRow) sizeRow.style.opacity = opts.attachments ? '1' : '0.4'; } } if (opts.max_attach_mb !== undefined) { const el = document.getElementById('optMaxAttachMB'); if (el) el.value = opts.max_attach_mb; } if (opts.max_emails !== undefined) { const el = document.getElementById('optMaxEmails'); if (el) el.value = opts.max_emails; } if (opts.delta !== undefined) { const el = document.getElementById('optDelta'); if (el) el.checked = opts.delta; } if (opts.scan_photos !== undefined) { const el = document.getElementById('optScanPhotos'); if (el) el.checked = opts.scan_photos; } if (opts.skip_gps_images !== undefined) { const el = document.getElementById('optSkipGps'); if (el) el.checked = opts.skip_gps_images; } if (opts.min_cpr_count !== undefined) { const el = document.getElementById('optMinCpr'); if (el) el.value = opts.min_cpr_count; } if (opts.ocr_lang !== undefined) { const el = document.getElementById('optOcrLang'); if (el) el.value = opts.ocr_lang; } if (opts.cpr_only !== undefined) { const el = document.getElementById('optCprOnly'); if (el) el.checked = opts.cpr_only; } if (opts.scan_emails !== undefined) { const el = document.getElementById('optScanEmails'); if (el) el.checked = opts.scan_emails; } if (opts.scan_phones !== undefined) { const el = document.getElementById('optScanPhones'); if (el) el.checked = opts.scan_phones; } // ── Date filter ─────────────────────────────────────────────────────────── const days = opts.older_than_days; if (days !== undefined) { const hidden = document.getElementById('olderThan'); const dateIn = document.getElementById('olderThanDate'); const presets = document.querySelectorAll('.date-preset'); if (hidden) hidden.value = days; if (dateIn) { if (!days) { dateIn.value = ''; } else { const d = new Date(); d.setDate(d.getDate() - days); dateIn.value = d.toISOString().slice(0, 10); } } // Highlight matching preset button presets.forEach(p => { const y = parseInt(p.dataset.years || '0'); const presetDays = y === 0 ? 0 : y * 365; if (y === 0) { p.classList.toggle('selected', !days); } else { p.classList.toggle('selected', days > 0 && presetDays === days); } }); } // ── Retention ───────────────────────────────────────────────────────────── const retEnabled = !!(opts.retention_enabled || profile.retention_years); const retEl = document.getElementById('optRetention'); if (retEl) { retEl.checked = retEnabled; // Show/hide panel directly const panel = document.getElementById('retentionPanel'); if (panel) panel.style.display = retEnabled ? 'block' : 'none'; } if (profile.retention_years) { const el = document.getElementById('optRetentionYears'); if (el) el.value = profile.retention_years; } if (profile.fiscal_year_end) { const el = document.getElementById('optFiscalYearEnd'); if (el) el.value = profile.fiscal_year_end; } updateRetentionCutoffHint && updateRetentionCutoffHint(); // ── User selection ──────────────────────────────────────────────────────── if (profile.user_ids === 'all') { if (S._allUsers.length) { S._allUsers.forEach(u => { u.selected = true; }); renderAccountList(); } else { // Users not loaded yet — defer until loadUsers() resolves window._pendingProfileAllUsers = true; } } else if (Array.isArray(profile.user_ids) && profile.user_ids.length) { window._pendingProfileUserIds = profile.user_ids.map(u => u.id || u); _applyPendingProfileUsers(); } else if (Array.isArray(profile.user_ids) && profile.user_ids.length === 0) { // Explicitly empty list — deselect everyone so previous sidebar state doesn't persist S._allUsers.forEach(u => { u.selected = false; }); if (S._allUsers.length) renderAccountList(); } log(t('m365_profile_applied', 'Profile loaded') + ': ' + profile.name); } function _applyPendingProfileUsers() { const ids = window._pendingProfileUserIds; if (!ids || !ids.length || !S._allUsers.length) return; // Select only the users listed in the profile S._allUsers.forEach(u => { u.selected = ids.includes(u.id); }); renderAccountList(); window._pendingProfileUserIds = null; } async function saveCurrentAsProfile() { const name = prompt(t('m365_profile_save_prompt', 'Profile name:'), S._activeProfileId ? (S._profiles.find(p => p.id === S._activeProfileId) || {}).name || '' : ''); if (!name) return; const { sources, fileSources, googleSources, allSources, user_ids, options } = buildScanPayload(); const existing = S._profiles.find(p => p.name.toLowerCase() === name.toLowerCase()); const profile = { id: existing?.id || '', name, description: existing?.description || '', sources: allSources, google_sources: googleSources, user_ids, options, retention_years: parseInt(document.getElementById('optRetentionYears')?.value) || null, fiscal_year_end: document.getElementById('optFiscalYearEnd')?.value || '', email_to: '', file_sources: fileSources, }; try { const r = await fetch('/api/profiles/save', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(profile) }); const d = await r.json(); if (d.error) { alert(d.error); return; } await loadProfiles(); // Select the newly saved profile const sel = document.getElementById('profileSelect'); if (sel) { sel.value = d.profile.id; S._activeProfileId = d.profile.id; _setProfileClearBtn(true); } log(t('m365_profile_saved', 'Profile saved') + ': ' + name); } catch(e) { alert('Save failed: ' + e.message); } } // ── Profile management modal (#15d) ────────────────────────────────────────── function openProfileMgmtModal() { try { _renderProfileMgmt(); } catch(e) { console.error('[profiles] _renderProfileMgmt threw:', e); } document.getElementById('pmgmtBackdrop').classList.add('open'); // Auto-open editor for the first profile if (S._profiles.length > 0) { try { _pmgmtOpenEditor(S._profiles[0].id); } catch(e) { console.error('[profiles] _pmgmtOpenEditor threw:', e); } } } function closeProfileMgmt() { document.getElementById('pmgmtBackdrop').classList.remove('open'); } function _sourceLabel(id) { const known = {email:'Outlook', onedrive:'OneDrive', sharepoint:'SharePoint', teams:'Teams', gmail:'Gmail', gdrive:'Google Drive'}; if (known[id]) return known[id]; const fs = S._fileSources.find(s => s.id === id); return fs ? (fs.label || fs.path || id) : id; } function _renderProfileMgmt() { const list = document.getElementById('pmgmtList'); if (!list) return; const saved = S._profiles.filter(p => p.name !== 'Default' || S._profiles.length === 1); if (!saved.length) { list.innerHTML = `