bmc_hub/script_5.js
Christian bc504b9257 feat: Add subscription management functionality and AnyDesk API integration
- Implemented subscription creation, updating, and rendering in script_9.js.
- Added functions for handling subscription line items, product selection, and total calculations.
- Integrated AnyDesk API for session management in test_anydesk.py.
- Created REST client test requests for API endpoints in api.http.
- Developed a script to check ESET machine status and save details in tmp_check_eset_machine.py.
2026-03-30 07:50:15 +02:00

344 lines
15 KiB
JavaScript

let reminderUserId = null;
const remindersCaseId = {{ case.id }};
function getReminderUserId() {
const token = localStorage.getItem('access_token') || sessionStorage.getItem('access_token');
if (token) {
try {
const payload = JSON.parse(atob(token.split('.')[1]));
return payload.sub || payload.user_id;
} catch (e) {
console.warn('Could not decode token for reminder user_id');
}
}
const metaTag = document.querySelector('meta[name="user-id"]');
if (metaTag) return metaTag.getAttribute('content');
return null;
}
async function ensureReminderUserId() {
const localId = getReminderUserId();
if (localId) return localId;
try {
const res = await fetch('/api/v1/auth/me', { credentials: 'include' });
if (!res.ok) return null;
const me = await res.json();
return me?.id || me?.user_id || null;
} catch (err) {
return null;
}
}
function formatReminderDate(value) {
if (!value) return '-';
const date = new Date(value);
if (Number.isNaN(date.getTime())) return '-';
return date.toLocaleString('da-DK', { hour12: false });
}
function updateReminderTriggerFields() {
const triggerType = document.getElementById('rem_trigger_type')?.value;
const timeWrap = document.getElementById('rem_trigger_time_wrap');
const statusWrap = document.getElementById('rem_trigger_status_wrap');
if (timeWrap && statusWrap) {
if (triggerType === 'status_change') {
timeWrap.classList.add('d-none');
statusWrap.classList.remove('d-none');
} else {
timeWrap.classList.remove('d-none');
statusWrap.classList.add('d-none');
}
}
}
function updateReminderRecurrenceFields() {
const recurrenceType = document.getElementById('rem_recurrence_type')?.value;
const dowWrap = document.getElementById('rem_recurrence_dow_wrap');
const domWrap = document.getElementById('rem_recurrence_dom_wrap');
if (!dowWrap || !domWrap) return;
dowWrap.classList.toggle('d-none', recurrenceType !== 'weekly');
domWrap.classList.toggle('d-none', recurrenceType !== 'monthly');
}
function openCreateReminderModal(defaultEventType) {
reminderUserId = getReminderUserId();
const warning = document.getElementById('rem_user_warning');
if (warning) warning.classList.toggle('d-none', !!reminderUserId);
const form = document.getElementById('createReminderForm');
if (form) form.reset();
document.getElementById('rem_notify_frontend').checked = true;
document.getElementById('rem_priority').value = 'normal';
document.getElementById('rem_event_type').value = defaultEventType || 'reminder';
document.getElementById('rem_trigger_type').value = 'time_based';
document.getElementById('rem_recurrence_type').value = 'once';
updateReminderTriggerFields();
updateReminderRecurrenceFields();
new bootstrap.Modal(document.getElementById('createReminderModal')).show();
}
async function loadReminders() {
const list = document.getElementById('remindersList');
if (!list) return;
reminderUserId = await ensureReminderUserId();
if (!reminderUserId) {
list.innerHTML = '<div class="p-4 text-center text-muted">Kunne ikke finde bruger-id.</div>';
setModuleContentState('reminders', true);
return;
}
list.innerHTML = '<div class="p-4 text-center text-muted"><span class="spinner-border spinner-border-sm"></span> Henter reminders...</div>';
try {
const res = await fetch(`/api/v1/sag/${remindersCaseId}/reminders?user_id=${reminderUserId}`);
if (!res.ok) throw new Error('Kunne ikke hente reminders');
const reminders = await res.json();
renderReminders(reminders);
} catch (e) {
console.error(e);
list.innerHTML = '<div class="p-4 text-center text-danger">Fejl ved hentning af reminders</div>';
setModuleContentState('reminders', true);
}
}
function renderReminders(reminders) {
const list = document.getElementById('remindersList');
if (!list) return;
if (!reminders || reminders.length === 0) {
list.innerHTML = '<div class="p-4 text-center text-muted">Ingen reminders endnu.</div>';
setModuleContentState('reminders', false);
return;
}
const triggerLabels = {
time_based: 'Tidspunkt',
status_change: 'Status ændring',
deadline_approaching: 'Deadline'
};
const eventTypeLabels = {
reminder: 'Reminder',
meeting: 'Moede',
technician_visit: 'Teknikerbesoeg',
obs: 'OBS',
deadline: 'Deadline'
};
const recurrenceLabels = {
once: 'Én gang',
daily: 'Dagligt',
weekly: 'Ugentligt',
monthly: 'Månedligt'
};
list.innerHTML = reminders.map(reminder => {
const nextCheck = formatReminderDate(reminder.next_check_at);
const createdAt = formatReminderDate(reminder.created_at);
const isActive = reminder.is_active;
const statusBadge = isActive
? '<span class="badge bg-success">Aktiv</span>'
: '<span class="badge bg-secondary">Inaktiv</span>';
return `
<div class="list-group-item">
<div class="d-flex justify-content-between align-items-start">
<div class="me-3">
<div class="fw-bold">${reminder.title}</div>
<div class="text-muted small">${reminder.message || '-'} </div>
<div class="small text-muted mt-1">
Type: ${eventTypeLabels[reminder.event_type] || reminder.event_type || 'Reminder'} · Trigger: ${triggerLabels[reminder.trigger_type] || reminder.trigger_type} · Gentagelse: ${recurrenceLabels[reminder.recurrence_type] || reminder.recurrence_type}
</div>
<div class="small text-muted">Næste: ${nextCheck} · Oprettet: ${createdAt}</div>
</div>
<div class="d-flex flex-column align-items-end gap-2">
${statusBadge}
<button class="btn btn-sm btn-outline-danger" onclick="deleteReminder(${reminder.id})">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
</div>
`;
}).join('');
setModuleContentState('reminders', true);
}
async function saveReminder() {
reminderUserId = await ensureReminderUserId();
if (!reminderUserId) {
alert('Mangler bruger-id. Log ind igen.');
return;
}
const title = document.getElementById('rem_title').value.trim();
const message = document.getElementById('rem_message').value.trim();
const priority = document.getElementById('rem_priority').value;
const eventType = document.getElementById('rem_event_type').value;
const triggerType = document.getElementById('rem_trigger_type').value;
const scheduledAtValue = document.getElementById('rem_scheduled_at').value;
const targetStatus = document.getElementById('rem_target_status').value;
const recurrenceType = document.getElementById('rem_recurrence_type').value;
const recurrenceDow = document.getElementById('rem_recurrence_dow').value;
const recurrenceDom = document.getElementById('rem_recurrence_dom').value;
const notifyFrontend = document.getElementById('rem_notify_frontend').checked;
const notifyEmail = document.getElementById('rem_notify_email').checked;
const notifyMattermost = document.getElementById('rem_notify_mattermost').checked;
const overridePrefs = document.getElementById('rem_override_prefs').checked;
if (!title) {
alert('Titel er påkrævet');
return;
}
let triggerConfig = {};
let scheduledAt = null;
if (triggerType === 'status_change') {
if (!targetStatus) {
alert('Vælg en status for statusændring');
return;
}
triggerConfig = { target_status: targetStatus };
} else {
if (!scheduledAtValue) {
alert('Vælg et tidspunkt');
return;
}
scheduledAt = new Date(scheduledAtValue).toISOString();
}
const payload = {
title,
message: message || null,
priority,
event_type: eventType,
trigger_type: triggerType,
trigger_config: triggerConfig,
recipient_user_ids: [Number(reminderUserId)],
recipient_emails: [],
notify_mattermost: notifyMattermost,
notify_email: notifyEmail,
notify_frontend: notifyFrontend,
override_user_preferences: overridePrefs,
recurrence_type: recurrenceType,
recurrence_day_of_week: recurrenceType === 'weekly' ? Number(recurrenceDow) : null,
recurrence_day_of_month: recurrenceType === 'monthly' ? Number(recurrenceDom) : null,
scheduled_at: scheduledAt
};
try {
const res = await fetch(`/api/v1/sag/${remindersCaseId}/reminders?user_id=${reminderUserId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.detail || 'Kunne ikke oprette reminder');
}
bootstrap.Modal.getInstance(document.getElementById('createReminderModal')).hide();
await loadReminders();
await loadCaseCalendar();
} catch (e) {
alert('Fejl: ' + e.message);
}
}
async function deleteReminder(reminderId) {
if (!confirm('Vil du slette denne reminder?')) return;
try {
const res = await fetch(`/api/v1/sag/reminders/${reminderId}`, { method: 'DELETE' });
if (!res.ok) throw new Error('Kunne ikke slette reminder');
await loadReminders();
await loadCaseCalendar();
} catch (e) {
alert('Fejl: ' + e.message);
}
}
function formatCalendarEvent(event) {
const dateLabel = formatReminderDate(event.start);
const typeLabelMap = {
reminder: 'Reminder',
meeting: 'Moede',
technician_visit: 'Teknikerbesoeg',
obs: 'OBS',
deadline: 'Deadline',
deferred: 'Deferred'
};
const typeLabel = typeLabelMap[event.event_kind] || event.event_kind || 'Reminder';
return `
<a href="${event.url}" class="list-group-item list-group-item-action">
<div class="d-flex justify-content-between">
<div>
<div class="fw-semibold">${event.title || 'Aftale'}</div>
<div class="text-muted small">${typeLabel} · ${dateLabel}</div>
</div>
</div>
</a>
`;
}
async function loadCaseCalendar() {
const currentList = document.getElementById('caseCalendarCurrent');
const childrenList = document.getElementById('caseCalendarChildren');
if (!currentList || !childrenList) return;
currentList.innerHTML = '<div class="text-muted small">Indlæser aftaler...</div>';
childrenList.innerHTML = '<div class="text-muted small">Indlæser børnesager...</div>';
try {
const res = await fetch(`/api/v1/sag/${remindersCaseId}/calendar-events?include_children=true`);
if (!res.ok) throw new Error('Kunne ikke hente kalenderaftaler');
const data = await res.json();
const currentEvents = data.current || [];
const childGroups = data.children || [];
const childCount = childGroups.reduce((sum, child) => sum + (child.events || []).length, 0);
const hasAnyEvents = currentEvents.length > 0 || childCount > 0;
if (!currentEvents.length) {
currentList.innerHTML = '<div class="text-muted small">Ingen aftaler for denne sag.</div>';
} else {
currentList.innerHTML = currentEvents
.map(formatCalendarEvent)
.join('');
}
if (!childGroups.length) {
childrenList.innerHTML = '<div class="text-muted small">Ingen børnesager.</div>';
} else {
childrenList.innerHTML = childGroups.map(child => {
const eventsHtml = (child.events || []).length
? child.events.map(formatCalendarEvent).join('')
: '<div class="text-muted small">Ingen aftaler.</div>';
return `
<div class="mb-3">
<div class="fw-semibold mb-1">${child.case_title}</div>
<div class="list-group">
${eventsHtml}
</div>
</div>
`;
}).join('');
}
setModuleContentState('calendar', hasAnyEvents);
} catch (e) {
console.error(e);
currentList.innerHTML = '<div class="text-danger small">Fejl ved hentning af aftaler.</div>';
childrenList.innerHTML = '';
setModuleContentState('calendar', true);
}
}
document.addEventListener('DOMContentLoaded', function() {
updateReminderTriggerFields();
updateReminderRecurrenceFields();
loadReminders();
loadCaseCalendar();
});