asdsd
This commit is contained in:
@@ -504,6 +504,11 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
|
|||||||
$fxCatalogAvailable = $fxCatalogOptions !== [];
|
$fxCatalogAvailable = $fxCatalogOptions !== [];
|
||||||
$fxPreferred = is_array($current['preferred_currencies'] ?? null) ? $current['preferred_currencies'] : [];
|
$fxPreferred = is_array($current['preferred_currencies'] ?? null) ? $current['preferred_currencies'] : [];
|
||||||
$fxUseSeparateDb = !empty($current['use_separate_db']);
|
$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">
|
<section class="setup-panel">
|
||||||
<div class="setup-panel__head">
|
<div class="setup-panel__head">
|
||||||
@@ -558,24 +563,36 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
|
|||||||
<div class="setup-grid">
|
<div class="setup-grid">
|
||||||
<label class="setup-field muted">
|
<label class="setup-field muted">
|
||||||
<span>Anzeige-Basiswaehrung</span>
|
<span>Anzeige-Basiswaehrung</span>
|
||||||
<select name="display_base_currency">
|
<input type="hidden" name="display_base_currency" value="<?= e((string) ($current['display_base_currency'] ?? '')) ?>" data-fx-base-hidden>
|
||||||
<?php foreach ($fxCatalogOptions as $code => $name): ?>
|
<div class="fx-setup-picker" data-fx-base-picker data-currencies='<?= e(is_string($fxCatalogJson) ? $fxCatalogJson : '[]') ?>'>
|
||||||
<option value="<?= e($code) ?>" <?= (string) ($current['display_base_currency'] ?? '') === $code ? 'selected' : '' ?>>
|
<input
|
||||||
<?= e($code . ' - ' . $name) ?>
|
type="text"
|
||||||
</option>
|
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']]) : '')) ?>"
|
||||||
<?php endforeach; ?>
|
placeholder="Waehrung suchen"
|
||||||
</select>
|
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>
|
||||||
<label class="setup-field muted">
|
<label class="setup-field muted">
|
||||||
<span>Bevorzugte Waehrungen</span>
|
<span>Bevorzugte Waehrungen</span>
|
||||||
<select name="preferred_currencies[]" multiple size="<?= e((string) min(12, max(6, count($fxCatalogOptions)))) ?>">
|
<div class="fx-setup-picker" data-fx-multi-picker data-currencies='<?= e(is_string($fxCatalogJson) ? $fxCatalogJson : '[]') ?>'>
|
||||||
<?php foreach ($fxCatalogOptions as $code => $name): ?>
|
<div class="fx-setup-tags" data-fx-tags>
|
||||||
<option value="<?= e($code) ?>" <?= in_array($code, $fxPreferred, true) ? 'selected' : '' ?>>
|
<?php foreach ($fxPreferred as $code): ?>
|
||||||
<?= e($code . ' - ' . $name) ?>
|
<?php if (!isset($fxCatalogOptions[$code])) { continue; } ?>
|
||||||
</option>
|
<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; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</div>
|
||||||
<small class="muted">Mehrfachauswahl mit Strg/Cmd oder Shift.</small>
|
<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>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@@ -727,7 +744,232 @@ $activeDbGroup = $testGroup !== null && array_key_exists($testGroup, $dbGroups)
|
|||||||
toggle.addEventListener('change', sync);
|
toggle.addEventListener('change', sync);
|
||||||
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>
|
</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 else: ?>
|
||||||
<?php if ($generalFields !== []): ?>
|
<?php if ($generalFields !== []): ?>
|
||||||
<section class="setup-panel">
|
<section class="setup-panel">
|
||||||
|
|||||||
Reference in New Issue
Block a user