Tax excpetion texts
This commit is contained in:
@@ -14,12 +14,14 @@ use App\Scopes\CommonModel;
|
||||
* @property string $name
|
||||
* @property string $invoice_text
|
||||
* @property bool $requires_note
|
||||
* @property int $sort_order
|
||||
*/
|
||||
class TaxExemptionReason extends CommonModel
|
||||
{
|
||||
public const string SMALL_BUSINESS = 'small_business';
|
||||
public const string EDUCATION = 'education';
|
||||
public const string YOUTH_WELFARE = 'youth_welfare';
|
||||
public const string USTG_19 = 'ustg_19';
|
||||
public const string USTG_4_24 = 'ustg_4_24';
|
||||
public const string USTG_4_25A = 'ustg_4_25a';
|
||||
public const string USTG_4_22A = 'ustg_4_22a';
|
||||
public const string CUSTOM = 'custom';
|
||||
|
||||
protected $table = 'tax_exemption_reasons';
|
||||
@@ -32,10 +34,12 @@ class TaxExemptionReason extends CommonModel
|
||||
'name',
|
||||
'invoice_text',
|
||||
'requires_note',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'requires_note' => 'boolean',
|
||||
'sort_order' => 'integer',
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -55,7 +59,7 @@ class TaxExemptionReason extends CommonModel
|
||||
*/
|
||||
public static function options(): array
|
||||
{
|
||||
return self::all()
|
||||
return self::orderBy('sort_order')->get()
|
||||
->map(static fn (self $reason): array => [
|
||||
'value' => $reason->slug,
|
||||
'label' => $reason->name,
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
<script setup>
|
||||
import {computed, onBeforeUnmount, onMounted, ref} from 'vue'
|
||||
import {computed, nextTick, onBeforeUnmount, onMounted, ref} from 'vue'
|
||||
import Icon from './Icon.vue'
|
||||
|
||||
// Generisches Custom-Dropdown, das je Option ein Icon + Label anzeigt (ein natives <select> kann
|
||||
// keine SVG-Icons rendern). Wiederverwendbar für Icon-/Rich-Selects.
|
||||
// keine SVG-Icons rendern). Die Options-Liste wird per <Teleport> an <body> gehängt und fixed
|
||||
// positioniert, damit sie nicht von einem Eltern-Container mit overflow (z. B. .tab-content in
|
||||
// TabbedPage) abgeschnitten wird. Wiederverwendbar für Icon-/Rich-Selects.
|
||||
const props = defineProps({
|
||||
// Ausgewählter Wert (value einer Option).
|
||||
modelValue: {type: String, default: ''},
|
||||
@@ -15,11 +17,23 @@ const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const open = ref(false)
|
||||
const root = ref(null)
|
||||
const listRef = ref(null)
|
||||
const pos = ref({top: 0, left: 0, width: 0})
|
||||
|
||||
const selected = computed(() => props.options.find(o => o.value === props.modelValue) ?? null)
|
||||
|
||||
function toggle() {
|
||||
function updatePosition() {
|
||||
if (!root.value) return
|
||||
const rect = root.value.getBoundingClientRect()
|
||||
pos.value = {top: rect.bottom + 4, left: rect.left, width: rect.width}
|
||||
}
|
||||
|
||||
async function toggle() {
|
||||
open.value = !open.value
|
||||
if (open.value) {
|
||||
await nextTick()
|
||||
updatePosition()
|
||||
}
|
||||
}
|
||||
|
||||
function select(option) {
|
||||
@@ -28,7 +42,9 @@ function select(option) {
|
||||
}
|
||||
|
||||
function onClickOutside(event) {
|
||||
if (root.value && !root.value.contains(event.target)) {
|
||||
const inRoot = root.value && root.value.contains(event.target)
|
||||
const inList = listRef.value && listRef.value.contains(event.target)
|
||||
if (!inRoot && !inList) {
|
||||
open.value = false
|
||||
}
|
||||
}
|
||||
@@ -39,13 +55,22 @@ function onKeydown(event) {
|
||||
}
|
||||
}
|
||||
|
||||
function onReposition() {
|
||||
if (open.value) updatePosition()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', onClickOutside)
|
||||
document.addEventListener('keydown', onKeydown)
|
||||
window.addEventListener('resize', onReposition)
|
||||
// capture=true, damit auch Scrollen in inneren Containern die Position aktualisiert
|
||||
window.addEventListener('scroll', onReposition, true)
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('click', onClickOutside)
|
||||
document.removeEventListener('keydown', onKeydown)
|
||||
window.removeEventListener('resize', onReposition)
|
||||
window.removeEventListener('scroll', onReposition, true)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -67,20 +92,28 @@ onBeforeUnmount(() => {
|
||||
<span class="rich-select__caret" aria-hidden="true">▾</span>
|
||||
</button>
|
||||
|
||||
<ul v-if="open" class="rich-select__list" role="listbox">
|
||||
<li
|
||||
v-for="option in options"
|
||||
:key="option.value"
|
||||
class="rich-select__option"
|
||||
:class="{ 'rich-select__option--active': option.value === modelValue }"
|
||||
role="option"
|
||||
:aria-selected="option.value === modelValue"
|
||||
@click="select(option)"
|
||||
<Teleport to="body">
|
||||
<ul
|
||||
v-if="open"
|
||||
ref="listRef"
|
||||
class="rich-select__list"
|
||||
role="listbox"
|
||||
:style="{ top: pos.top + 'px', left: pos.left + 'px', width: pos.width + 'px' }"
|
||||
>
|
||||
<Icon v-if="option.icon" :name="option.icon"/>
|
||||
<span>{{ option.label }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<li
|
||||
v-for="option in options"
|
||||
:key="option.value"
|
||||
class="rich-select__option"
|
||||
:class="{ 'rich-select__option--active': option.value === modelValue }"
|
||||
role="option"
|
||||
:aria-selected="option.value === modelValue"
|
||||
@click="select(option)"
|
||||
>
|
||||
<Icon v-if="option.icon" :name="option.icon"/>
|
||||
<span>{{ option.label }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -126,11 +159,8 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.rich-select__list {
|
||||
position: absolute;
|
||||
z-index: 50;
|
||||
top: calc(100% + 4px);
|
||||
left: 0;
|
||||
right: 0;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
margin: 0;
|
||||
padding: 4px;
|
||||
list-style: none;
|
||||
@@ -140,6 +170,7 @@ onBeforeUnmount(() => {
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
|
||||
max-height: 260px;
|
||||
overflow: auto;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.rich-select__option {
|
||||
|
||||
Reference in New Issue
Block a user