Files
InvoiceShelf/resources/scripts-v2/components/base/BaseItemSelect.vue
Darko Gjorgjijoski a46cca5cd8 Complete scripts-v2 TypeScript migration — all imports resolved,
build passes

Create all missing components (modals, dropdowns, icons, tabs, mail
drivers, customer partials), fix all @/scripts/ imports to @v2/,
wire up vite entry point and blade template. 382 files, 48883 lines.

- 27 settings components: modals (tax, payment, custom field, note,
  category, role, exchange rate, unit, mail test), dropdowns (6),
  customization tabs (4), mail driver forms (4)
- 22 icon components: 5 utility icons, 4 dashboard icons, 13 editor
  toolbar icons with typed barrel export
- 3 customer components: info, chart placeholder, custom fields single
- Fixed usePopper composable, client/format-money import patterns
- Zero remaining @/scripts/ imports in scripts-v2/

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 09:30:00 +02:00

159 lines
3.7 KiB
Vue

<script setup lang="ts">
import { computed, reactive, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { usePermissions } from '@v2/composables/use-permissions'
import { useModal } from '@v2/composables/use-modal'
import { ABILITIES } from '@v2/config/abilities'
import type { Item } from '@v2/types/domain'
import type { Tax } from '@v2/types/domain'
interface LineItem {
item_id: number | null
name: string
description: string | null
[key: string]: unknown
}
interface Props {
contentLoading?: boolean
type?: string | null
item: LineItem
index?: number
invalid?: boolean
invalidDescription?: boolean
taxPerItem?: string
taxes?: Tax[] | null
}
interface Emits {
(e: 'search', val: string): void
(e: 'select', val: Item): void
(e: 'deselect', index: number): void
(e: 'update:description', value: string): void
}
const props = withDefaults(defineProps<Props>(), {
contentLoading: false,
type: null,
index: 0,
invalid: false,
invalidDescription: false,
taxPerItem: '',
taxes: null,
})
const emit = defineEmits<Emits>()
const { hasAbility } = usePermissions()
const { openModal } = useModal()
const { t } = useI18n()
const itemSelect = ref<Item | null>(null)
const loading = ref<boolean>(false)
const itemData = reactive<LineItem>({ ...props.item })
const description = computed<string | null>({
get: () => props.item.description,
set: (value: string | null) => {
emit('update:description', value ?? '')
},
})
function openItemModal(): void {
openModal({
title: t('items.add_item'),
componentName: 'ItemModal',
refreshData: () => {},
data: {
taxPerItem: props.taxPerItem,
taxes: props.taxes,
itemIndex: props.index,
},
})
}
function deselectItem(index: number): void {
emit('deselect', index)
}
</script>
<template>
<div class="flex-1 text-sm">
<!-- Selected Item Field -->
<div
v-if="item.item_id"
class="
relative
flex
items-center
h-10
pl-2
bg-surface-muted
border border-line-default border-solid
rounded
"
>
{{ item.name }}
<span
class="absolute text-subtle cursor-pointer top-[8px] right-[10px]"
@click="deselectItem(index)"
>
<BaseIcon name="XCircleIcon" />
</span>
</div>
<!-- Select Item Field -->
<BaseMultiselect
v-else
v-model="itemSelect"
:content-loading="contentLoading"
value-prop="id"
track-by="name"
:invalid="invalid"
preserve-search
:initial-search="itemData.name"
label="name"
:filter-results="false"
resolve-on-load
:delay="500"
searchable
object
@update:modelValue="(val: Item) => $emit('select', val)"
@searchChange="(val: string) => $emit('search', val)"
>
<!-- Add Item Action -->
<template #action>
<BaseSelectAction
v-if="hasAbility(ABILITIES.CREATE_ITEM)"
@click="openItemModal"
>
<BaseIcon
name="PlusCircleIcon"
class="h-4 mr-2 -ml-2 text-center text-primary-400"
/>
{{ $t('general.add_new_item') }}
</BaseSelectAction>
</template>
</BaseMultiselect>
<!-- Item Description -->
<div class="w-full pt-1 text-xs text-light">
<BaseTextarea
v-model="description"
:content-loading="contentLoading"
:autosize="true"
class="text-xs"
:borderless="true"
:placeholder="$t('estimates.item.type_item_description')"
:invalid="invalidDescription"
/>
<div v-if="invalidDescription">
<span class="text-red-600">
{{ $t('validation.description_maxlength') }}
</span>
</div>
</div>
</div>
</template>