mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-04-15 09:14:08 +00:00
Finalize Typescript restructure
This commit is contained in:
@@ -186,6 +186,7 @@ import { required, between, maxLength, helpers, minValue } from '@vuelidate/vali
|
||||
import useVuelidate from '@vuelidate/core'
|
||||
import DocumentItemRowTax from './DocumentItemRowTax.vue'
|
||||
import DragIcon from '@v2/components/icons/DragIcon.vue'
|
||||
import { generateClientId } from '../../../utils'
|
||||
import type { Currency } from '../../../types/domain/currency'
|
||||
import type { DocumentItem, DocumentFormData, DocumentTax } from './use-document-calculations'
|
||||
|
||||
@@ -329,7 +330,7 @@ function updateTax(data: { index: number; item: DocumentTax }): void {
|
||||
props.store.$patch((state: Record<string, unknown>) => {
|
||||
const form = state[props.storeProp] as DocumentFormData
|
||||
form.items[props.index].taxes!.push({
|
||||
id: crypto.randomUUID(),
|
||||
id: generateClientId(),
|
||||
tax_type_id: 0,
|
||||
name: '',
|
||||
amount: 0,
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, reactive, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useModalStore } from '../../../stores/modal.store'
|
||||
import type { TaxType } from '../../../types/domain/tax'
|
||||
import type { Currency } from '../../../types/domain/currency'
|
||||
import type { DocumentFormData, DocumentTax } from './use-document-calculations'
|
||||
@@ -106,6 +107,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const modalStore = useModalStore()
|
||||
|
||||
// We assume these stores are available globally or injected
|
||||
// In the v2 arch, we'll use a lighter approach
|
||||
@@ -205,11 +207,7 @@ function updateRowTax(): void {
|
||||
}
|
||||
|
||||
function openTaxModal(): void {
|
||||
// Modal integration - emit event or use modal store
|
||||
const modalStore = (window as Record<string, unknown>).__modalStore as
|
||||
| { openModal: (opts: Record<string, unknown>) => void }
|
||||
| undefined
|
||||
modalStore?.openModal({
|
||||
modalStore.openModal({
|
||||
title: t('settings.tax_types.add_tax'),
|
||||
componentName: 'TaxTypeModal',
|
||||
data: { itemIndex: props.itemIndex, taxIndex: props.index },
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="rounded-xl border border-line-light shadow overflow-hidden bg-surface">
|
||||
<div class="rounded-xl border border-line-light shadow bg-surface">
|
||||
<!-- Tax Included Toggle -->
|
||||
<div
|
||||
v-if="taxIncludedSetting === 'YES'"
|
||||
|
||||
@@ -223,6 +223,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import TaxSelectPopup from './TaxSelectPopup.vue'
|
||||
import { generateClientId } from '../../../utils'
|
||||
import type { Currency } from '../../../types/domain/currency'
|
||||
import type { TaxType } from '../../../types/domain/tax'
|
||||
import type { DocumentFormData, DocumentTax, DocumentStore, DocumentItem } from './use-document-calculations'
|
||||
@@ -357,7 +358,7 @@ function onSelectTax(selectedTax: TaxType): void {
|
||||
}
|
||||
|
||||
const data: DocumentTax = {
|
||||
id: crypto.randomUUID(),
|
||||
id: generateClientId(),
|
||||
name: selectedTax.name,
|
||||
percent: selectedTax.percent,
|
||||
tax_type_id: selectedTax.id,
|
||||
|
||||
@@ -51,6 +51,8 @@
|
||||
<script setup lang="ts">
|
||||
import { watch, computed, ref, onBeforeUnmount } from 'vue'
|
||||
import { exchangeRateService } from '../../../api/services/exchange-rate.service'
|
||||
import { useCompanyStore } from '../../../stores/company.store'
|
||||
import { useGlobalStore } from '../../../stores/global.store'
|
||||
import type { Currency } from '../../../types/domain/currency'
|
||||
import type { DocumentFormData } from './use-document-calculations'
|
||||
|
||||
@@ -63,18 +65,17 @@ interface Props {
|
||||
storeProp: string
|
||||
isEdit?: boolean
|
||||
customerCurrency?: number | string | null
|
||||
companyCurrency?: Currency | null
|
||||
currencies?: Currency[]
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
isLoading: false,
|
||||
isEdit: false,
|
||||
customerCurrency: null,
|
||||
companyCurrency: null,
|
||||
currencies: () => [],
|
||||
})
|
||||
|
||||
const companyStore = useCompanyStore()
|
||||
const globalStore = useGlobalStore()
|
||||
|
||||
const hasActiveProvider = ref<boolean>(false)
|
||||
const isFetching = ref<boolean>(false)
|
||||
|
||||
@@ -93,14 +94,18 @@ const exchangeRate = computed<number | null | string>({
|
||||
},
|
||||
})
|
||||
|
||||
const companyCurrency = computed<Currency | null>(() => {
|
||||
return companyStore.selectedCompanyCurrency
|
||||
})
|
||||
|
||||
const selectedCurrency = computed<Currency | null>(() => {
|
||||
return (
|
||||
props.currencies.find((c) => c.id === formData.value.currency_id) ?? null
|
||||
globalStore.currencies.find((c: Currency) => c.id === formData.value.currency_id) ?? null
|
||||
)
|
||||
})
|
||||
|
||||
const isCurrencyDifferent = computed<boolean>(() => {
|
||||
return props.companyCurrency?.id !== props.customerCurrency
|
||||
return companyCurrency.value?.id !== props.customerCurrency
|
||||
})
|
||||
|
||||
watch(
|
||||
@@ -150,13 +155,13 @@ function setCustomerCurrency(v: Record<string, unknown> | null): void {
|
||||
if (currency) {
|
||||
formData.value.currency_id = currency.id
|
||||
}
|
||||
} else if (props.companyCurrency) {
|
||||
formData.value.currency_id = props.companyCurrency.id
|
||||
} else if (companyCurrency.value) {
|
||||
formData.value.currency_id = companyCurrency.value.id
|
||||
}
|
||||
}
|
||||
|
||||
async function onChangeCurrency(v: number | undefined): Promise<void> {
|
||||
if (v !== props.companyCurrency?.id) {
|
||||
if (v !== companyCurrency.value?.id) {
|
||||
if (!props.isEdit && v) {
|
||||
await getCurrentExchangeRate(v)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<div>
|
||||
<NoteModal />
|
||||
<div class="w-full">
|
||||
<Popover v-slot="{ open: isOpen }">
|
||||
<PopoverButton
|
||||
v-if="canViewNotes"
|
||||
@@ -77,6 +79,7 @@
|
||||
</PopoverPanel>
|
||||
</transition>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -84,13 +87,15 @@
|
||||
import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useModalStore } from '../../../stores/modal.store'
|
||||
import { useUserStore } from '../../../stores/user.store'
|
||||
import { ABILITIES } from '../../../config/abilities'
|
||||
import NoteModal from '../../company/settings/components/NoteModal.vue'
|
||||
import type { Note } from '../../../types/domain/note'
|
||||
import { noteService } from '../../../api/services/note.service'
|
||||
|
||||
interface Props {
|
||||
type?: string | null
|
||||
canViewNotes?: boolean
|
||||
canManageNotes?: boolean
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
@@ -99,16 +104,24 @@ interface Emits {
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
type: null,
|
||||
canViewNotes: true,
|
||||
canManageNotes: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const modalStore = useModalStore()
|
||||
const userStore = useUserStore()
|
||||
const textSearch = ref<string | null>(null)
|
||||
const notes = ref<Note[]>([])
|
||||
|
||||
const canViewNotes = computed<boolean>(() =>
|
||||
userStore.hasAbilities(ABILITIES.VIEW_NOTE),
|
||||
)
|
||||
|
||||
const canManageNotes = computed<boolean>(() =>
|
||||
userStore.hasAbilities(ABILITIES.MANAGE_NOTE),
|
||||
)
|
||||
|
||||
const filteredNotes = computed<Note[]>(() => {
|
||||
if (textSearch.value) {
|
||||
return notes.value.filter((el) =>
|
||||
@@ -138,14 +151,12 @@ function selectNote(index: number, close: () => void): void {
|
||||
}
|
||||
|
||||
function openNoteModal(): void {
|
||||
const modalStore = (window as Record<string, unknown>).__modalStore as
|
||||
| { openModal: (opts: Record<string, unknown>) => void }
|
||||
| undefined
|
||||
modalStore?.openModal({
|
||||
modalStore.openModal({
|
||||
title: t('settings.customization.notes.add_note'),
|
||||
componentName: 'NoteModal',
|
||||
size: 'lg',
|
||||
data: props.type,
|
||||
refreshData: () => fetchInitialData(),
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useModalStore } from '@v2/stores/modal.store'
|
||||
import { useUserStore } from '@v2/stores/user.store'
|
||||
|
||||
interface ModalData {
|
||||
templates: Array<{ name: string; path?: string }>
|
||||
store: { setTemplate: (name: string) => void; isEdit?: boolean; [key: string]: unknown }
|
||||
storeProp: string
|
||||
isMarkAsDefault: boolean
|
||||
markAsDefaultDescription: string
|
||||
}
|
||||
|
||||
const modalStore = useModalStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const selectedTemplate = ref<string>('')
|
||||
|
||||
const modalActive = computed<boolean>(
|
||||
() => modalStore.active && modalStore.componentName === 'SelectTemplate',
|
||||
)
|
||||
|
||||
const modalData = computed<ModalData | null>(() => {
|
||||
return modalStore.data as ModalData | null
|
||||
})
|
||||
|
||||
function setData(): void {
|
||||
if (!modalData.value) return
|
||||
|
||||
const currentName =
|
||||
(modalData.value.store[modalData.value.storeProp] as Record<string, unknown>)
|
||||
?.template_name as string | undefined
|
||||
|
||||
if (currentName) {
|
||||
selectedTemplate.value = currentName
|
||||
} else if (modalData.value.templates.length) {
|
||||
selectedTemplate.value = modalData.value.templates[0].name
|
||||
}
|
||||
}
|
||||
|
||||
async function chooseTemplate(): Promise<void> {
|
||||
if (!modalData.value) return
|
||||
|
||||
modalData.value.store.setTemplate(selectedTemplate.value)
|
||||
|
||||
if (!modalData.value.store.isEdit && modalData.value.isMarkAsDefault) {
|
||||
if (modalData.value.storeProp === 'newEstimate') {
|
||||
await userStore.updateUserSettings({
|
||||
settings: {
|
||||
default_estimate_template: selectedTemplate.value,
|
||||
},
|
||||
})
|
||||
} else if (modalData.value.storeProp === 'newInvoice') {
|
||||
await userStore.updateUserSettings({
|
||||
settings: {
|
||||
default_invoice_template: selectedTemplate.value,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
closeModal()
|
||||
}
|
||||
|
||||
function getTickImage(): string {
|
||||
return new URL('$images/tick.png', import.meta.url).href
|
||||
}
|
||||
|
||||
function closeModal(): void {
|
||||
modalStore.closeModal()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseModal :show="modalActive" @close="closeModal" @open="setData">
|
||||
<template #header>
|
||||
<div class="flex justify-between w-full">
|
||||
{{ modalStore.title }}
|
||||
<BaseIcon
|
||||
name="XMarkIcon"
|
||||
class="h-6 w-6 text-muted cursor-pointer"
|
||||
@click="closeModal"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="px-8 py-8 sm:p-6">
|
||||
<div
|
||||
v-if="modalData"
|
||||
class="grid grid-cols-3 gap-2 p-1 overflow-x-auto"
|
||||
>
|
||||
<div
|
||||
v-for="(template, index) in modalData.templates"
|
||||
:key="index"
|
||||
:class="{
|
||||
'border border-solid border-primary-500':
|
||||
selectedTemplate === template.name,
|
||||
}"
|
||||
class="
|
||||
relative
|
||||
flex flex-col
|
||||
m-2
|
||||
border border-line-default border-solid
|
||||
cursor-pointer
|
||||
hover:border-primary-300
|
||||
"
|
||||
@click="selectedTemplate = template.name"
|
||||
>
|
||||
<img
|
||||
:src="template.path"
|
||||
:alt="template.name"
|
||||
class="w-full min-h-[100px]"
|
||||
/>
|
||||
<img
|
||||
v-if="selectedTemplate === template.name"
|
||||
:alt="template.name"
|
||||
class="absolute z-10 w-5 h-5 text-primary-500"
|
||||
style="top: -6px; right: -5px"
|
||||
:src="getTickImage()"
|
||||
/>
|
||||
<span
|
||||
:class="[
|
||||
'w-full p-1 bg-surface-muted text-sm text-center absolute bottom-0 left-0',
|
||||
{
|
||||
'text-primary-500 bg-primary-100':
|
||||
selectedTemplate === template.name,
|
||||
'text-body': selectedTemplate !== template.name,
|
||||
},
|
||||
]"
|
||||
>
|
||||
{{ template.name }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="modalData && !modalData.store.isEdit"
|
||||
class="z-0 flex ml-3 pt-5"
|
||||
>
|
||||
<BaseCheckbox
|
||||
v-model="modalData.isMarkAsDefault"
|
||||
:set-initial-value="false"
|
||||
variant="primary"
|
||||
:label="$t('general.mark_as_default')"
|
||||
:description="modalData.markAsDefaultDescription"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="z-0 flex justify-end p-4 border-t border-line-default border-solid"
|
||||
>
|
||||
<BaseButton class="mr-3" variant="primary-outline" @click="closeModal">
|
||||
{{ $t('general.cancel') }}
|
||||
</BaseButton>
|
||||
<BaseButton variant="primary" @click="chooseTemplate">
|
||||
<template #left="slotProps">
|
||||
<BaseIcon name="ArrowDownOnSquareIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
{{ $t('general.choose') }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
</BaseModal>
|
||||
</template>
|
||||
@@ -103,6 +103,7 @@
|
||||
import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useModalStore } from '../../../stores/modal.store'
|
||||
import type { TaxType } from '../../../types/domain/tax'
|
||||
import type { Currency } from '../../../types/domain/currency'
|
||||
import type { DocumentFormData, DocumentTax } from './use-document-calculations'
|
||||
@@ -130,6 +131,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const modalStore = useModalStore()
|
||||
const textSearch = ref<string | null>(null)
|
||||
|
||||
const formData = computed<DocumentFormData>(() => {
|
||||
@@ -159,10 +161,7 @@ function selectTaxType(data: TaxType, close: () => void): void {
|
||||
}
|
||||
|
||||
function openTaxTypeModal(): void {
|
||||
const modalStore = (window as Record<string, unknown>).__modalStore as
|
||||
| { openModal: (opts: Record<string, unknown>) => void }
|
||||
| undefined
|
||||
modalStore?.openModal({
|
||||
modalStore.openModal({
|
||||
title: t('settings.tax_types.add_tax'),
|
||||
componentName: 'TaxTypeModal',
|
||||
size: 'sm',
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useModalStore } from '@v2/stores/modal.store'
|
||||
import type { DocumentFormData } from './use-document-calculations'
|
||||
|
||||
interface Props {
|
||||
@@ -36,6 +37,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const modalStore = useModalStore()
|
||||
|
||||
const formData = computed<DocumentFormData>(() => {
|
||||
return props.store[props.storeProp] as DocumentFormData
|
||||
@@ -53,10 +55,7 @@ function openTemplateModal(): void {
|
||||
markAsDefaultDescription = t('invoices.mark_as_default_invoice_template_description')
|
||||
}
|
||||
|
||||
const modalStore = (window as Record<string, unknown>).__modalStore as
|
||||
| { openModal: (opts: Record<string, unknown>) => void }
|
||||
| undefined
|
||||
modalStore?.openModal({
|
||||
modalStore.openModal({
|
||||
title: t('general.choose_template'),
|
||||
componentName: 'SelectTemplate',
|
||||
data: {
|
||||
|
||||
@@ -6,6 +6,7 @@ export { default as DocumentNotes } from './DocumentNotes.vue'
|
||||
export { default as TaxSelectPopup } from './TaxSelectPopup.vue'
|
||||
export { default as NoteSelectPopup } from './NoteSelectPopup.vue'
|
||||
export { default as TemplateSelectButton } from './TemplateSelectButton.vue'
|
||||
export { default as SelectTemplateModal } from './SelectTemplateModal.vue'
|
||||
export { default as ExchangeRateConverter } from './ExchangeRateConverter.vue'
|
||||
|
||||
export {
|
||||
|
||||
Reference in New Issue
Block a user