519 lines
22 KiB
JavaScript
519 lines
22 KiB
JavaScript
|
|
(function () {
|
||
|
|
'use strict';
|
||
|
|
|
||
|
|
const DEFAULT_ACTION_CONFIG = {
|
||
|
|
default_category: null,
|
||
|
|
allow_company_templates: true,
|
||
|
|
allow_global_templates: true
|
||
|
|
};
|
||
|
|
|
||
|
|
let modalElement = null;
|
||
|
|
let modalInstance = null;
|
||
|
|
let currentCaseId = null;
|
||
|
|
let currentAction = null;
|
||
|
|
let availableTemplates = [];
|
||
|
|
let currentPreview = null;
|
||
|
|
|
||
|
|
function notify(message, level) {
|
||
|
|
if (typeof window.showNotification === 'function') {
|
||
|
|
window.showNotification(message, level || 'info');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (level === 'error') {
|
||
|
|
alert(message);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function ensureModal() {
|
||
|
|
if (modalElement) return;
|
||
|
|
|
||
|
|
const html = `
|
||
|
|
<div class="modal fade" id="taskTemplateSelectorModal" tabindex="-1" aria-hidden="true">
|
||
|
|
<div class="modal-dialog modal-xl modal-dialog-centered">
|
||
|
|
<div class="modal-content">
|
||
|
|
<div class="modal-header">
|
||
|
|
<h5 class="modal-title"><i class="bi bi-file-earmark-check me-2"></i>Vaelg opgave-template</h5>
|
||
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
|
|
</div>
|
||
|
|
<div class="modal-body">
|
||
|
|
<div class="row g-3">
|
||
|
|
<div class="col-lg-4">
|
||
|
|
<label class="form-label">Template-kilde</label>
|
||
|
|
<select id="ttSource" class="form-select">
|
||
|
|
<option value="all" selected>Firma + faelles</option>
|
||
|
|
<option value="company">Kun firma</option>
|
||
|
|
<option value="global">Kun faelles</option>
|
||
|
|
<option value="internal">Kun intern</option>
|
||
|
|
</select>
|
||
|
|
</div>
|
||
|
|
<div class="col-lg-4">
|
||
|
|
<label class="form-label">Kategori</label>
|
||
|
|
<select id="ttCategory" class="form-select">
|
||
|
|
<option value="">Alle</option>
|
||
|
|
<option value="onboarding">Onboarding</option>
|
||
|
|
<option value="offboarding">Offboarding</option>
|
||
|
|
<option value="simkort">Mobil / SIM-kort</option>
|
||
|
|
<option value="hardwarebestilling">Hardwarebestilling</option>
|
||
|
|
<option value="brugerandring">Brugeraendring</option>
|
||
|
|
<option value="andet">Andet</option>
|
||
|
|
</select>
|
||
|
|
</div>
|
||
|
|
<div class="col-lg-4">
|
||
|
|
<label class="form-label">Startdato</label>
|
||
|
|
<input id="ttStartDate" type="date" class="form-control" />
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="col-lg-8">
|
||
|
|
<label class="form-label">Template</label>
|
||
|
|
<input id="ttTemplateSearch" class="form-control mb-2" placeholder="Soeg template..." />
|
||
|
|
<select id="ttTemplate" class="form-select" size="8"></select>
|
||
|
|
<div id="ttTemplateEmpty" class="small text-muted mt-2 d-none">Ingen templates matchede dit filter.</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="col-lg-4">
|
||
|
|
<label class="form-label">Oprettelsestype</label>
|
||
|
|
<select id="ttMode" class="form-select mb-3">
|
||
|
|
<option value="subcases">Opret som undersager</option>
|
||
|
|
<option value="tasks">Opret som tasks paa nuvaerende sag</option>
|
||
|
|
<option value="combined" selected>Kombineret</option>
|
||
|
|
</select>
|
||
|
|
|
||
|
|
<label class="form-label">Ansvarlig</label>
|
||
|
|
<select id="ttAssigneeMode" class="form-select mb-2">
|
||
|
|
<option value="template_default" selected>Brug standard fra template</option>
|
||
|
|
<option value="specific_user">Vaelg specifik medarbejder</option>
|
||
|
|
<option value="specific_role">Vaelg team/rolle</option>
|
||
|
|
</select>
|
||
|
|
<input id="ttAssigneeUserId" type="number" class="form-control mb-2 d-none" placeholder="Medarbejder ID" min="1" step="1" />
|
||
|
|
<input id="ttAssigneeRoleId" type="number" class="form-control d-none" placeholder="Rolle ID" min="1" step="1" />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="border-top mt-4 pt-3">
|
||
|
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||
|
|
<h6 class="mb-0">Preview</h6>
|
||
|
|
<button type="button" class="btn btn-sm btn-outline-primary" id="ttPreviewBtn">Opdater preview</button>
|
||
|
|
</div>
|
||
|
|
<div id="ttPreviewSummary" class="small text-muted mb-2">Ingen preview endnu.</div>
|
||
|
|
<div id="ttPreviewList" class="list-group" style="max-height: 280px; overflow:auto;"></div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="border-top mt-4 pt-3">
|
||
|
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||
|
|
<h6 class="mb-0">Seneste template-koersler paa sagen</h6>
|
||
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" id="ttRefreshRunsBtn">Opdater</button>
|
||
|
|
</div>
|
||
|
|
<div id="ttRunHistory" class="small text-muted">Ingen template-koersler endnu.</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="modal-footer">
|
||
|
|
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Annuller</button>
|
||
|
|
<button type="button" class="btn btn-primary" id="ttRunBtn" disabled>Opret</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>`;
|
||
|
|
|
||
|
|
document.body.insertAdjacentHTML('beforeend', html);
|
||
|
|
modalElement = document.getElementById('taskTemplateSelectorModal');
|
||
|
|
modalInstance = new bootstrap.Modal(modalElement);
|
||
|
|
|
||
|
|
document.getElementById('ttSource').addEventListener('change', refreshTemplates);
|
||
|
|
document.getElementById('ttCategory').addEventListener('change', refreshTemplates);
|
||
|
|
document.getElementById('ttTemplateSearch').addEventListener('input', renderTemplateList);
|
||
|
|
document.getElementById('ttTemplate').addEventListener('change', onTemplateSelected);
|
||
|
|
document.getElementById('ttAssigneeMode').addEventListener('change', onAssigneeModeChanged);
|
||
|
|
document.getElementById('ttPreviewBtn').addEventListener('click', runPreview);
|
||
|
|
document.getElementById('ttRunBtn').addEventListener('click', runTemplate);
|
||
|
|
document.getElementById('ttRefreshRunsBtn').addEventListener('click', loadRunHistory);
|
||
|
|
|
||
|
|
modalElement.addEventListener('shown.bs.modal', () => {
|
||
|
|
document.getElementById('ttTemplateSearch').focus();
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function todayIso() {
|
||
|
|
const now = new Date();
|
||
|
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||
|
|
const day = String(now.getDate()).padStart(2, '0');
|
||
|
|
return `${now.getFullYear()}-${month}-${day}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
async function fetchCase(caseId) {
|
||
|
|
const response = await fetch(`/api/v1/sag/${caseId}`, { credentials: 'include' });
|
||
|
|
if (!response.ok) {
|
||
|
|
throw new Error('Kunne ikke hente sag');
|
||
|
|
}
|
||
|
|
return response.json();
|
||
|
|
}
|
||
|
|
|
||
|
|
function getActionConfig() {
|
||
|
|
const cfg = (currentAction && currentAction.config) || {};
|
||
|
|
return { ...DEFAULT_ACTION_CONFIG, ...cfg };
|
||
|
|
}
|
||
|
|
|
||
|
|
async function refreshTemplates() {
|
||
|
|
const source = document.getElementById('ttSource').value;
|
||
|
|
const category = document.getElementById('ttCategory').value;
|
||
|
|
const params = new URLSearchParams();
|
||
|
|
|
||
|
|
if (source) params.set('source', source);
|
||
|
|
if (category) params.set('category', category);
|
||
|
|
|
||
|
|
try {
|
||
|
|
const caseRow = await fetchCase(currentCaseId);
|
||
|
|
if (caseRow && caseRow.customer_id) {
|
||
|
|
params.set('company_id', String(caseRow.customer_id));
|
||
|
|
}
|
||
|
|
|
||
|
|
const response = await fetch(`/api/v1/task-templates?${params.toString()}`, { credentials: 'include' });
|
||
|
|
if (!response.ok) {
|
||
|
|
const payload = await response.json().catch(() => ({}));
|
||
|
|
throw new Error(payload.detail || 'Kunne ikke hente templates');
|
||
|
|
}
|
||
|
|
|
||
|
|
availableTemplates = await response.json();
|
||
|
|
renderTemplateList();
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Failed to load templates:', error);
|
||
|
|
availableTemplates = [];
|
||
|
|
renderTemplateList();
|
||
|
|
notify(error.message || 'Kunne ikke hente templates', 'error');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderTemplateList() {
|
||
|
|
const search = String(document.getElementById('ttTemplateSearch').value || '').toLowerCase().trim();
|
||
|
|
const select = document.getElementById('ttTemplate');
|
||
|
|
const emptyState = document.getElementById('ttTemplateEmpty');
|
||
|
|
|
||
|
|
const filtered = availableTemplates.filter((template) => {
|
||
|
|
if (!search) return true;
|
||
|
|
const haystack = `${template.name || ''} ${template.description || ''} ${template.category || ''}`.toLowerCase();
|
||
|
|
return haystack.includes(search);
|
||
|
|
});
|
||
|
|
|
||
|
|
select.innerHTML = filtered.map((template) => {
|
||
|
|
const scopeLabel = template.template_type === 'company' ? 'Firma' : template.template_type === 'global' ? 'Faelles' : 'Intern';
|
||
|
|
const cat = template.category || 'andet';
|
||
|
|
return `<option value="${template.id}">${template.name} [${scopeLabel}] (${cat})</option>`;
|
||
|
|
}).join('');
|
||
|
|
|
||
|
|
emptyState.classList.toggle('d-none', filtered.length > 0);
|
||
|
|
document.getElementById('ttRunBtn').disabled = true;
|
||
|
|
currentPreview = null;
|
||
|
|
document.getElementById('ttPreviewSummary').textContent = 'Ingen preview endnu.';
|
||
|
|
document.getElementById('ttPreviewList').innerHTML = '';
|
||
|
|
}
|
||
|
|
|
||
|
|
function onTemplateSelected() {
|
||
|
|
currentPreview = null;
|
||
|
|
document.getElementById('ttRunBtn').disabled = true;
|
||
|
|
document.getElementById('ttPreviewSummary').textContent = 'Template valgt. Klik "Opdater preview".';
|
||
|
|
document.getElementById('ttPreviewList').innerHTML = '';
|
||
|
|
}
|
||
|
|
|
||
|
|
function onAssigneeModeChanged() {
|
||
|
|
const mode = document.getElementById('ttAssigneeMode').value;
|
||
|
|
const userInput = document.getElementById('ttAssigneeUserId');
|
||
|
|
const roleInput = document.getElementById('ttAssigneeRoleId');
|
||
|
|
|
||
|
|
userInput.classList.toggle('d-none', mode !== 'specific_user');
|
||
|
|
roleInput.classList.toggle('d-none', mode !== 'specific_role');
|
||
|
|
}
|
||
|
|
|
||
|
|
function buildPayload() {
|
||
|
|
const templateId = Number(document.getElementById('ttTemplate').value);
|
||
|
|
if (!templateId) {
|
||
|
|
throw new Error('Vaelg en template');
|
||
|
|
}
|
||
|
|
|
||
|
|
const mode = document.getElementById('ttMode').value;
|
||
|
|
const assigneeMode = document.getElementById('ttAssigneeMode').value;
|
||
|
|
const startDate = document.getElementById('ttStartDate').value || todayIso();
|
||
|
|
const assigneeUserIdRaw = document.getElementById('ttAssigneeUserId').value;
|
||
|
|
const assigneeRoleIdRaw = document.getElementById('ttAssigneeRoleId').value;
|
||
|
|
|
||
|
|
const payload = {
|
||
|
|
template_id: templateId,
|
||
|
|
start_date: startDate,
|
||
|
|
mode,
|
||
|
|
assignee_mode: assigneeMode,
|
||
|
|
};
|
||
|
|
|
||
|
|
if (assigneeMode === 'specific_user') {
|
||
|
|
const parsed = Number(assigneeUserIdRaw);
|
||
|
|
if (!parsed) throw new Error('Udfyld medarbejder ID');
|
||
|
|
payload.assignee_user_id = parsed;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (assigneeMode === 'specific_role') {
|
||
|
|
const parsed = Number(assigneeRoleIdRaw);
|
||
|
|
if (!parsed) throw new Error('Udfyld rolle ID');
|
||
|
|
payload.assignee_role_id = parsed;
|
||
|
|
}
|
||
|
|
|
||
|
|
return payload;
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderPreview(preview) {
|
||
|
|
const summary = preview.summary || {};
|
||
|
|
const list = Array.isArray(preview.items) ? preview.items : [];
|
||
|
|
|
||
|
|
document.getElementById('ttPreviewSummary').textContent =
|
||
|
|
`Du er ved at oprette: ${summary.subcases || 0} undersager, ${summary.tasks || 0} opgaver, ${summary.assignments || 0} tildelinger, ${summary.deadlines || 0} deadlines.`;
|
||
|
|
|
||
|
|
document.getElementById('ttPreviewList').innerHTML = list.map((item) => {
|
||
|
|
const typeLabel = item.item_type === 'subcase' ? 'Undersag' : 'Task';
|
||
|
|
return `
|
||
|
|
<div class="list-group-item">
|
||
|
|
<div class="d-flex justify-content-between align-items-start gap-3">
|
||
|
|
<div>
|
||
|
|
<div class="fw-semibold">${escapeHtml(item.title || '')}</div>
|
||
|
|
<div class="small text-muted">${escapeHtml(item.description || '')}</div>
|
||
|
|
</div>
|
||
|
|
<div class="text-end small text-muted">
|
||
|
|
<div>${typeLabel}</div>
|
||
|
|
<div>${escapeHtml(item.planned_due_date || '-')}</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
}).join('');
|
||
|
|
}
|
||
|
|
|
||
|
|
async function runPreview() {
|
||
|
|
if (!currentCaseId) return;
|
||
|
|
|
||
|
|
try {
|
||
|
|
const payload = buildPayload();
|
||
|
|
const response = await fetch(`/api/v1/cases/${currentCaseId}/template-preview`, {
|
||
|
|
method: 'POST',
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
credentials: 'include',
|
||
|
|
body: JSON.stringify(payload)
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!response.ok) {
|
||
|
|
const body = await response.json().catch(() => ({}));
|
||
|
|
throw new Error(body.detail || 'Preview fejlede');
|
||
|
|
}
|
||
|
|
|
||
|
|
const preview = await response.json();
|
||
|
|
currentPreview = preview;
|
||
|
|
renderPreview(preview);
|
||
|
|
document.getElementById('ttRunBtn').disabled = false;
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Preview failed:', error);
|
||
|
|
notify(error.message || 'Preview fejlede', 'error');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function runTemplate() {
|
||
|
|
if (!currentCaseId) return;
|
||
|
|
if (!currentPreview) {
|
||
|
|
notify('Koer preview foerst', 'error');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const runButton = document.getElementById('ttRunBtn');
|
||
|
|
runButton.disabled = true;
|
||
|
|
const originalText = runButton.textContent;
|
||
|
|
runButton.textContent = 'Opretter...';
|
||
|
|
|
||
|
|
try {
|
||
|
|
const payload = buildPayload();
|
||
|
|
const response = await fetch(`/api/v1/cases/${currentCaseId}/run-template`, {
|
||
|
|
method: 'POST',
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
credentials: 'include',
|
||
|
|
body: JSON.stringify(payload)
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!response.ok) {
|
||
|
|
const body = await response.json().catch(() => ({}));
|
||
|
|
throw new Error(body.detail || 'Koersel fejlede');
|
||
|
|
}
|
||
|
|
|
||
|
|
const result = await response.json();
|
||
|
|
notify(`Template koert. Oprettet ${result.summary?.tasks || 0} tasks og ${result.summary?.subcases || 0} undersager.`, 'success');
|
||
|
|
|
||
|
|
if (typeof window.syncCaseTagsUi === 'function') {
|
||
|
|
window.syncCaseTagsUi();
|
||
|
|
}
|
||
|
|
if (typeof window.loadTodoSteps === 'function') {
|
||
|
|
window.loadTodoSteps();
|
||
|
|
}
|
||
|
|
await loadRunHistory();
|
||
|
|
|
||
|
|
document.getElementById('ttRunBtn').disabled = true;
|
||
|
|
document.getElementById('ttPreviewSummary').textContent = 'Template koert. Vaelg en ny template eller opdater preview igen.';
|
||
|
|
} catch (error) {
|
||
|
|
console.error('Run failed:', error);
|
||
|
|
notify(error.message || 'Koersel fejlede', 'error');
|
||
|
|
} finally {
|
||
|
|
runButton.disabled = false;
|
||
|
|
runButton.textContent = originalText;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function escapeHtml(value) {
|
||
|
|
return String(value || '')
|
||
|
|
.replace(/&/g, '&')
|
||
|
|
.replace(/</g, '<')
|
||
|
|
.replace(/>/g, '>')
|
||
|
|
.replace(/"/g, '"')
|
||
|
|
.replace(/'/g, ''');
|
||
|
|
}
|
||
|
|
|
||
|
|
function formatDateTime(value) {
|
||
|
|
if (!value) return '-';
|
||
|
|
const parsed = new Date(value);
|
||
|
|
if (Number.isNaN(parsed.getTime())) return String(value);
|
||
|
|
return parsed.toLocaleString('da-DK', {
|
||
|
|
year: 'numeric',
|
||
|
|
month: '2-digit',
|
||
|
|
day: '2-digit',
|
||
|
|
hour: '2-digit',
|
||
|
|
minute: '2-digit'
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
async function loadRunHistory() {
|
||
|
|
const container = document.getElementById('ttRunHistory');
|
||
|
|
if (!container || !currentCaseId) return;
|
||
|
|
|
||
|
|
container.innerHTML = '<div class="text-muted">Indlaeser historik...</div>';
|
||
|
|
|
||
|
|
try {
|
||
|
|
const response = await fetch(`/api/v1/cases/${currentCaseId}/template-runs?limit=10`, {
|
||
|
|
credentials: 'include'
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!response.ok) {
|
||
|
|
const body = await response.json().catch(() => ({}));
|
||
|
|
throw new Error(body.detail || 'Kunne ikke hente historik');
|
||
|
|
}
|
||
|
|
|
||
|
|
const rows = await response.json();
|
||
|
|
if (!Array.isArray(rows) || rows.length === 0) {
|
||
|
|
container.innerHTML = '<div class="text-muted">Ingen template-koersler endnu.</div>';
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
container.innerHTML = rows.map((run) => {
|
||
|
|
const status = escapeHtml(run.status || 'ukendt');
|
||
|
|
const templateName = escapeHtml(run.template_name || `Template #${run.template_id || '-'}`);
|
||
|
|
const startedAt = formatDateTime(run.started_at);
|
||
|
|
const taskCount = Number(run.created_tasks || 0);
|
||
|
|
const subcaseCount = Number(run.created_subcases || 0);
|
||
|
|
const statusClass = status === 'completed' ? 'text-success' : (status === 'failed' ? 'text-danger' : 'text-muted');
|
||
|
|
|
||
|
|
return `
|
||
|
|
<div class="border rounded p-2 mb-2">
|
||
|
|
<div class="d-flex justify-content-between align-items-start gap-2">
|
||
|
|
<div>
|
||
|
|
<div class="fw-semibold">${templateName}</div>
|
||
|
|
<div class="small text-muted">${escapeHtml(startedAt)}</div>
|
||
|
|
</div>
|
||
|
|
<span class="small fw-semibold ${statusClass}">${status}</span>
|
||
|
|
</div>
|
||
|
|
<div class="small mt-1">Oprettet: ${taskCount} opgaver, ${subcaseCount} undersager</div>
|
||
|
|
${run.template_id ? `
|
||
|
|
<div class="mt-2">
|
||
|
|
<button type="button" class="btn btn-sm btn-outline-primary" onclick="window.reuseCaseTemplateFromHistory(${Number(run.template_id)})">Vaelg igen</button>
|
||
|
|
</div>
|
||
|
|
` : ''}
|
||
|
|
${run.error_message ? `<div class="small text-danger mt-1">${escapeHtml(run.error_message)}</div>` : ''}
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
}).join('');
|
||
|
|
} catch (error) {
|
||
|
|
container.innerHTML = `<div class="text-danger">${escapeHtml(error.message || 'Kunne ikke hente historik')}</div>`;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
async function reuseTemplateFromHistory(templateId) {
|
||
|
|
const wantedId = Number(templateId || 0);
|
||
|
|
if (!wantedId) {
|
||
|
|
notify('Ugyldigt template-id', 'error');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const sourceSelect = document.getElementById('ttSource');
|
||
|
|
const categorySelect = document.getElementById('ttCategory');
|
||
|
|
const searchInput = document.getElementById('ttTemplateSearch');
|
||
|
|
const templateSelect = document.getElementById('ttTemplate');
|
||
|
|
|
||
|
|
// Widen filters to make sure template is available in selector.
|
||
|
|
sourceSelect.value = 'all';
|
||
|
|
categorySelect.value = '';
|
||
|
|
searchInput.value = '';
|
||
|
|
|
||
|
|
await refreshTemplates();
|
||
|
|
|
||
|
|
const hasOption = Array.from(templateSelect.options).some((opt) => Number(opt.value) === wantedId);
|
||
|
|
if (!hasOption) {
|
||
|
|
notify('Template findes ikke i nuvaerende scope eller er deaktiveret', 'error');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
templateSelect.value = String(wantedId);
|
||
|
|
onTemplateSelected();
|
||
|
|
await runPreview();
|
||
|
|
}
|
||
|
|
|
||
|
|
async function openTaskTemplateSelectorModal(detailOrCaseId) {
|
||
|
|
ensureModal();
|
||
|
|
|
||
|
|
if (typeof detailOrCaseId === 'object' && detailOrCaseId !== null) {
|
||
|
|
currentCaseId = Number(detailOrCaseId.entity_id || detailOrCaseId.case_id || 0);
|
||
|
|
currentAction = detailOrCaseId.action || null;
|
||
|
|
} else {
|
||
|
|
currentCaseId = Number(detailOrCaseId || 0);
|
||
|
|
currentAction = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!currentCaseId) {
|
||
|
|
notify('Kunne ikke finde sag-id til template modal', 'error');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
document.getElementById('ttStartDate').value = todayIso();
|
||
|
|
document.getElementById('ttTemplateSearch').value = '';
|
||
|
|
document.getElementById('ttAssigneeMode').value = 'template_default';
|
||
|
|
document.getElementById('ttAssigneeUserId').value = '';
|
||
|
|
document.getElementById('ttAssigneeRoleId').value = '';
|
||
|
|
onAssigneeModeChanged();
|
||
|
|
|
||
|
|
const actionConfig = getActionConfig();
|
||
|
|
|
||
|
|
const sourceSelect = document.getElementById('ttSource');
|
||
|
|
sourceSelect.value = 'all';
|
||
|
|
if (!actionConfig.allow_company_templates && actionConfig.allow_global_templates) {
|
||
|
|
sourceSelect.value = 'global';
|
||
|
|
}
|
||
|
|
if (actionConfig.allow_company_templates && !actionConfig.allow_global_templates) {
|
||
|
|
sourceSelect.value = 'company';
|
||
|
|
}
|
||
|
|
|
||
|
|
const categorySelect = document.getElementById('ttCategory');
|
||
|
|
categorySelect.value = actionConfig.default_category || '';
|
||
|
|
|
||
|
|
await refreshTemplates();
|
||
|
|
await loadRunHistory();
|
||
|
|
modalInstance.show();
|
||
|
|
}
|
||
|
|
|
||
|
|
window.reuseCaseTemplateFromHistory = reuseTemplateFromHistory;
|
||
|
|
window.openTaskTemplateSelectorModal = openTaskTemplateSelectorModal;
|
||
|
|
|
||
|
|
window.addEventListener('hub:tag-action', function (event) {
|
||
|
|
const detail = event && event.detail ? event.detail : null;
|
||
|
|
if (!detail || !detail.action || detail.action.type !== 'open_task_template_modal') {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
// Auto-open is disabled. Users open the selector by clicking the template tag.
|
||
|
|
});
|
||
|
|
})();
|