multi value filtering added and font color fix for duplicates

This commit is contained in:
2026-08-11 23:28:35 +02:00
parent 5594431749
commit e0175ab48a
340 changed files with 739 additions and 160 deletions
Regular → Executable
+120 -6
View File
@@ -99,6 +99,33 @@
{% if selected %}<input type="hidden" name="category_id" value="{{ selected.id }}">{% endif %}
</form>
{% endif %}
<dialog id="software-compare-select-dialog" class="software-compare-select-dialog">
<form method="dialog">
<div class="dialog-heading">
<h2>{{ t('software.compare.select_second_title', 'Zweites Asset auswählen') }}</h2>
<button type="button" class="button button-secondary button-small" id="software-compare-dialog-close" aria-label="{{ t('common.close', 'Schließen')|e }}">×</button>
</div>
<p class="muted">{{ t('software.compare.select_second_help', 'Wähle das zweite Asset für den Vergleich. Das Auswahlfeld zeigt nur vorhandene Werte des gewählten Feldes.') }}</p>
<label>{{ t('software.compare.first_asset', 'Erstes Asset') }}
<input type="text" id="software-compare-first-name" readonly>
</label>
<label>{{ t('software.compare.select_by', 'Auswahl nach') }}
<select id="software-compare-field-select">
{% for option in compare_field_options %}<option value="{{ option.field_name }}"{% if option.field_name == 'name' %} selected{% endif %}>{{ option.label }}</option>{% endfor %}
</select>
</label>
<label>{{ t('software.compare.second_asset', 'Zweites Asset') }}
<select id="software-compare-second-select" disabled>
<option value="">{{ t('common.loading', 'Lade…') }}</option>
</select>
</label>
<p class="muted software-compare-selection-status" id="software-compare-selection-status" role="status" aria-live="polite"></p>
<div class="form-actions">
<button type="button" class="button button-secondary" id="software-compare-dialog-cancel">{{ t('common.cancel', 'Abbrechen') }}</button>
<button type="button" class="button" id="software-compare-dialog-start" disabled>⇄ {{ t('software.compare.button', 'Software vergleichen') }}</button>
</div>
</form>
</dialog>
{% endif %}
<div class="table-scrollbar-top" id="asset-table-scroll-top" aria-hidden="true"><div></div></div>
<div class="table-scroll sticky-table" id="asset-table-scroll">
@@ -120,7 +147,7 @@
<th data-no-sort="1" data-no-filter="1" class="actions-column">{{ t('common.actions', 'Aktionen') }}</th>
</tr></thead>
<tbody>
{% for a in assets %}<tr data-platform="{{ asset_platforms.get(a.id, 'unknown') }}">
{% for a in assets %}<tr data-platform="{{ asset_platforms.get(a.id, 'unknown') }}" data-asset-id="{{ a.id }}" data-asset-name="{{ a.name|e }}">
<td class="asset-visual-column" data-field="asset_visual">
<a href="/assets/{{ a.id }}" class="asset-visual-link" aria-label="{{ a.name|e }}">
{% set visual_source = a.visual_source or 'auto' %}
@@ -387,6 +414,14 @@
const deleteButton = document.getElementById('bulk-delete-button');
const mergeButton = document.getElementById('merge-duplicates-button');
const softwareCompareButton = document.getElementById('software-compare-button');
const softwareCompareDialog = document.getElementById('software-compare-select-dialog');
const softwareCompareField = document.getElementById('software-compare-field-select');
const softwareCompareSecond = document.getElementById('software-compare-second-select');
const softwareCompareFirstName = document.getElementById('software-compare-first-name');
const softwareCompareStatus = document.getElementById('software-compare-selection-status');
const softwareCompareStart = document.getElementById('software-compare-dialog-start');
const softwareCompareClose = document.getElementById('software-compare-dialog-close');
const softwareCompareCancel = document.getElementById('software-compare-dialog-cancel');
const jobTypeSelect = document.getElementById('bulk-job-type');
const jobStartButton = document.getElementById('bulk-job-start-button');
const isVisible = box => {
@@ -424,8 +459,10 @@
mergeButton.textContent = count >= 2 ? `⧉ {{ t('duplicates.merge') }} (${count})` : '⧉ {{ t('duplicates.merge') }}';
}
if (softwareCompareButton) {
softwareCompareButton.disabled = count !== 2;
softwareCompareButton.textContent = count === 2 ? `⇄ {{ t('software.compare.button', 'Software vergleichen') }} (2)` : '⇄ {{ t('software.compare.button', 'Software vergleichen') }}';
softwareCompareButton.disabled = count < 1 || count > 2;
softwareCompareButton.textContent = (count === 1 || count === 2)
? `⇄ {{ t('software.compare.button', 'Software vergleichen') }} (${count})`
: '⇄ {{ t('software.compare.button', 'Software vergleichen') }}';
}
if (jobTypeSelect) { jobTypeSelect.disabled = count === 0; filterJobDefinitions(); }
if (jobStartButton) {
@@ -436,13 +473,70 @@
}
if (all) { all.checked = visible.length > 0 && count === visible.length; all.indeterminate = count > 0 && count < visible.length; }
}
function submit(formId, containerId) {
function submitIds(formId, containerId, ids) {
const form = document.getElementById(formId);
const container = document.getElementById(containerId);
if (!form || !container) return;
container.innerHTML = '';
selected().forEach(id => { const input = document.createElement('input'); input.type='hidden'; input.name='asset_ids'; input.value=id; container.appendChild(input); });
ids.forEach(id => { const input = document.createElement('input'); input.type='hidden'; input.name='asset_ids'; input.value=id; container.appendChild(input); });
form.requestSubmit();
}
function submit(formId, containerId) {
submitIds(formId, containerId, selected());
}
function selectedAssetName(assetId) {
const box = boxes().find(item => item.value === String(assetId));
return box?.closest('tr')?.dataset.assetName || '';
}
async function loadSoftwareCompareOptions() {
const ids = selected();
if (ids.length !== 1 || !softwareCompareField || !softwareCompareSecond) return;
const firstId = ids[0];
softwareCompareSecond.disabled = true;
softwareCompareSecond.innerHTML = `<option value="">{{ t('common.loading', 'Lade…')|e }}</option>`;
if (softwareCompareStatus) softwareCompareStatus.textContent = '';
if (softwareCompareStart) softwareCompareStart.disabled = true;
const target = new URL('/api/assets/software-compare/options', window.location.origin);
target.searchParams.set('field_name', softwareCompareField.value || 'name');
target.searchParams.set('exclude_asset_id', firstId);
try {
const response = await fetch(target.toString(), {headers: {'Accept': 'application/json'}, cache: 'no-store'});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const payload = await response.json();
const options = Array.isArray(payload.options) ? payload.options : [];
softwareCompareSecond.innerHTML = '';
const placeholder = document.createElement('option');
placeholder.value = '';
placeholder.textContent = options.length
? {{ t('software.compare.choose_value', 'Wert auswählen …')|tojson }}
: {{ t('software.compare.no_selectable_values', 'Keine passenden Werte vorhanden')|tojson }};
softwareCompareSecond.appendChild(placeholder);
options.forEach(item => {
const option = document.createElement('option');
option.value = String(item.asset_id);
option.textContent = item.label || item.value || item.asset_name || String(item.asset_id);
option.dataset.assetName = item.asset_name || '';
softwareCompareSecond.appendChild(option);
});
softwareCompareSecond.disabled = options.length === 0;
if (softwareCompareStatus) {
softwareCompareStatus.textContent = options.length
? {{ t('software.compare.available_values', '{count} Werte verfügbar')|tojson }}.replace('{count}', String(options.length))
: {{ t('software.compare.no_selectable_values', 'Keine passenden Werte vorhanden')|tojson }};
}
} catch (error) {
softwareCompareSecond.innerHTML = `<option value="">{{ t('software.compare.load_error', 'Auswahl konnte nicht geladen werden')|e }}</option>`;
if (softwareCompareStatus) softwareCompareStatus.textContent = {{ t('software.compare.load_error', 'Auswahl konnte nicht geladen werden')|tojson }};
}
}
async function openSoftwareCompareDialog() {
const ids = selected();
if (ids.length !== 1 || !softwareCompareDialog) return;
if (softwareCompareFirstName) softwareCompareFirstName.value = selectedAssetName(ids[0]);
if (softwareCompareField) softwareCompareField.value = 'name';
softwareCompareDialog.showModal();
await loadSoftwareCompareOptions();
}
table.addEventListener('change', event => {
if (event.target.matches('.asset-select')) refresh();
});
@@ -450,7 +544,27 @@
editButton?.addEventListener('click', () => submit('bulk-edit-form', 'bulk-edit-ids'));
deleteButton?.addEventListener('click', () => submit('bulk-delete-form', 'bulk-delete-ids'));
mergeButton?.addEventListener('click', () => submit('merge-duplicates-form', 'merge-duplicate-ids'));
softwareCompareButton?.addEventListener('click', () => submit('software-compare-form', 'software-compare-ids'));
softwareCompareButton?.addEventListener('click', () => {
const ids = selected();
if (ids.length === 2) {
submitIds('software-compare-form', 'software-compare-ids', ids);
return;
}
if (ids.length === 1) openSoftwareCompareDialog();
});
softwareCompareField?.addEventListener('change', loadSoftwareCompareOptions);
softwareCompareSecond?.addEventListener('change', () => {
if (softwareCompareStart) softwareCompareStart.disabled = !softwareCompareSecond.value;
});
softwareCompareStart?.addEventListener('click', () => {
const ids = selected();
const secondId = softwareCompareSecond?.value || '';
if (ids.length !== 1 || !secondId) return;
softwareCompareDialog?.close();
submitIds('software-compare-form', 'software-compare-ids', [ids[0], secondId]);
});
softwareCompareClose?.addEventListener('click', () => softwareCompareDialog?.close());
softwareCompareCancel?.addEventListener('click', () => softwareCompareDialog?.close());
jobTypeSelect?.addEventListener('change', refresh);
jobStartButton?.addEventListener('click', () => {
const count = selected().length;