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 || [];
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;
}
// ── 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') {
S._allUsers.forEach(u => { u.selected = true; });
if (S._allUsers.length) renderAccountList();
} 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 = `
${t('m365_profile_no_profiles','No saved profiles yet. Use 💾 to save the current sidebar settings as a profile.')}
`;
return;
}
list.innerHTML = '';
for (const p of S._profiles) {
const sources = (p.sources || []).map(_sourceLabel).join(', ') || '—';
const lastRun = p.last_run ? p.last_run.slice(0,16).replace('T',' ') : t('m365_profile_never','never');
const isActive = p.id === S._activeProfileId;
const row = document.createElement('div');
row.className = 'pmgmt-row';
row.dataset.id = p.id;
row.onclick = function() { _pmgmtOpenEditor(p.id); };
row.innerHTML = `
${_esc(p.name)}${isActive ? ' ● activ' : ''}
${_esc(sources)}
${p.description ? `${_esc(p.description)}
` : ''}
${t('m365_profile_last_run','Last run')}: ${lastRun}
`;
list.appendChild(row);
}
}
function _esc(s) {
return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"');
}
function _pmgmtUse(id) {
const profile = S._profiles.find(p => p.id === id);
if (!profile) return;
S._activeProfileId = id;
_setProfileClearBtn(true);
_applyProfile(profile);
// Sync the topbar dropdown
const sel = document.getElementById('profileSelect');
if (sel) sel.value = id;
closeProfileMgmt();
}
function _pmgmtOpenEditor(id) {
const profile = S._profiles.find(p => p.id === id);
if (!profile) return;
_openEditorForProfile(profile);
}
function _openEditorForProfile(profile) {
const id = profile.id || '';
window._pmgmtEditId = id;
_pmgmtRoleActive = '';
// Highlight active row
document.querySelectorAll('.pmgmt-row').forEach(r => r.classList.toggle('active', id && r.dataset.id === id));
document.getElementById('pmgmtEditorTitle').textContent = profile.name;
const body = document.getElementById('pmgmtEditorBody');
const allSources = profile.sources || [];
const opts = profile.options || {};
const srcCheck = (id) => allSources.includes(id) ? 'checked' : '';
// Build account list from S._allUsers
const savedIds = new Set((profile.user_ids || []).map(u => u.id || u));
// If no saved IDs match current users, treat as all-selected (new profile or users changed)
const anyMatch = savedIds.size > 0 && S._allUsers.some(u => savedIds.has(u.id));
const accountRows = S._allUsers.map(u => {
// Only check if the user was explicitly saved — default to unchecked like the main window
const checked = anyMatch && savedIds.has(u.id) ? 'checked' : '';
const platBadge = u.platform === 'both' ? 'M365+GWS'
: (u.platform || 'm365') === 'google' ? 'GWS'
: 'M365';
const roleBadge = u.userRole === 'student' ? t('role_student','Elev') : u.userRole === 'staff' ? t('role_staff','Ansat') : t('role_other','Anden');
const roleOverrideStyle = u.roleOverride ? 'color:var(--color-text-info);outline:1px solid var(--color-border-info);' : '';
return ``;
}).join('');
body.innerHTML = `
Konti
${accountRows}
Indstillinger
${t('m365_opt_email_body','Scan e-mailindhold')}
${t('m365_opt_attachments','Scan vedhæftede filer')}
${t('m365_opt_max_attach','Maks. vedhæftet filstørrelse (MB)')}
${t('m365_opt_max_emails','Maks. e-mails pr. bruger')}
${t('m365_opt_delta','Delta-scanning')}
${t('m365_opt_scan_photos','Søg efter ansigter i billeder')}
${t('m365_opt_skip_gps','Ignorer GPS i billeder')}
${t('m365_opt_min_cpr','Min. CPR-antal pr. fil')}
${t('m365_opt_retention','Opbevaringspolitik')}
`;;
document.getElementById('pmgmtEditorPlaceholder')?.remove();
document.getElementById('pmgmtEditor').classList.add('open');
_renderEditorSources((profile.sources || []).concat(profile.google_sources || []).concat(profile.file_sources || []));
}
function _peSetDate(val) {
if (!val) return;
const ms = new Date() - new Date(val);
const days = Math.round(ms / 86400000);
const hidden = document.getElementById('peOptDays');
if (hidden) hidden.value = days;
// Clear selected year buttons since user picked a custom date
document.querySelectorAll('.peYearBtn').forEach(b => b.classList.remove('selected'));
}
function _peSetYear(years) {
const days = years === 0 ? 0 : years * 365;
const hidden = document.getElementById('peOptDays');
if (hidden) hidden.value = days;
document.querySelectorAll('.peYearBtn').forEach(function(btn) {
const y = parseInt(btn.dataset.years);
const active = (years === 0 && y === 0) || (years > 0 && y === years);
btn.classList.toggle('selected', active);
});
// Sync the date input
var dateInput = document.getElementById('peOptDate');
if (dateInput) {
if (days === 0) { dateInput.value = ''; }
else { var d = new Date(); d.setDate(d.getDate()-days); dateInput.value = d.toISOString().slice(0,10); }
}
}
function _renderEditorSources(checkedIds) {
const panel = document.getElementById('peSourcesPanel');
if (!panel) return;
let html = '';
_M365_SOURCES.forEach(function(s) {
const toggle = s.toggleId ? document.getElementById(s.toggleId) : null;
if (toggle && !toggle.checked) return;
const isChecked = checkedIds.includes(s.id);
html += '';
});
if (window._googleConnected) {
var gmailOn = !document.getElementById('smGoogleSrcGmail') || document.getElementById('smGoogleSrcGmail').checked;
var driveOn = !document.getElementById('smGoogleSrcDrive') || document.getElementById('smGoogleSrcDrive').checked;
if (gmailOn || driveOn) html += '
';
if (gmailOn) {
html += '';
}
if (driveOn) {
html += '';
}
}
if (S._fileSources.length > 0) {
html += '
';
S._fileSources.forEach(function(s) {
const isSmb = s.path && (s.path.startsWith('//') || s.path.startsWith('\\\\'));
html += '';
});
}
panel.innerHTML = html;
}
function _pmgmtNewProfile() {
// Create a blank profile shell and open the editor
const blank = {
id: '',
name: '',
description: '',
sources: [],
google_sources: [],
user_ids: [],
options: {},
file_sources: [],
};
// Temporarily add to S._profiles so the editor can find it
window._pmgmtNewDraft = blank;
_openEditorForProfile(blank);
}
function _pmgmtCloseEditor() {
document.getElementById('pmgmtEditor').classList.remove('open');
document.querySelectorAll('.pmgmt-row').forEach(r => r.classList.remove('active'));
window._pmgmtEditId = null;
closeProfileMgmt();
}
async function _pmgmtCycleRole(uid, event) {
event.stopPropagation();
if (typeof cycleUserRole !== 'function') return;
await cycleUserRole(uid);
// Refresh the badge inside the profile modal to reflect the new role
const u = S._allUsers.find(function(u){ return u.id === uid; });
if (!u) return;
const lbl = document.querySelector('#pmgmtAcctList label[data-uid="' + uid.replace(/"/g, '\\"') + '"]');
if (!lbl) return;
const badge = lbl.querySelector('.pmgmt-role-badge');
if (!badge) return;
const roleText = u.userRole === 'student' ? t('role_student','Elev')
: u.userRole === 'staff' ? t('role_staff','Ansat')
: t('role_other','Anden');
badge.textContent = roleText;
lbl.dataset.role = u.userRole || 'other';
badge.style.color = u.roleOverride ? 'var(--color-text-info)' : '';
badge.style.outline = u.roleOverride ? '1px solid var(--color-border-info)' : '';
}
function _pmgmtSelectAllAccounts(checked) {
document.querySelectorAll('#pmgmtAcctList label input[type=checkbox]').forEach(function(cb) {
if (cb.closest('label').style.display !== 'none') cb.checked = checked;
});
}
let _pmgmtRoleActive = '';
function _pmgmtRoleFilter(role) {
_pmgmtRoleActive = role;
// Update button styles
['peRoleAll','peRoleStaff','peRoleStudent'].forEach(function(id) {
const btn = document.getElementById(id);
if (!btn) return;
const isActive = (id === 'peRoleAll' && role === '') || (id === 'peRoleStaff' && role === 'staff') || (id === 'peRoleStudent' && role === 'student');
btn.style.background = isActive ? 'var(--accent)' : 'none';
btn.style.color = isActive ? '#fff' : 'var(--muted)';
btn.style.border = isActive ? '1px solid var(--accent)' : '1px solid var(--border)';
});
// Apply filter combined with any active text search
_pmgmtFilterAccounts(document.getElementById('pmgmtAcctSearch')?.value || '');
}
function _pmgmtAddManual() {
const email = prompt('E-mail adresse:');
if (!email || !email.trim()) return;
const list = document.getElementById('pmgmtAcctList');
if (!list) return;
const id = 'manual:' + email.trim().toLowerCase();
if (list.querySelector(`input[data-uid="${id}"]`)) return; // already exists
const lbl = document.createElement('label');
lbl.className = 'pmgmt-acct-row';
lbl.innerHTML = `${_esc(email.trim())}Manuel`;
list.appendChild(lbl);
}
function _pmgmtFilterAccounts(q) {
q = (q || '').toLowerCase();
document.querySelectorAll('#pmgmtAcctList label').forEach(function(lbl) {
var name = (lbl.querySelector('span') || {}).textContent || '';
var role = lbl.dataset.role || 'other';
var roleOk = !_pmgmtRoleActive || role === _pmgmtRoleActive;
var nameOk = !q || name.toLowerCase().includes(q);
lbl.style.display = (roleOk && nameOk) ? '' : 'none';
});
}
async function _pmgmtSaveFullEdit() {
const id = window._pmgmtEditId;
const profile = (id ? S._profiles.find(p => p.id === id) : null) || window._pmgmtNewDraft || {};
const name = document.getElementById('pmgmtEditName')?.value?.trim();
if (!name) { alert(t('m365_profile_name_required','Profile name is required.')); return; }
const peSources = Array.from(document.querySelectorAll('#peSourcesPanel input[type=checkbox]:checked'));
const m365Sources = peSources.filter(cb => cb.dataset.sourceType === 'm365').map(cb => cb.dataset.sourceId);
const googleSources = peSources.filter(cb => cb.dataset.sourceType === 'google').map(cb => cb.dataset.sourceId);
const fileSources = peSources.filter(cb => cb.dataset.sourceType === 'file').map(cb => cb.dataset.sourceId);
// Check whether the checkboxes were actually rendered in the editor DOM —
// NOT whether Google is connected or file sources are loaded. Those are async
// and may not have resolved when the editor first opened, leaving the panel
// without checkboxes even though the connection exists. Using the DOM as the
// source of truth avoids a race-condition that silently cleared google/file sources.
const googleRendered = !!document.querySelector('#peSourcesPanel input[data-source-type="google"]');
const fileRendered = !!document.querySelector('#peSourcesPanel input[data-source-type="file"]');
const effectiveGoogleSources = googleRendered ? googleSources : (profile.google_sources || []);
const effectiveFileSources = fileRendered ? fileSources : (profile.file_sources || []);
const allSources = m365Sources.concat(effectiveGoogleSources).concat(effectiveFileSources);
const user_ids = Array.from(document.querySelectorAll('#pmgmtAcctList input[type=checkbox]:checked'))
.map(cb => cb.dataset.uid)
.filter(Boolean);
const updated = {
...profile,
name,
description: document.getElementById('pmgmtEditDesc')?.value?.trim() || '',
sources: allSources,
google_sources: effectiveGoogleSources,
file_sources: effectiveFileSources,
user_ids,
options: {
...(profile.options || {}),
older_than_days: parseInt(document.getElementById('peOptDays')?.value) || 0,
email_body: document.getElementById('peOptBody')?.checked ?? true,
attachments: document.getElementById('peOptAtt')?.checked ?? true,
max_attach_mb: parseInt(document.getElementById('peOptMaxAttach')?.value) || 20,
max_emails: parseInt(document.getElementById('peOptMaxEmails')?.value) || 2000,
delta: document.getElementById('peOptDelta')?.checked ?? false,
scan_photos: document.getElementById('peOptPhotos')?.checked ?? false,
skip_gps_images: document.getElementById('peOptSkipGps')?.checked ?? false,
min_cpr_count: parseInt(document.getElementById('peOptMinCpr')?.value) || 1,
},
retention_years: document.getElementById('peOptRetention')?.checked ? (parseInt(document.getElementById('peOptRetYears')?.value) || 5) : null,
fiscal_year_end: document.getElementById('peOptRetention')?.checked ? (document.getElementById('peOptFiscalYearEnd')?.value || '') : '',
};
try {
const r = await fetch('/api/profiles/save', {
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify(updated)
});
const d = await r.json();
if (d.error) { alert(d.error); return; }
await loadProfiles();
window._pmgmtNewDraft = null;
log(t('m365_profile_saved','Profile saved') + ': ' + name);
// Show inline saved feedback without closing the modal
const footer = document.querySelector('#pmgmtEditor > div:last-child');
if (footer) {
const fb = document.createElement('span');
fb.textContent = '✓ ' + t('m365_profile_saved', 'Saved');
fb.style.cssText = 'font-size:11px;color:var(--success);margin-right:auto';
footer.prepend(fb);
setTimeout(function() { fb.remove(); }, 2000);
}
// Re-open the editor for the saved profile so it reflects the saved state
const saved = S._profiles.find(function(p) { return p.name === name; });
if (saved) { window._pmgmtEditId = saved.id; }
} catch(e) { alert('Save failed: ' + e.message); }
}
async function _pmgmtSaveEdit(id) {
const name = document.getElementById(`pmgmt-edit-name-${id}`)?.value?.trim();
const desc = document.getElementById(`pmgmt-edit-desc-${id}`)?.value?.trim();
if (!name) { alert(t('m365_profile_name_required','Profile name is required.')); return; }
const profile = S._profiles.find(p => p.id === id);
if (!profile) return;
const updated = { ...profile, name, description: desc || '' };
try {
const r = await fetch('/api/profiles/save', {
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify(updated)
});
const d = await r.json();
if (d.error) { alert(d.error); return; }
await loadProfiles();
_renderProfileMgmt();
log(t('m365_profile_saved','Profile saved') + ': ' + name);
} catch(e) { alert('Save failed: ' + e.message); }
}
async function _pmgmtDuplicate(id) {
const profile = S._profiles.find(p => p.id === id);
if (!profile) return;
const base = profile.name.replace(/ \(copy( \d+)?\)$/, '');
// Find a unique name
let n = 1, name = base + ' (copy)';
while (S._profiles.some(p => p.name === name)) { n++; name = `${base} (copy ${n})`; }
const copy = { ...profile, id: '', name, last_run: null, last_scan_id: null };
try {
const r = await fetch('/api/profiles/save', {
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify(copy)
});
const d = await r.json();
if (d.error) { alert(d.error); return; }
await loadProfiles();
_renderProfileMgmt();
log(t('m365_profile_duplicated','Profile duplicated') + ': ' + name);
} catch(e) { alert('Duplicate failed: ' + e.message); }
}
async function _pmgmtDelete(id, name) {
if (!confirm(t('m365_profile_delete_confirm','Delete profile') + ' "' + name + '"?')) return;
try {
const r = await fetch('/api/profiles/delete', {
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify({ id })
});
const d = await r.json();
if (d.error) { alert(d.error); return; }
if (S._activeProfileId === id) { S._activeProfileId = null; _setProfileClearBtn(false); }
await loadProfiles();
_renderProfileMgmt();
log(t('m365_profile_deleted','Profile deleted') + ': ' + name);
} catch(e) { alert('Delete failed: ' + e.message); }
}
// ── Window exports (HTML handlers + cross-module calls) ─────────────────────
window.loadProfiles = loadProfiles;
window._renderProfileSelect = _renderProfileSelect;
window._setProfileClearBtn = _setProfileClearBtn;
window.onProfileChange = onProfileChange;
window.clearActiveProfile = clearActiveProfile;
window._applyProfile = _applyProfile;
window._applyPendingProfileUsers = _applyPendingProfileUsers;
window.saveCurrentAsProfile = saveCurrentAsProfile;
window.openProfileMgmtModal = openProfileMgmtModal;
window.closeProfileMgmt = closeProfileMgmt;
window._sourceLabel = _sourceLabel;
window._renderProfileMgmt = _renderProfileMgmt;
window._esc = _esc;
window._pmgmtUse = _pmgmtUse;
window._pmgmtOpenEditor = _pmgmtOpenEditor;
window._openEditorForProfile = _openEditorForProfile;
window._peSetDate = _peSetDate;
window._peSetYear = _peSetYear;
window._renderEditorSources = _renderEditorSources;
window._pmgmtNewProfile = _pmgmtNewProfile;
window._pmgmtCloseEditor = _pmgmtCloseEditor;
window._pmgmtCycleRole = _pmgmtCycleRole;
window._pmgmtSelectAllAccounts = _pmgmtSelectAllAccounts;
window._pmgmtRoleFilter = _pmgmtRoleFilter;
window._pmgmtAddManual = _pmgmtAddManual;
window._pmgmtFilterAccounts = _pmgmtFilterAccounts;
window._pmgmtSaveFullEdit = _pmgmtSaveFullEdit;
window._pmgmtSaveEdit = _pmgmtSaveEdit;
window._pmgmtDuplicate = _pmgmtDuplicate;
window._pmgmtDelete = _pmgmtDelete;
window._pmgmtRoleActive = _pmgmtRoleActive;