bmc_hub/fix_timeline_clean.py

224 lines
9.1 KiB
Python
Raw Normal View History

import re
with open("app/modules/sag/templates/detail.html", "r", encoding="utf-8") as f:
text = f.read()
old_css_pattern = r"\.time-v1-track \{.*?\n \}"
new_css = """
.time-v1-global-timeline {
position: relative;
padding-left: 2rem;
margin-bottom: 2rem;
}
.time-v1-global-timeline::before {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: 0.75rem;
width: 2px;
background-color: var(--accent, #0f4c75);
opacity: 0.2;
}
.time-v1-date-node {
position: relative;
margin-bottom: 1.5rem;
}
.time-v1-date-badge {
display: inline-block;
background-color: var(--accent, #0f4c75);
color: #fff;
padding: 0.25rem 0.75rem;
border-radius: 1rem;
font-size: 0.85rem;
font-weight: 600;
margin-bottom: 1rem;
margin-left: -2.5rem;
position: relative;
z-index: 1;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.time-v1-item {
position: relative;
background: #fff;
border-radius: 0.5rem;
padding: 1rem;
margin-bottom: 0.75rem;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
border: 1px solid rgba(0,0,0,0.05);
transition: all 0.2s ease;
}
.time-v1-item::before {
content: '';
position: absolute;
top: 1.5rem;
left: -2rem;
width: 1rem;
height: 2px;
background-color: var(--accent, #0f4c75);
opacity: 0.2;
}
.time-v1-item:hover {
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
transform: translateY(-2px);
}
.time-v1-avatar {
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
background-color: color-mix(in srgb, var(--accent, #0f4c75) 10%, white);
color: var(--accent, #0f4c75);
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 0.9rem;
flex-shrink: 0;
border: 2px solid white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
"""
new_js = """function renderTimeV1Timeline(entries) {
const timeline = document.getElementById('timeV1Timeline');
if (!timeline) return;
if (!entries || entries.length === 0) {
timeline.innerHTML = '<div class="text-muted text-center p-4">Ingen tidsregistreringer endnu</div>';
return;
}
// Saml og sortér alle tidsregistreringer efter dato, nyeste først
const sortedEntries = [...entries].sort((a, b) => {
const dateA = new Date(a.worked_date || a.start_tid || 0);
const dateB = new Date(b.worked_date || b.start_tid || 0);
return dateB - dateA;
});
// Gruppér efter formatert dato
const groupedByDate = {};
sortedEntries.forEach((entry) => {
const rawDate = new Date(entry.worked_date || entry.start_tid || 0);
const dateKey = !isNaN(rawDate.getTime())
? rawDate.toLocaleDateString('da-DK', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' })
: 'Ukendt dato';
if (!groupedByDate[dateKey]) groupedByDate[dateKey] = [];
groupedByDate[dateKey].push(entry);
});
// Byg HTML for den overordnede tidslinje
let html = '<div class="time-v1-global-timeline">';
Object.entries(groupedByDate).forEach(([dateLabel, dateEntries]) => {
// Konverter det første bogstav i dato-strengen til stort
const formattedDateLab = dateLabel.charAt(0).toUpperCase() + dateLabel.slice(1);
html += `
<div class="time-v1-date-node">
<div class="time-v1-date-badge">
<i class="bi bi-calendar3 me-1"></i>${formattedDateLab}
</div>
`;
dateEntries.forEach(entry => {
const desc = escapeHtml(entry.beskrivelse || 'Ingen beskrivelse');
const userName = escapeHtml(entry.bruger_navn || 'Ukendt');
// Lav initialer til Avatar
const initials = userName.split(' ').map(n => n[0]).join('').slice(0, 2).toUpperCase() || '?';
// Formatér tid
let timeOutput = '0 t';
let isRunning = false;
let clockClass = "text-muted";
if (entry.kilde === 'live' && !entry.faktisk_tid_min && !entry.stop_tid) {
timeOutput = 'Kører...';
isRunning = true;
clockClass = "text-success fw-bold";
} else if (entry.is_running) {
timeOutput = 'Kører...';
isRunning = true;
clockClass = "text-success fw-bold";
} else if (entry.faktisk_tid_min !== null && entry.faktisk_tid_min !== undefined) {
const h = Math.floor(entry.faktisk_tid_min / 60);
const m = Math.floor(entry.faktisk_tid_min % 60);
timeOutput = `${h}t ${m}m`;
} else {
// Reservere for original_hours fallback
const origHours = parseFloat(entry.original_hours || 0);
const h = Math.floor(origHours);
const m = Math.round((origHours - h) * 60);
timeOutput = `${h}t ${m}m`;
}
// Tjek synlighed for kunden (intern markering)
const isInternal = entry.is_internal ? true : false;
const internalBadge = isInternal
? `<span class="badge bg-danger-subtle text-danger-emphasis border border-danger-subtle rounded-pill me-2" title="Skjult for kunde">
<i class="bi bi-eye-slash-fill me-1"></i>Intern
</span>`
: '';
html += `
<div class="time-v1-item d-flex gap-3 align-items-start">
<div class="time-v1-avatar" title="${userName}">
${initials}
</div>
<div class="flex-grow-1">
<div class="d-flex justify-content-between align-items-start">
<div>
<div class="fw-semibold text-dark">${userName}</div>
<div class="small text-muted mb-2">
<i class="bi bi-clock ${clockClass} me-1"></i>
<span class="${isRunning ? 'text-success fw-bold' : ''}">${timeOutput}</span>
${entry.entry_type ? ` &middot; <span class="badge bg-light text-secondary border">${escapeHtml(entry.entry_type)}</span>` : ''}
</div>
</div>
<div class="d-flex align-items-center">
${internalBadge}
<button class="btn btn-sm btn-link text-muted p-0" onclick="deleteTimeV1Entry(${entry.id})" title="Slet">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
<div class="text-dark bg-light rounded p-2 small border" style="white-space: pre-wrap;">${desc}</div>
</div>
</div>
`;
});
html += `</div>`; // Luk time-v1-date-node
});
html += '</div>'; // Luk time-v1-global-timeline
timeline.innerHTML = html;
}"""
old_js_pattern = r'function renderTimeV1Timeline\(entries\).*?\n }'
orig_text_len = len(text)
import sys
if re.search(old_css_pattern, text, re.DOTALL):
text = re.sub(old_css_pattern, new_css.strip(), text, flags=re.DOTALL)
else:
print("Could NOT find old CSS!")
if re.search(old_js_pattern, text, re.DOTALL):
text = re.sub(old_js_pattern, new_js.strip(), text, flags=re.DOTALL)
else:
print("Could NOT find old JS!")
with open("app/modules/sag/templates/detail.html", "w", encoding="utf-8") as f:
f.write(text)
print(f"Replacement complete! Original length {orig_text_len}, new length {len(text)}")