235 lines
11 KiB
JavaScript
235 lines
11 KiB
JavaScript
|
|
|
||
|
|
function showCreateSolutionModal() {
|
||
|
|
const addTimeCheckbox = document.getElementById('sol_add_time');
|
||
|
|
const timeFields = document.getElementById('sol_time_fields');
|
||
|
|
if (addTimeCheckbox && timeFields) {
|
||
|
|
addTimeCheckbox.checked = false;
|
||
|
|
timeFields.classList.add('d-none');
|
||
|
|
}
|
||
|
|
const timeDate = document.getElementById('sol_time_date');
|
||
|
|
if (timeDate) timeDate.valueAsDate = new Date();
|
||
|
|
const timeHours = document.getElementById('sol_time_hours');
|
||
|
|
const timeMinutes = document.getElementById('sol_time_minutes');
|
||
|
|
const timeTotal = document.getElementById('sol_time_total');
|
||
|
|
if (timeHours) timeHours.value = '';
|
||
|
|
if (timeMinutes) timeMinutes.value = '';
|
||
|
|
if (timeTotal) timeTotal.textContent = 'Total: 0.00 timer';
|
||
|
|
const timeDesc = document.getElementById('sol_time_desc');
|
||
|
|
if (timeDesc) timeDesc.value = '';
|
||
|
|
const timeInternal = document.getElementById('sol_time_internal');
|
||
|
|
if (timeInternal) timeInternal.checked = false;
|
||
|
|
new bootstrap.Modal(document.getElementById('createSolutionModal')).show();
|
||
|
|
}
|
||
|
|
|
||
|
|
function updateSolutionTimeTotal() {
|
||
|
|
const h = parseInt(document.getElementById('sol_time_hours').value) || 0;
|
||
|
|
const m = parseInt(document.getElementById('sol_time_minutes').value) || 0;
|
||
|
|
const total = h + (m / 60);
|
||
|
|
const output = document.getElementById('sol_time_total');
|
||
|
|
if (output) output.textContent = `Total: ${total.toFixed(2)} timer`;
|
||
|
|
}
|
||
|
|
|
||
|
|
async function saveSolution() {
|
||
|
|
const data = {
|
||
|
|
sag_id: document.getElementById('sol_sag_id').value,
|
||
|
|
title: document.getElementById('sol_title').value,
|
||
|
|
solution_type: document.getElementById('sol_type').value,
|
||
|
|
result: document.getElementById('sol_result').value,
|
||
|
|
description: document.getElementById('sol_desc').value,
|
||
|
|
created_by_user_id: 1 // TODO: Get from auth
|
||
|
|
};
|
||
|
|
const addTime = document.getElementById('sol_add_time')?.checked;
|
||
|
|
const timeHours = parseInt(document.getElementById('sol_time_hours').value) || 0;
|
||
|
|
const timeMinutes = parseInt(document.getElementById('sol_time_minutes').value) || 0;
|
||
|
|
const timeTotal = timeHours + (timeMinutes / 60);
|
||
|
|
|
||
|
|
try {
|
||
|
|
const res = await fetch(`/api/v1/sag/${data.sag_id}/solution`, {
|
||
|
|
method: 'POST',
|
||
|
|
headers: {'Content-Type': 'application/json'},
|
||
|
|
body: JSON.stringify(data)
|
||
|
|
});
|
||
|
|
if (res.ok) {
|
||
|
|
if (addTime && timeTotal > 0) {
|
||
|
|
const solution = await res.json();
|
||
|
|
const timePayload = {
|
||
|
|
sag_id: data.sag_id,
|
||
|
|
solution_id: solution.id,
|
||
|
|
description: document.getElementById('sol_time_desc').value || data.title,
|
||
|
|
original_hours: timeTotal,
|
||
|
|
worked_date: document.getElementById('sol_time_date').value || null,
|
||
|
|
is_internal: document.getElementById('sol_time_internal').checked,
|
||
|
|
work_type: 'support'
|
||
|
|
};
|
||
|
|
const timeRes = await fetch('/api/v1/timetracking/entries/internal', {
|
||
|
|
method: 'POST',
|
||
|
|
headers: {'Content-Type': 'application/json'},
|
||
|
|
body: JSON.stringify(timePayload)
|
||
|
|
});
|
||
|
|
if (!timeRes.ok) {
|
||
|
|
alert('Løsning oprettet, men tid kunne ikke registreres');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
window.location.reload();
|
||
|
|
} else {
|
||
|
|
alert('Fejl ved oprettelse af løsning');
|
||
|
|
}
|
||
|
|
} catch(e) { console.error(e); alert('Fejl'); }
|
||
|
|
}
|
||
|
|
|
||
|
|
function showAddTimeModal() {
|
||
|
|
// Set date to today
|
||
|
|
document.getElementById('time_date').valueAsDate = new Date();
|
||
|
|
|
||
|
|
// Reset fields
|
||
|
|
if(document.getElementById('time_total_minutes')) {
|
||
|
|
document.getElementById('time_total_minutes').value = '';
|
||
|
|
document.getElementById('time_start_input').value = '';
|
||
|
|
document.getElementById('time_end_input').value = '';
|
||
|
|
}
|
||
|
|
document.getElementById('time_desc').value = '';
|
||
|
|
if(document.getElementById('time_internal')) document.getElementById('time_internal').checked = false;
|
||
|
|
if(document.getElementById('time_billing_method')) document.getElementById('time_billing_method').value = 'invoice';
|
||
|
|
if(document.getElementById('time_work_type')) document.getElementById('time_work_type').value = 'support';
|
||
|
|
|
||
|
|
new bootstrap.Modal(document.getElementById('createTimeModal')).show();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Auto-calculate total hours
|
||
|
|
/* removed updateTimeTotal */
|
||
|
|
|
||
|
|
// Add listeners safely
|
||
|
|
document.addEventListener('DOMContentLoaded', () => {
|
||
|
|
const hInput = document.getElementById('time_hours_input');
|
||
|
|
const mInput = document.getElementById('time_minutes_input');
|
||
|
|
if(hInput) hInput.addEventListener('input', updateTimeTotal);
|
||
|
|
if(mInput) mInput.addEventListener('input', updateTimeTotal);
|
||
|
|
const solAddTime = document.getElementById('sol_add_time');
|
||
|
|
const solFields = document.getElementById('sol_time_fields');
|
||
|
|
if (solAddTime && solFields) {
|
||
|
|
solAddTime.addEventListener('change', () => {
|
||
|
|
solFields.classList.toggle('d-none', !solAddTime.checked);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
const solHours = document.getElementById('sol_time_hours');
|
||
|
|
const solMinutes = document.getElementById('sol_time_minutes');
|
||
|
|
if (solHours) solHours.addEventListener('input', updateSolutionTimeTotal);
|
||
|
|
if (solMinutes) solMinutes.addEventListener('input', updateSolutionTimeTotal);
|
||
|
|
});
|
||
|
|
|
||
|
|
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'));
|
||
|
|
}
|
||
|
|
|
||
|
|
document.addEventListener('DOMContentLoaded', bindTimeModalCalculations);
|
||
|
|
|
||
|
|
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;
|
||
|
|
const dateVal = document.getElementById('time_date').value;
|
||
|
|
// extract optional start/end limits
|
||
|
|
const tStart = document.getElementById('time_start_input')?.value;
|
||
|
|
const tEnd = document.getElementById('time_end_input')?.value;
|
||
|
|
|
||
|
|
let startObj = null;
|
||
|
|
let endObj = null;
|
||
|
|
if (dateVal && tStart) {
|
||
|
|
try {
|
||
|
|
const l = new Date(`${dateVal}T${tStart}:00`);
|
||
|
|
startObj = l.toISOString();
|
||
|
|
} catch(e){}
|
||
|
|
}
|
||
|
|
if (dateVal && tEnd) {
|
||
|
|
try {
|
||
|
|
const l = new Date(`${dateVal}T${tEnd}:00`);
|
||
|
|
if (startObj && new Date(startObj) > l) {
|
||
|
|
l.setDate(l.getDate() + 1);
|
||
|
|
}
|
||
|
|
endObj = l.toISOString();
|
||
|
|
} catch(e){}
|
||
|
|
}
|
||
|
|
|
||
|
|
const sagId = document.getElementById('time_sag_id').value;
|
||
|
|
const payload = {
|
||
|
|
sag_id: parseInt(sagId),
|
||
|
|
// Note: saveTime modal expects 'timer' as totalHours currently, let's keep compatibility:
|
||
|
|
timer: totalHours,
|
||
|
|
faktisk_tid_min: minVal,
|
||
|
|
worked_date: dateVal,
|
||
|
|
start_tid: startObj,
|
||
|
|
slut_tid: endObj,
|
||
|
|
description: document.getElementById('time_desc').value,
|
||
|
|
work_type: document.getElementById('time_work_type').value,
|
||
|
|
billing_method: document.getElementById('time_billing_method').value
|
||
|
|
};
|
||
|
|
|
||
|
|
try {
|
||
|
|
const res = await fetch(`/api/v1/cases/${sagId}/time`, {
|
||
|
|
method: 'POST',
|
||
|
|
headers: {'Content-Type':'application/json'},
|
||
|
|
body: JSON.stringify(payload)
|
||
|
|
});
|
||
|
|
if(res.ok) {
|
||
|
|
window.location.reload();
|
||
|
|
} else {
|
||
|
|
alert("Fejl ved registrering af tid");
|
||
|
|
}
|
||
|
|
} catch(err) {
|
||
|
|
console.error(err);
|
||
|
|
alert("Forbindelsesfejl");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|