release: v2.2.57 email+sag tab stability
This commit is contained in:
parent
eb5e14e2a1
commit
9a3ada380f
18
RELEASE_NOTES_v2.2.57.md
Normal file
18
RELEASE_NOTES_v2.2.57.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Release Notes v2.2.57
|
||||||
|
|
||||||
|
Dato: 2026-03-18
|
||||||
|
|
||||||
|
## Fokus
|
||||||
|
Stabilisering af UI i Email- og SAG-modulerne.
|
||||||
|
|
||||||
|
## Aendringer
|
||||||
|
- Email-visning: yderligere hardening af HTML-tabeller i mail-body, inklusive normalisering af inline styles for at undgaa layout break.
|
||||||
|
- Email-visning: forbedret overflow-haandtering for bredt indhold (tabeller, celler og media).
|
||||||
|
- SAG detaljeside: forbedret tab-loading, saa data hentes ved faneskift for Varekob & Salg, Abonnement og Paamindelser.
|
||||||
|
- SAG detaljeside: robust fallback for reminder user-id via `/api/v1/auth/me`.
|
||||||
|
- SAG detaljeside: rettet API-kald for reminders og kalender til stabil case-id reference.
|
||||||
|
|
||||||
|
## Berorte filer
|
||||||
|
- app/emails/frontend/emails.html
|
||||||
|
- app/modules/sag/templates/detail.html
|
||||||
|
- RELEASE_NOTES_v2.2.57.md
|
||||||
@ -323,7 +323,38 @@
|
|||||||
.email-html-body table {
|
.email-html-body table {
|
||||||
display: block;
|
display: block;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
width: 100%;
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
table-layout: auto !important;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-html-body tbody,
|
||||||
|
.email-html-body thead,
|
||||||
|
.email-html-body tfoot,
|
||||||
|
.email-html-body tr,
|
||||||
|
.email-html-body td,
|
||||||
|
.email-html-body th {
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-html-body td,
|
||||||
|
.email-html-body th {
|
||||||
|
white-space: normal !important;
|
||||||
|
word-break: break-word !important;
|
||||||
|
overflow-wrap: anywhere !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-html-body img,
|
||||||
|
.email-html-body video,
|
||||||
|
.email-html-body iframe {
|
||||||
|
max-width: 100% !important;
|
||||||
|
height: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-html-body [style*="position:fixed"],
|
||||||
|
.email-html-body [style*="position: fixed"] {
|
||||||
|
position: static !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.email-body iframe {
|
.email-body iframe {
|
||||||
@ -1886,10 +1917,40 @@ function renderEmailDetail(email) {
|
|||||||
// If HTML, inject it as innerHTML after rendering
|
// If HTML, inject it as innerHTML after rendering
|
||||||
if (email.body_html) {
|
if (email.body_html) {
|
||||||
const htmlDiv = pane.querySelector('.email-html-body');
|
const htmlDiv = pane.querySelector('.email-html-body');
|
||||||
if (htmlDiv) htmlDiv.innerHTML = email.body_html;
|
if (htmlDiv) {
|
||||||
|
htmlDiv.innerHTML = email.body_html;
|
||||||
|
normalizeEmailHtmlLayout(htmlDiv);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeEmailHtmlLayout(container) {
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const tables = container.querySelectorAll('table');
|
||||||
|
tables.forEach((table) => {
|
||||||
|
table.style.maxWidth = '100%';
|
||||||
|
table.style.width = '100%';
|
||||||
|
table.style.tableLayout = 'auto';
|
||||||
|
table.removeAttribute('width');
|
||||||
|
});
|
||||||
|
|
||||||
|
const cells = container.querySelectorAll('td, th');
|
||||||
|
cells.forEach((cell) => {
|
||||||
|
cell.style.whiteSpace = 'normal';
|
||||||
|
cell.style.wordBreak = 'break-word';
|
||||||
|
cell.style.overflowWrap = 'anywhere';
|
||||||
|
});
|
||||||
|
|
||||||
|
const images = container.querySelectorAll('img, iframe, video');
|
||||||
|
images.forEach((el) => {
|
||||||
|
el.style.maxWidth = '100%';
|
||||||
|
if (el.tagName === 'IMG' || el.tagName === 'VIDEO') {
|
||||||
|
el.style.height = 'auto';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function renderEmailAnalysis(email) {
|
function renderEmailAnalysis(email) {
|
||||||
const aiAnalysisTab = document.getElementById('aiAnalysisTab');
|
const aiAnalysisTab = document.getElementById('aiAnalysisTab');
|
||||||
if (!aiAnalysisTab) {
|
if (!aiAnalysisTab) {
|
||||||
|
|||||||
@ -2286,6 +2286,27 @@
|
|||||||
todoForm.addEventListener('submit', createTodoStep);
|
todoForm.addEventListener('submit', createTodoStep);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const caseTabs = document.getElementById('caseTabs');
|
||||||
|
if (caseTabs) {
|
||||||
|
caseTabs.addEventListener('shown.bs.tab', async (event) => {
|
||||||
|
const targetSelector = event?.target?.getAttribute('data-bs-target') || '';
|
||||||
|
const tabId = targetSelector.startsWith('#') ? targetSelector.slice(1) : targetSelector;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (tabId === 'sales' && typeof loadVarekobSalg === 'function') {
|
||||||
|
await loadVarekobSalg();
|
||||||
|
} else if (tabId === 'subscription' && typeof loadSubscriptionForCase === 'function') {
|
||||||
|
await loadSubscriptionForCase();
|
||||||
|
} else if (tabId === 'reminders') {
|
||||||
|
if (typeof loadReminders === 'function') await loadReminders();
|
||||||
|
if (typeof loadCaseCalendar === 'function') await loadCaseCalendar();
|
||||||
|
}
|
||||||
|
} catch (tabLoadError) {
|
||||||
|
console.error('Tab data reload failed:', tabLoadError);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Focus on title when create modal opens
|
// Focus on title when create modal opens
|
||||||
const createModalEl = document.getElementById('createRelatedCaseModal');
|
const createModalEl = document.getElementById('createRelatedCaseModal');
|
||||||
if (createModalEl) {
|
if (createModalEl) {
|
||||||
@ -4570,6 +4591,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
let reminderUserId = null;
|
let reminderUserId = null;
|
||||||
|
const remindersCaseId = {{ case.id }};
|
||||||
|
|
||||||
function getReminderUserId() {
|
function getReminderUserId() {
|
||||||
const token = localStorage.getItem('access_token') || sessionStorage.getItem('access_token');
|
const token = localStorage.getItem('access_token') || sessionStorage.getItem('access_token');
|
||||||
@ -4586,6 +4608,20 @@
|
|||||||
return null;
|
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) {
|
function formatReminderDate(value) {
|
||||||
if (!value) return '-';
|
if (!value) return '-';
|
||||||
const date = new Date(value);
|
const date = new Date(value);
|
||||||
@ -4637,7 +4673,7 @@
|
|||||||
async function loadReminders() {
|
async function loadReminders() {
|
||||||
const list = document.getElementById('remindersList');
|
const list = document.getElementById('remindersList');
|
||||||
if (!list) return;
|
if (!list) return;
|
||||||
reminderUserId = getReminderUserId();
|
reminderUserId = await ensureReminderUserId();
|
||||||
|
|
||||||
if (!reminderUserId) {
|
if (!reminderUserId) {
|
||||||
list.innerHTML = '<div class="p-4 text-center text-muted">Kunne ikke finde bruger-id.</div>';
|
list.innerHTML = '<div class="p-4 text-center text-muted">Kunne ikke finde bruger-id.</div>';
|
||||||
@ -4648,7 +4684,7 @@
|
|||||||
list.innerHTML = '<div class="p-4 text-center text-muted"><span class="spinner-border spinner-border-sm"></span> Henter reminders...</div>';
|
list.innerHTML = '<div class="p-4 text-center text-muted"><span class="spinner-border spinner-border-sm"></span> Henter reminders...</div>';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/v1/sag/${caseIds}/reminders?user_id=${reminderUserId}`);
|
const res = await fetch(`/api/v1/sag/${remindersCaseId}/reminders?user_id=${reminderUserId}`);
|
||||||
if (!res.ok) throw new Error('Kunne ikke hente reminders');
|
if (!res.ok) throw new Error('Kunne ikke hente reminders');
|
||||||
const reminders = await res.json();
|
const reminders = await res.json();
|
||||||
renderReminders(reminders);
|
renderReminders(reminders);
|
||||||
@ -4722,7 +4758,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function saveReminder() {
|
async function saveReminder() {
|
||||||
reminderUserId = getReminderUserId();
|
reminderUserId = await ensureReminderUserId();
|
||||||
if (!reminderUserId) {
|
if (!reminderUserId) {
|
||||||
alert('Mangler bruger-id. Log ind igen.');
|
alert('Mangler bruger-id. Log ind igen.');
|
||||||
return;
|
return;
|
||||||
@ -4785,7 +4821,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/v1/sag/${caseIds}/reminders?user_id=${reminderUserId}`, {
|
const res = await fetch(`/api/v1/sag/${remindersCaseId}/reminders?user_id=${reminderUserId}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(payload)
|
body: JSON.stringify(payload)
|
||||||
@ -4846,7 +4882,7 @@
|
|||||||
childrenList.innerHTML = '<div class="text-muted small">Indlæser børnesager...</div>';
|
childrenList.innerHTML = '<div class="text-muted small">Indlæser børnesager...</div>';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/v1/sag/${caseIds}/calendar-events?include_children=true`);
|
const res = await fetch(`/api/v1/sag/${remindersCaseId}/calendar-events?include_children=true`);
|
||||||
if (!res.ok) throw new Error('Kunne ikke hente kalenderaftaler');
|
if (!res.ok) throw new Error('Kunne ikke hente kalenderaftaler');
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user