172 lines
9.2 KiB
Python
172 lines
9.2 KiB
Python
|
|
with open('app/modules/sag/templates/detail.html', 'r', encoding='utf-8') as f:
|
||
|
|
text = f.read()
|
||
|
|
|
||
|
|
# Replace HTML for timeForm
|
||
|
|
html_start = text.find('<form id="timeForm">')
|
||
|
|
html_end = text.find('</form>', html_start) + 7
|
||
|
|
|
||
|
|
new_html = """<form id="timeForm">
|
||
|
|
<input type="hidden" id="time_sag_id" value="{{ case.id }}">
|
||
|
|
<div class="row g-3">
|
||
|
|
<div class="col-12 col-md-6">
|
||
|
|
<label class="form-label">Dato *</label>
|
||
|
|
<input type="date" class="form-control" id="time_date" required>
|
||
|
|
</div>
|
||
|
|
<div class="col-12 col-md-6">
|
||
|
|
<label class="form-label">Tid brugt *</label>
|
||
|
|
<div class="input-group">
|
||
|
|
<span class="input-group-text">Min.</span>
|
||
|
|
<input type="number" class="form-control" id="time_total_minutes" min="1" placeholder="45" step="1" required>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="col-6">
|
||
|
|
<label class="form-label">Starttid</label>
|
||
|
|
<input type="time" class="form-control" id="time_start_input">
|
||
|
|
</div>
|
||
|
|
<div class="col-6">
|
||
|
|
<label class="form-label">Sluttid</label>
|
||
|
|
<input type="time" class="form-control" id="time_end_input">
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="col-6">
|
||
|
|
<label class="form-label">Type</label>
|
||
|
|
<select class="form-select" id="time_work_type">
|
||
|
|
<option value="support" selected>Support</option>
|
||
|
|
<option value="troubleshooting">Fejlsøgning</option>
|
||
|
|
<option value="development">Udvikling</option>
|
||
|
|
<option value="on_site">Kørsel / On-site</option>
|
||
|
|
<option value="meeting">Møde</option>
|
||
|
|
<option value="other">Andet</option>
|
||
|
|
</select>
|
||
|
|
</div>
|
||
|
|
<div class="col-6">
|
||
|
|
<label class="form-label">Afregning</label>
|
||
|
|
<select class="form-select" id="time_billing_method">
|
||
|
|
<option value="invoice" selected>Faktura</option>
|
||
|
|
{% if prepaid_cards %}
|
||
|
|
<optgroup label="Klippekort">
|
||
|
|
{% for card in prepaid_cards %}
|
||
|
|
<option value="card_{{ card.id }}">💳 Klippekort #{{ card.card_number or card.id }} ({{ '%.2f' % card.remaining_hours }}t tilbage{% if card.expires_at %} • Udløber {{ card.expires_at }}{% endif %})</option>
|
||
|
|
{% endfor %}
|
||
|
|
</optgroup>
|
||
|
|
{% endif %}
|
||
|
|
{% if fixed_price_agreements %}
|
||
|
|
<optgroup label="Fastpris Aftaler">
|
||
|
|
{% for agr in fixed_price_agreements %}
|
||
|
|
<option value="fpa_{{ agr.id }}">📋 Fastpris #{{ agr.agreement_number }} ({{ '%.1f' % agr.remaining_hours_this_month }}t tilbage / {{ '%.0f' % agr.monthly_hours }}t/måned)</option>
|
||
|
|
{% endfor %}
|
||
|
|
</optgroup>
|
||
|
|
{% endif %}
|
||
|
|
<option value="internal">Internt / Ingen faktura</option>
|
||
|
|
<option value="warranty">Garanti / Reklamation</option>
|
||
|
|
</select>
|
||
|
|
</div>
|
||
|
|
<div class="col-12">
|
||
|
|
<label class="form-label">Beskrivelse</label>
|
||
|
|
<textarea class="form-control" id="time_desc" rows="3" placeholder="Hvad er der brugt tid på?"></textarea>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</form>"""
|
||
|
|
|
||
|
|
if html_start != -1 and html_end != -1:
|
||
|
|
text = text[:html_start] + new_html + text[html_end:]
|
||
|
|
print("Replaced timeForm HTML.")
|
||
|
|
|
||
|
|
# Modify reset logic in showAddTimeModal
|
||
|
|
reset_start = text.find('if(document.getElementById(\'time_hours_input\')) {')
|
||
|
|
reset_end = text.find('}', reset_start) + 1
|
||
|
|
if reset_start != -1:
|
||
|
|
new_reset = """if(document.getElementById('time_total_minutes')) {
|
||
|
|
document.getElementById('time_total_minutes').value = '';
|
||
|
|
document.getElementById('time_start_input').value = '';
|
||
|
|
document.getElementById('time_end_input').value = '';
|
||
|
|
}"""
|
||
|
|
text = text[:reset_start] + new_reset + text[reset_end:]
|
||
|
|
print("Replaced modal form reset.")
|
||
|
|
|
||
|
|
# Delete old updateTimeTotal function, add bindTimeModalCalculations
|
||
|
|
updateTotalStart = text.find('function updateTimeTotal() {')
|
||
|
|
updateTotalEnd = text.find('}', updateTotalStart) + 1
|
||
|
|
if updateTotalStart != -1:
|
||
|
|
new_updateTotal = """function bindTimeModalCalculations() {
|
||
|
|
const startIn = document.getElementById('time_start_input');
|
||
|
|
const endIn = document.getElementById('time_end_input');
|
||
|
|
const minIn = document.getElementById('time_total_minutes');
|
||
|
|
|
||
|
|
if (!startIn || !endIn || !minIn) return;
|
||
|
|
|
||
|
|
const parseTime = (val) => {
|
||
|
|
if (!val) return null;
|
||
|
|
const [h,m] = val.split(':').map(Number);
|
||
|
|
return (h * 60) + m;
|
||
|
|
};
|
||
|
|
|
||
|
|
const toTimeStr = (totalMins) => {
|
||
|
|
const h = Math.floor(totalMins / 60) % 24;
|
||
|
|
const m = totalMins % 60;
|
||
|
|
return `${h.toString().padStart(2,'0')}:${m.toString().padStart(2,'0')}`;
|
||
|
|
};
|
||
|
|
|
||
|
|
const recalculate = (trigger) => {
|
||
|
|
const s = parseTime(startIn.value);
|
||
|
|
const e = parseTime(endIn.value);
|
||
|
|
const dur = parseInt(minIn.value);
|
||
|
|
|
||
|
|
if (trigger === 'start' || trigger === 'end') {
|
||
|
|
if (s !== null && e !== null) {
|
||
|
|
let diff = e - s;
|
||
|
|
if (diff < 0) diff += 24*60;
|
||
|
|
minIn.value = diff;
|
||
|
|
} else if (s !== null && !isNaN(dur) && dur > 0 && !endIn.value) {
|
||
|
|
endIn.value = toTimeStr(s + dur);
|
||
|
|
} else if (e !== null && !isNaN(dur) && dur > 0 && !startIn.value) {
|
||
|
|
let base = e - dur;
|
||
|
|
while (base < 0) base += 24*60;
|
||
|
|
startIn.value = toTimeStr(base);
|
||
|
|
}
|
||
|
|
} else if (trigger === 'min') {
|
||
|
|
if (s !== null && !isNaN(dur) && dur > 0) {
|
||
|
|
endIn.value = toTimeStr(s + dur);
|
||
|
|
} else if (e !== null && !isNaN(dur) && dur > 0 && !startIn.value) {
|
||
|
|
let base = e - dur;
|
||
|
|
while(base < 0) base+=24*60;
|
||
|
|
startIn.value = toTimeStr(base);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
startIn.addEventListener('change', () => recalculate('start'));
|
||
|
|
endIn.addEventListener('change', () => recalculate('end'));
|
||
|
|
minIn.addEventListener('input', () => recalculate('min'));
|
||
|
|
}"""
|
||
|
|
text = text[:updateTotalStart] + new_updateTotal + text[updateTotalEnd:]
|
||
|
|
print("Replaced updateTimeTotal with bindTimeModalCalculations")
|
||
|
|
|
||
|
|
# Fix listeners initialization
|
||
|
|
dom_start = text.find('const hInput = document.getElementById(\'time_hours_input\');')
|
||
|
|
dom_end = text.find('if(mInput) mInput.addEventListener(\'input\', updateTimeTotal);', dom_start) + 63
|
||
|
|
if dom_start != -1:
|
||
|
|
text = text[:dom_start] + "bindTimeModalCalculations();" + text[dom_end:]
|
||
|
|
print("Fixed DOM listeners")
|
||
|
|
|
||
|
|
# Replace saveTime body part logic: calculate minutes explicitly from `time_total_minutes`
|
||
|
|
save_start = text.find('async function saveTime() {')
|
||
|
|
save_end = text.find('const isInternal = document.getElementById(\'time_internal\')?.checked || false;', save_start)
|
||
|
|
if save_start != -1:
|
||
|
|
new_save = """async function saveTime() {
|
||
|
|
const mInput = document.getElementById('time_total_minutes');
|
||
|
|
const minVal = parseInt(mInput ? mInput.value : 0);
|
||
|
|
if (!minVal || minVal <= 0) {
|
||
|
|
alert('Indtast en gyldig varighed (minutter).');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
const totalHours = minVal / 60;
|
||
|
|
"""
|
||
|
|
text = text[:save_start] + new_save + text[save_end:]
|
||
|
|
print("Updated saveTime first half.")
|
||
|
|
|
||
|
|
# Note: saveTime uses `POST /api/v1/cases/${sagId}/time` or similar, wait let me check the actual fetch path.
|
||
|
|
# Let's check `saveTime` first before committing blindly. I will just do the above first, then verify `saveTime`.
|
||
|
|
with open('app/modules/sag/templates/detail.html', 'w', encoding='utf-8') as f:
|
||
|
|
f.write(text)
|