mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-04-15 09:14:08 +00:00
customers, items, invoices, estimates, shared document form 77 files, 14451 lines. Typed layouts (CompanyLayout, AuthLayout, header, sidebar, company switcher), auth views (login, register, forgot/reset password), admin feature (dashboard, companies, users, settings with typed store), company features (dashboard with chart/ stats, customers CRUD, items CRUD, invoices CRUD with full store, estimates CRUD with full store), and shared document form components (items table, item row, totals, notes, tax popup, template select, exchange rate converter, calculation composable). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
152 lines
4.9 KiB
Vue
152 lines
4.9 KiB
Vue
<template>
|
|
<div class="rounded-xl border border-line-light shadow overflow-hidden bg-surface">
|
|
<!-- Tax Included Toggle -->
|
|
<div
|
|
v-if="taxIncludedSetting === 'YES'"
|
|
class="flex items-center justify-end w-full px-6 text-base border-b border-line-light cursor-pointer text-primary-400 bg-surface"
|
|
>
|
|
<BaseSwitchSection
|
|
v-model="taxIncludedField"
|
|
:title="$t('settings.tax_types.tax_included')"
|
|
:store="store"
|
|
:store-prop="storeProp"
|
|
/>
|
|
</div>
|
|
|
|
<table class="text-center item-table min-w-full">
|
|
<colgroup>
|
|
<col style="width: 40%; min-width: 280px" />
|
|
<col style="width: 10%; min-width: 120px" />
|
|
<col style="width: 15%; min-width: 120px" />
|
|
<col
|
|
v-if="formData.discount_per_item === 'YES'"
|
|
style="width: 15%; min-width: 160px"
|
|
/>
|
|
<col style="width: 15%; min-width: 120px" />
|
|
</colgroup>
|
|
|
|
<thead class="bg-surface-secondary border-b border-line-light">
|
|
<tr>
|
|
<th class="px-5 py-3 text-sm not-italic font-medium leading-5 text-left text-body">
|
|
<BaseContentPlaceholders v-if="isLoading">
|
|
<BaseContentPlaceholdersText :lines="1" class="w-16 h-5" />
|
|
</BaseContentPlaceholders>
|
|
<span v-else class="pl-7">
|
|
{{ $t('items.item', 2) }}
|
|
</span>
|
|
</th>
|
|
<th class="px-5 py-3 text-sm not-italic font-medium leading-5 text-right text-body">
|
|
<BaseContentPlaceholders v-if="isLoading">
|
|
<BaseContentPlaceholdersText :lines="1" class="w-16 h-5" />
|
|
</BaseContentPlaceholders>
|
|
<span v-else>
|
|
{{ $t('invoices.item.quantity') }}
|
|
</span>
|
|
</th>
|
|
<th class="px-5 py-3 text-sm not-italic font-medium leading-5 text-left text-body">
|
|
<BaseContentPlaceholders v-if="isLoading">
|
|
<BaseContentPlaceholdersText :lines="1" class="w-16 h-5" />
|
|
</BaseContentPlaceholders>
|
|
<span v-else>
|
|
{{ $t('invoices.item.price') }}
|
|
</span>
|
|
</th>
|
|
<th
|
|
v-if="formData.discount_per_item === 'YES'"
|
|
class="px-5 py-3 text-sm not-italic font-medium leading-5 text-left text-body"
|
|
>
|
|
<BaseContentPlaceholders v-if="isLoading">
|
|
<BaseContentPlaceholdersText :lines="1" class="w-16 h-5" />
|
|
</BaseContentPlaceholders>
|
|
<span v-else>
|
|
{{ $t('invoices.item.discount') }}
|
|
</span>
|
|
</th>
|
|
<th class="px-5 py-3 text-sm not-italic font-medium leading-5 text-right text-body">
|
|
<BaseContentPlaceholders v-if="isLoading">
|
|
<BaseContentPlaceholdersText :lines="1" class="w-16 h-5" />
|
|
</BaseContentPlaceholders>
|
|
<span v-else class="pr-10 column-heading">
|
|
{{ $t('invoices.item.amount') }}
|
|
</span>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
|
|
<draggable
|
|
v-model="formData.items"
|
|
item-key="id"
|
|
tag="tbody"
|
|
handle=".handle"
|
|
>
|
|
<template #item="{ element, index }">
|
|
<DocumentItemRow
|
|
:key="element.id"
|
|
:index="index"
|
|
:item-data="element"
|
|
:loading="isLoading"
|
|
:currency="defaultCurrency"
|
|
:item-validation-scope="itemValidationScope"
|
|
:invoice-items="formData.items"
|
|
:store="store"
|
|
:store-prop="storeProp"
|
|
/>
|
|
</template>
|
|
</draggable>
|
|
</table>
|
|
|
|
<div
|
|
class="flex items-center justify-center w-full px-6 py-3 text-base border-t border-line-light cursor-pointer text-primary-400 hover:bg-primary-100"
|
|
@click="store.addItem()"
|
|
>
|
|
<BaseIcon name="PlusCircleIcon" class="mr-2" />
|
|
{{ $t('general.add_new_item') }}
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed } from 'vue'
|
|
import draggable from 'vuedraggable'
|
|
import DocumentItemRow from './DocumentItemRow.vue'
|
|
import type { Currency } from '../../../types/domain/currency'
|
|
import type { DocumentFormData } from './use-document-calculations'
|
|
|
|
interface Props {
|
|
store: Record<string, unknown> & {
|
|
addItem: () => void
|
|
}
|
|
storeProp: string
|
|
currency: Currency | Record<string, unknown> | string | null
|
|
isLoading?: boolean
|
|
itemValidationScope?: string
|
|
taxIncludedSetting?: string
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
isLoading: false,
|
|
itemValidationScope: '',
|
|
taxIncludedSetting: 'NO',
|
|
})
|
|
|
|
const formData = computed<DocumentFormData>(() => {
|
|
return props.store[props.storeProp] as DocumentFormData
|
|
})
|
|
|
|
const defaultCurrency = computed(() => {
|
|
if (props.currency) {
|
|
return props.currency
|
|
}
|
|
return null
|
|
})
|
|
|
|
const taxIncludedField = computed<boolean>({
|
|
get: () => {
|
|
return !!formData.value.tax_included
|
|
},
|
|
set: (value: boolean) => {
|
|
formData.value.tax_included = value
|
|
},
|
|
})
|
|
</script>
|