Finalize Typescript restructure

This commit is contained in:
Darko Gjorgjijoski
2026-04-06 17:59:15 +02:00
parent cab785172e
commit 74b4b2df4e
209 changed files with 12419 additions and 1745 deletions

View File

@@ -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,

View File

@@ -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 },

View File

@@ -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'"

View File

@@ -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,

View File

@@ -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)
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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',

View File

@@ -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: {

View File

@@ -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 {