asdasd
This commit is contained in:
158
modules/fx-rates/assets/fx-rates-currencies.js
Normal file
158
modules/fx-rates/assets/fx-rates-currencies.js
Normal file
@@ -0,0 +1,158 @@
|
||||
(() => {
|
||||
const root = document.getElementById('fx-rates-currencies');
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
const page = JSON.parse(root.dataset.page || '{}');
|
||||
const currencies = Array.isArray(page.currencies) ? page.currencies : [];
|
||||
const selected = new Set(
|
||||
(Array.isArray(page.preferred_currencies) ? page.preferred_currencies : [])
|
||||
.map((code) => String(code || '').trim().toUpperCase())
|
||||
.filter(Boolean)
|
||||
);
|
||||
let displayBase = String(page.display_base_currency || '').trim().toUpperCase();
|
||||
|
||||
const nodes = {
|
||||
tokenList: root.querySelector('[data-fx-token-list]'),
|
||||
searchInput: root.querySelector('[data-fx-search-input]'),
|
||||
suggestions: root.querySelector('[data-fx-suggestions]'),
|
||||
displayBaseSelect: root.querySelector('[data-fx-display-base-select]'),
|
||||
displayBaseHidden: root.querySelector('[data-fx-display-base-hidden]'),
|
||||
hiddenPreferred: root.querySelector('[data-fx-hidden-preferred]'),
|
||||
};
|
||||
|
||||
const escapeHtml = (value) => String(value || '')
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll('<', '<')
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll("'", ''');
|
||||
|
||||
const currencyByCode = new Map(
|
||||
currencies.map((currency) => [String(currency.code || '').toUpperCase(), currency])
|
||||
);
|
||||
|
||||
const sortedSelectedCodes = () => Array.from(selected).sort((left, right) => left.localeCompare(right));
|
||||
|
||||
const ensureDisplayBase = () => {
|
||||
const available = sortedSelectedCodes();
|
||||
if (available.length === 0) {
|
||||
displayBase = '';
|
||||
return;
|
||||
}
|
||||
if (!displayBase || !selected.has(displayBase)) {
|
||||
displayBase = available[0];
|
||||
}
|
||||
};
|
||||
|
||||
const renderHiddenInputs = () => {
|
||||
if (!nodes.hiddenPreferred) {
|
||||
return;
|
||||
}
|
||||
nodes.hiddenPreferred.innerHTML = sortedSelectedCodes()
|
||||
.map((code) => `<input type="hidden" name="preferred_currencies[]" value="${escapeHtml(code)}">`)
|
||||
.join('');
|
||||
if (nodes.displayBaseHidden) {
|
||||
nodes.displayBaseHidden.value = displayBase;
|
||||
}
|
||||
};
|
||||
|
||||
const renderDisplayBase = () => {
|
||||
if (!nodes.displayBaseSelect) {
|
||||
return;
|
||||
}
|
||||
ensureDisplayBase();
|
||||
const available = sortedSelectedCodes();
|
||||
nodes.displayBaseSelect.innerHTML = available.length
|
||||
? available.map((code) => `<option value="${escapeHtml(code)}" ${code === displayBase ? 'selected' : ''}>${escapeHtml(code)}</option>`).join('')
|
||||
: '<option value="">Keine Waehrungen ausgewaehlt</option>';
|
||||
nodes.displayBaseSelect.disabled = available.length === 0;
|
||||
};
|
||||
|
||||
const removeCode = (code) => {
|
||||
selected.delete(code);
|
||||
renderAll();
|
||||
};
|
||||
|
||||
const renderTokens = () => {
|
||||
if (!nodes.tokenList) {
|
||||
return;
|
||||
}
|
||||
const selectedCodes = sortedSelectedCodes();
|
||||
if (selectedCodes.length === 0) {
|
||||
nodes.tokenList.innerHTML = '<div class="fx-text">Noch keine bevorzugten Waehrungen ausgewaehlt.</div>';
|
||||
return;
|
||||
}
|
||||
nodes.tokenList.innerHTML = selectedCodes.map((code) => {
|
||||
const currency = currencyByCode.get(code) || { code, name: code };
|
||||
return `
|
||||
<button type="button" class="fx-token" data-remove-code="${escapeHtml(code)}" title="${escapeHtml(code)} entfernen">
|
||||
<span>${escapeHtml(`${code} (${currency.name || code})`)}</span>
|
||||
<span class="fx-token-close">x</span>
|
||||
</button>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
nodes.tokenList.querySelectorAll('[data-remove-code]').forEach((button) => {
|
||||
button.addEventListener('click', () => {
|
||||
removeCode(String(button.getAttribute('data-remove-code') || '').toUpperCase());
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const renderSuggestions = () => {
|
||||
if (!nodes.suggestions || !nodes.searchInput) {
|
||||
return;
|
||||
}
|
||||
const needle = String(nodes.searchInput.value || '').trim().toLowerCase();
|
||||
if (!needle) {
|
||||
nodes.suggestions.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
const matches = currencies
|
||||
.filter((currency) => !selected.has(String(currency.code || '').toUpperCase()))
|
||||
.filter((currency) => {
|
||||
const code = String(currency.code || '').toLowerCase();
|
||||
const name = String(currency.name || '').toLowerCase();
|
||||
return code.includes(needle) || name.includes(needle);
|
||||
})
|
||||
.slice(0, 12);
|
||||
|
||||
nodes.suggestions.innerHTML = matches.map((currency) => `
|
||||
<button type="button" class="fx-suggestion" data-add-code="${escapeHtml(String(currency.code || '').toUpperCase())}">
|
||||
<strong>${escapeHtml(String(currency.code || '').toUpperCase())}</strong>
|
||||
<span>${escapeHtml(String(currency.name || ''))}</span>
|
||||
</button>
|
||||
`).join('');
|
||||
|
||||
nodes.suggestions.querySelectorAll('[data-add-code]').forEach((button) => {
|
||||
button.addEventListener('click', () => {
|
||||
const code = String(button.getAttribute('data-add-code') || '').toUpperCase();
|
||||
if (code) {
|
||||
selected.add(code);
|
||||
if (nodes.searchInput) {
|
||||
nodes.searchInput.value = '';
|
||||
}
|
||||
renderAll();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const renderAll = () => {
|
||||
ensureDisplayBase();
|
||||
renderTokens();
|
||||
renderDisplayBase();
|
||||
renderHiddenInputs();
|
||||
renderSuggestions();
|
||||
};
|
||||
|
||||
nodes.searchInput?.addEventListener('input', renderSuggestions);
|
||||
nodes.displayBaseSelect?.addEventListener('change', () => {
|
||||
displayBase = String(nodes.displayBaseSelect?.value || '').trim().toUpperCase();
|
||||
renderHiddenInputs();
|
||||
});
|
||||
|
||||
renderAll();
|
||||
})();
|
||||
@@ -54,6 +54,19 @@
|
||||
border-color: #1c2734;
|
||||
}
|
||||
|
||||
.fx-button--ghost {
|
||||
background: #fff4fb;
|
||||
border-color: #f5b7d7;
|
||||
color: #ff8a00;
|
||||
}
|
||||
|
||||
.fx-button--accent {
|
||||
background: linear-gradient(90deg, #ff006a 0%, #ff9e00 100%);
|
||||
color: #111827;
|
||||
border-color: transparent;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.fx-button[disabled] {
|
||||
opacity: 0.6;
|
||||
cursor: wait;
|
||||
@@ -139,6 +152,122 @@
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.fx-action-row {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.fx-action-row form {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.fx-mini-grid {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
margin: 1rem 0 1.25rem;
|
||||
}
|
||||
|
||||
.fx-mini-card {
|
||||
display: grid;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
|
||||
.fx-mini-label,
|
||||
.fx-field-label {
|
||||
font-size: 0.9rem;
|
||||
color: #6a7383;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.fx-currency-selection-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.fx-token-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.fx-token-list--inline {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.fx-currency-search {
|
||||
flex: 0 1 360px;
|
||||
min-width: 260px;
|
||||
}
|
||||
|
||||
.fx-input,
|
||||
.fx-select {
|
||||
width: 100%;
|
||||
border: 1px solid #d0d7e2;
|
||||
border-radius: 18px;
|
||||
padding: 0.8rem 1rem;
|
||||
background: #fff;
|
||||
color: #1c2734;
|
||||
}
|
||||
|
||||
.fx-field {
|
||||
display: grid;
|
||||
gap: 0.45rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.fx-token,
|
||||
.fx-suggestion {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
border: 1px solid #d0d7e2;
|
||||
border-radius: 999px;
|
||||
padding: 0.7rem 1rem;
|
||||
background: #fff;
|
||||
color: #1c2734;
|
||||
}
|
||||
|
||||
.fx-token {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.fx-token:hover,
|
||||
.fx-suggestion:hover {
|
||||
border-color: rgba(255, 158, 0, 0.45);
|
||||
background: rgba(255, 244, 251, 0.9);
|
||||
}
|
||||
|
||||
.fx-token-close {
|
||||
color: #ff9e00;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.fx-suggestion-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
margin-top: 0.9rem;
|
||||
}
|
||||
|
||||
.fx-suggestion {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.fx-suggestion strong {
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.fx-text {
|
||||
color: #5b6573;
|
||||
}
|
||||
|
||||
.fx-history-date {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -161,3 +290,15 @@
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 860px) {
|
||||
.fx-currency-selection-row {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.fx-currency-search {
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user