asdsd
This commit is contained in:
@@ -504,6 +504,11 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
|
||||
$fxCatalogAvailable = $fxCatalogOptions !== [];
|
||||
$fxPreferred = is_array($current['preferred_currencies'] ?? null) ? $current['preferred_currencies'] : [];
|
||||
$fxUseSeparateDb = !empty($current['use_separate_db']);
|
||||
$fxCatalogJson = json_encode(array_map(
|
||||
static fn (string $name, string $code): array => ['code' => $code, 'name' => $name],
|
||||
array_values($fxCatalogOptions),
|
||||
array_keys($fxCatalogOptions)
|
||||
), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
?>
|
||||
<section class="setup-panel">
|
||||
<div class="setup-panel__head">
|
||||
@@ -558,24 +563,36 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
|
||||
<div class="setup-grid">
|
||||
<label class="setup-field muted">
|
||||
<span>Anzeige-Basiswaehrung</span>
|
||||
<select name="display_base_currency">
|
||||
<?php foreach ($fxCatalogOptions as $code => $name): ?>
|
||||
<option value="<?= e($code) ?>" <?= (string) ($current['display_base_currency'] ?? '') === $code ? 'selected' : '' ?>>
|
||||
<?= e($code . ' - ' . $name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<input type="hidden" name="display_base_currency" value="<?= e((string) ($current['display_base_currency'] ?? '')) ?>" data-fx-base-hidden>
|
||||
<div class="fx-setup-picker" data-fx-base-picker data-currencies='<?= e(is_string($fxCatalogJson) ? $fxCatalogJson : '[]') ?>'>
|
||||
<input
|
||||
type="text"
|
||||
value="<?= e((string) (($current['display_base_currency'] ?? '') !== '' && isset($fxCatalogOptions[(string) $current['display_base_currency']]) ? ((string) $current['display_base_currency'] . ' - ' . $fxCatalogOptions[(string) $current['display_base_currency']]) : '')) ?>"
|
||||
placeholder="Waehrung suchen"
|
||||
autocomplete="off"
|
||||
data-fx-base-input
|
||||
>
|
||||
<div class="fx-setup-suggestions" data-fx-base-suggestions hidden></div>
|
||||
</div>
|
||||
<small class="muted">Suche nach Code oder Bezeichnung und waehle einen Treffer aus.</small>
|
||||
</label>
|
||||
<label class="setup-field muted">
|
||||
<span>Bevorzugte Waehrungen</span>
|
||||
<select name="preferred_currencies[]" multiple size="<?= e((string) min(12, max(6, count($fxCatalogOptions)))) ?>">
|
||||
<?php foreach ($fxCatalogOptions as $code => $name): ?>
|
||||
<option value="<?= e($code) ?>" <?= in_array($code, $fxPreferred, true) ? 'selected' : '' ?>>
|
||||
<?= e($code . ' - ' . $name) ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<small class="muted">Mehrfachauswahl mit Strg/Cmd oder Shift.</small>
|
||||
<div class="fx-setup-picker" data-fx-multi-picker data-currencies='<?= e(is_string($fxCatalogJson) ? $fxCatalogJson : '[]') ?>'>
|
||||
<div class="fx-setup-tags" data-fx-tags>
|
||||
<?php foreach ($fxPreferred as $code): ?>
|
||||
<?php if (!isset($fxCatalogOptions[$code])) { continue; } ?>
|
||||
<span class="fx-setup-tag" data-code="<?= e($code) ?>">
|
||||
<?= e($code) ?>
|
||||
<button type="button" data-remove-code="<?= e($code) ?>" aria-label="Entfernen">x</button>
|
||||
</span>
|
||||
<input type="hidden" name="preferred_currencies[]" value="<?= e($code) ?>" data-code-hidden="<?= e($code) ?>">
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<input type="text" value="" placeholder="Waehrung suchen und hinzufuegen" autocomplete="off" data-fx-multi-input>
|
||||
<div class="fx-setup-suggestions" data-fx-multi-suggestions hidden></div>
|
||||
</div>
|
||||
<small class="muted">Tippen, Treffer anklicken und zur Liste hinzufuegen.</small>
|
||||
</label>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
@@ -727,7 +744,232 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
|
||||
toggle.addEventListener('change', sync);
|
||||
sync();
|
||||
})();
|
||||
|
||||
(() => {
|
||||
const baseRoot = document.querySelector('[data-fx-base-picker]');
|
||||
const baseHidden = document.querySelector('[data-fx-base-hidden]');
|
||||
const baseInput = document.querySelector('[data-fx-base-input]');
|
||||
const baseSuggestions = document.querySelector('[data-fx-base-suggestions]');
|
||||
|
||||
const multiRoot = document.querySelector('[data-fx-multi-picker]');
|
||||
const multiInput = document.querySelector('[data-fx-multi-input]');
|
||||
const multiSuggestions = document.querySelector('[data-fx-multi-suggestions]');
|
||||
const tagsRoot = document.querySelector('[data-fx-tags]');
|
||||
|
||||
const parseCurrencies = (root) => {
|
||||
if (!root) return [];
|
||||
try {
|
||||
const parsed = JSON.parse(root.dataset.currencies || '[]');
|
||||
return Array.isArray(parsed) ? parsed : [];
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const currencies = parseCurrencies(baseRoot || multiRoot);
|
||||
const formatLabel = (item) => `${item.code} - ${item.name}`;
|
||||
const searchCurrencies = (query, excluded = []) => {
|
||||
const needle = String(query || '').trim().toLowerCase();
|
||||
const excludedSet = new Set(excluded);
|
||||
const filtered = currencies.filter((item) => {
|
||||
if (!item || !item.code || excludedSet.has(item.code)) {
|
||||
return false;
|
||||
}
|
||||
if (needle === '') {
|
||||
return true;
|
||||
}
|
||||
return item.code.toLowerCase().includes(needle) || String(item.name || '').toLowerCase().includes(needle);
|
||||
});
|
||||
return filtered.slice(0, 12);
|
||||
};
|
||||
|
||||
const closeSuggestions = (node) => {
|
||||
if (!node) return;
|
||||
node.hidden = true;
|
||||
node.innerHTML = '';
|
||||
};
|
||||
|
||||
const renderSuggestions = (node, items, onSelect) => {
|
||||
if (!node || items.length === 0) {
|
||||
closeSuggestions(node);
|
||||
return;
|
||||
}
|
||||
node.innerHTML = '';
|
||||
items.forEach((item) => {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.className = 'fx-setup-suggestion';
|
||||
button.textContent = formatLabel(item);
|
||||
button.addEventListener('click', () => onSelect(item));
|
||||
node.appendChild(button);
|
||||
});
|
||||
node.hidden = false;
|
||||
};
|
||||
|
||||
if (baseRoot && baseHidden && baseInput && baseSuggestions) {
|
||||
const applyBase = (item) => {
|
||||
baseHidden.value = item.code;
|
||||
baseInput.value = formatLabel(item);
|
||||
closeSuggestions(baseSuggestions);
|
||||
};
|
||||
|
||||
baseInput.addEventListener('input', () => {
|
||||
baseHidden.value = '';
|
||||
renderSuggestions(baseSuggestions, searchCurrencies(baseInput.value), applyBase);
|
||||
});
|
||||
|
||||
baseInput.addEventListener('focus', () => {
|
||||
renderSuggestions(baseSuggestions, searchCurrencies(baseInput.value), applyBase);
|
||||
});
|
||||
|
||||
baseInput.form?.addEventListener('submit', () => {
|
||||
if (baseHidden.value !== '') {
|
||||
return;
|
||||
}
|
||||
const first = searchCurrencies(baseInput.value)[0] || null;
|
||||
if (first) {
|
||||
applyBase(first);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (multiRoot && multiInput && multiSuggestions && tagsRoot) {
|
||||
const selectedCodes = () => Array.from(tagsRoot.querySelectorAll('[data-code]')).map((node) => node.getAttribute('data-code') || '');
|
||||
|
||||
const addTag = (item) => {
|
||||
if (!item || selectedCodes().includes(item.code)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tag = document.createElement('span');
|
||||
tag.className = 'fx-setup-tag';
|
||||
tag.setAttribute('data-code', item.code);
|
||||
tag.append(document.createTextNode(item.code));
|
||||
|
||||
const remove = document.createElement('button');
|
||||
remove.type = 'button';
|
||||
remove.setAttribute('data-remove-code', item.code);
|
||||
remove.setAttribute('aria-label', 'Entfernen');
|
||||
remove.textContent = 'x';
|
||||
remove.addEventListener('click', () => {
|
||||
tag.remove();
|
||||
hidden.remove();
|
||||
renderSuggestions(multiSuggestions, searchCurrencies(multiInput.value, selectedCodes()), addTag);
|
||||
});
|
||||
tag.appendChild(remove);
|
||||
tagsRoot.appendChild(tag);
|
||||
|
||||
const hidden = document.createElement('input');
|
||||
hidden.type = 'hidden';
|
||||
hidden.name = 'preferred_currencies[]';
|
||||
hidden.value = item.code;
|
||||
hidden.setAttribute('data-code-hidden', item.code);
|
||||
multiRoot.appendChild(hidden);
|
||||
|
||||
multiInput.value = '';
|
||||
renderSuggestions(multiSuggestions, searchCurrencies('', selectedCodes()), addTag);
|
||||
};
|
||||
|
||||
tagsRoot.querySelectorAll('[data-remove-code]').forEach((button) => {
|
||||
button.addEventListener('click', () => {
|
||||
const code = button.getAttribute('data-remove-code') || '';
|
||||
tagsRoot.querySelector(`[data-code="${code}"]`)?.remove();
|
||||
multiRoot.querySelector(`[data-code-hidden="${code}"]`)?.remove();
|
||||
renderSuggestions(multiSuggestions, searchCurrencies(multiInput.value, selectedCodes()), addTag);
|
||||
});
|
||||
});
|
||||
|
||||
multiInput.addEventListener('input', () => {
|
||||
renderSuggestions(multiSuggestions, searchCurrencies(multiInput.value, selectedCodes()), addTag);
|
||||
});
|
||||
|
||||
multiInput.addEventListener('focus', () => {
|
||||
renderSuggestions(multiSuggestions, searchCurrencies(multiInput.value, selectedCodes()), addTag);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('click', (event) => {
|
||||
if (baseRoot && !baseRoot.contains(event.target)) {
|
||||
closeSuggestions(baseSuggestions);
|
||||
}
|
||||
if (multiRoot && !multiRoot.contains(event.target)) {
|
||||
closeSuggestions(multiSuggestions);
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<style>
|
||||
.fx-setup-picker {
|
||||
position: relative;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.fx-setup-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.fx-setup-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--brand-accent) 10%, var(--surface-strong));
|
||||
font-size: 0.88rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.fx-setup-tag button {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
line-height: 1;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.fx-setup-suggestions {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 20;
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
margin-top: 4px;
|
||||
padding: 8px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 14px;
|
||||
background: var(--surface-strong);
|
||||
box-shadow: 0 16px 35px rgba(1, 22, 32, 0.14);
|
||||
max-height: 280px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.fx-setup-suggestion {
|
||||
appearance: none;
|
||||
text-align: left;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 10px;
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
padding: 8px 10px;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.fx-setup-suggestion:hover,
|
||||
.fx-setup-suggestion:focus-visible {
|
||||
border-color: color-mix(in srgb, var(--brand-accent) 36%, transparent);
|
||||
background: color-mix(in srgb, var(--brand-accent) 8%, var(--surface-strong));
|
||||
outline: none;
|
||||
}
|
||||
</style>
|
||||
<?php else: ?>
|
||||
<?php if ($generalFields !== []): ?>
|
||||
<section class="setup-panel">
|
||||
|
||||
Reference in New Issue
Block a user