radius-v2-fix

This commit is contained in:
Luca Haid
2025-10-13 12:04:39 +02:00
parent b5acbd9bea
commit 41a8877ca7
4 changed files with 325 additions and 184 deletions
+3 -14
View File
@@ -164,19 +164,8 @@ Vue.component('radius-online-state', {
/* ---------- Autocomplete ---------- */
Vue.component('radius-autocomplete', {
props: { value: String, placeholder: String, wide: { type: Boolean, default: true } }, data() { return { q: this.value || '', open: false, items: {}, highlighted: -1, busy: false, mode: 'autocomplete', logoDropdownOpen: false }; }, watch: { value(v){ if(v!==this.q) this.q=v; } },
template: `
<div class="radius-scope ac-root" :data-wide="wide ? '1' : null" @keydown.down.prevent="mode === 'autocomplete' && move(1)" @keydown.up.prevent="mode === 'autocomplete' && move(-1)" @keydown.enter.prevent="onEnter">
<span class="ac-focus-tooltip">Klicken Sie auf das Logo, um die Kundenbasis zu wechseln</span>
<div class="input-wrap">
<div class="logo-switcher" @mousedown.prevent.stop="toggleLogoDropdown" :class="{'is-open': logoDropdownOpen}"><img v-if="mode === 'autocomplete'" src="/img/xinon-logo.png" class="input-icon-logo" alt="Xinon Logo"><img v-else src="/img/estmk_logo.png" class="input-icon-logo" alt="ESTMK Logo"><i class="fa-solid fa-chevron-down switcher-caret"></i></div>
<input ref="mainInput" :placeholder="placeholderText" class="ri" v-model="q" autocomplete="off" autocapitalize="none" autocorrect="off" @input="onInput" @focus="mode === 'autocomplete' && maybeOpen()" @blur="deferClose"/>
<button v-if="q" class="btn-clear" @mousedown.prevent="clear" title="Feld leeren"><i class="fa-duotone fa-xmark"></i></button>
</div>
<transition name="ac-pop"><div v-if="logoDropdownOpen" class="logo-dropdown"><div class="logo-option" @mousedown.prevent="selectMode('autocomplete')"><img src="/img/xinon-logo.png" alt="Xinon Logo"><span>XINON (Suche)</span></div><div class="logo-option" @mousedown.prevent="selectMode('text')"><img src="/img/estmk_logo.png" alt="ESTMK Logo"><span>ESTMK (Eingabe)</span></div></div></transition>
<transition name="ac-pop"><div v-if="open && mode === 'autocomplete'" class="ac-panel" :class="{'wide': wide}"><div v-if="busy" class="ac-skel"><div class="skeleton-line"></div><div class="skeleton-line"></div><div class="skeleton-line"></div></div><template v-else><div v-if="!Object.keys(items).length" class="ac-empty muted">Keine Treffer</div><ul ref="resultsList" class="ac-list" role="listbox"><li v-for="(disp, id) in items" :key="id" :class="['ac-item', highlightedId===id ? 'is-active' : '']" @mousedown.prevent="choose(id, disp)"><i class="fa-duotone fa-address-card"></i><span class="txt">{{ disp }}</span></li></ul></template></div></transition>
</div>
`,
props: { value: String, placeholder: String, wide: { type: Boolean, default: true } }, data() { return { q: this.value || '', open: false, items: {}, highlighted: -1, busy: false, mode: 'autocomplete', logoDropdownOpen: false, hasMoreResults: false }; }, watch: { value(v){ if (v !== this.q) { this.q = v; if (this.mode === 'autocomplete') this.debouncedFetch(); } } },
template: `<div class="radius-scope ac-root" :data-wide="wide ? '1' : null" @keydown.down.prevent="mode === 'autocomplete' && move(1)" @keydown.up.prevent="mode === 'autocomplete' && move(-1)" @keydown.enter.prevent="onEnter"><span class="ac-focus-tooltip">Klicken Sie auf das Logo, um die Kundenbasis zu wechseln</span><div class="input-wrap"><div class="logo-switcher" @mousedown.prevent.stop="toggleLogoDropdown" :class="{'is-open': logoDropdownOpen}"><img v-if="mode === 'autocomplete'" src="/img/xinon-logo.png" class="input-icon-logo" alt="Xinon Logo"><img v-else src="/img/estmk_logo.png" class="input-icon-logo" alt="ESTMK Logo"><i class="fa-solid fa-chevron-down switcher-caret"></i></div><input ref="mainInput" :placeholder="placeholderText" class="ri" v-model="q" autocomplete="off" autocapitalize="none" autocorrect="off" @input="onInput" @focus="mode === 'autocomplete' && maybeOpen()" @blur="deferClose"/><button v-if="q" class="btn-clear" @mousedown.prevent="clear" title="Feld leeren"><i class="fa-duotone fa-xmark"></i></button></div><transition name="ac-pop"><div v-if="logoDropdownOpen" class="logo-dropdown"><div class="logo-option" @mousedown.prevent="selectMode('autocomplete')"><img src="/img/xinon-logo.png" alt="Xinon Logo"><span>XINON (Suche)</span></div><div class="logo-option" @mousedown.prevent="selectMode('text')"><img src="/img/estmk_logo.png" alt="ESTMK Logo"><span>ESTMK (Eingabe)</span></div></div></transition><transition name="ac-pop"><div v-if="open && mode === 'autocomplete'" class="ac-panel" :class="{'wide': wide}"><div v-if="busy" class="ac-skel"><div class="skeleton-line"></div><div class="skeleton-line"></div><div class="skeleton-line"></div></div><template v-else><div v-if="!Object.keys(items).length && !hasMoreResults" class="ac-empty muted">Keine Treffer</div><ul ref="resultsList" class="ac-list" role="listbox"><li v-for="(disp, id) in items" :key="id" :class="['ac-item', highlightedId===id ? 'is-active' : '']" @mousedown.prevent="choose(id, disp)"><i class="fa-duotone fa-address-card"></i><span class="txt">{{ disp }}</span></li><li v-if="hasMoreResults" class="ac-more-info muted"><i class="fa-duotone fa-ellipsis"></i><span class="txt">Mehr Ergebnisse verfügbar</span></li></ul></template></div></transition></div>`,
computed: { highlightedId(){ const k=Object.keys(this.items); return k[this.highlighted] || null; }, placeholderText() { return this.mode === 'autocomplete' ? (this.placeholder || 'Rechnungsadresse suchen') : 'Partner-Kundennummer eingeben'; } },
created() { this.debouncedFetch = this.debounce(() => this.fetchItems(), 220); },
methods: {
@@ -191,7 +180,7 @@ Vue.component('radius-autocomplete', {
chooseHighlighted(e){ const i=this.highlightedId; if (i) this.choose(i, this.items[i], e); else if (e) this.$emit('enter'); },
choose(id, display, emitEnter){ const c=(display.match(/\[(\d+)\]/)||[])[1] || ''; this.emitSelection(c, display); this.open=false; if (emitEnter) this.$emit('enter'); },
emitSelection(custnum, display){ this.$emit('select', { custnum, display }); this.$emit('input', display); this.$emit('change', display); },
async fetchItems(){ if (this.mode!=='autocomplete'||!this.q||this.q.length<2){this.items={};return;} this.busy=true; try{ const b=window.TT_CONFIG.BASE_PATH||''; const r=await fetch(`${b}/Address/Api?do=findAddress&fibu_primary_account=1&q=${encodeURIComponent(this.q)}`); if(r.ok){const j=await r.json();this.items=(j?.result?.addresses)?j.result.addresses:{};this.highlighted=0;}else{this.items={};}}catch{this.items={};} this.busy=false; },
async fetchItems() { if (this.mode !== 'autocomplete' || !this.q || this.q.length < 2) { this.items = {}; this.hasMoreResults = false; return; } this.busy = true; try { const b = window.TT_CONFIG.BASE_PATH || ''; const r = await fetch(`${b}/Address/Api?do=findAddress&fibu_primary_account=1&q=${encodeURIComponent(this.q)}`); if (r.ok) { const j = await r.json(); const addresses = j?.result?.addresses || {}; if (addresses.more) { this.hasMoreResults = true; delete addresses.more; } else { this.hasMoreResults = false; } this.items = addresses; this.highlighted = 0; } else { this.items = {}; this.hasMoreResults = false; } } catch { this.items = {}; this.hasMoreResults = false; } this.busy = false; },
debounce(fn, ms){ let t; return (...a)=>{ clearTimeout(t); t=setTimeout(()=>fn(...a), ms); }; }
}
});