mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-07-18 06:45:20 +00:00
@@ -12,14 +12,7 @@
|
||||
</BaseBreadcrumb>
|
||||
|
||||
<template #actions>
|
||||
<div class="flex items-center justify-end space-x-4">
|
||||
<BaseCsvExportButton
|
||||
v-show="customerStore.totalCustomers"
|
||||
v-if="userStore.hasAbilities(abilities.VIEW_CUSTOMER)"
|
||||
url="/api/v1/customers/export"
|
||||
:params="exportParams"
|
||||
/>
|
||||
|
||||
<div class="flex items-center justify-end space-x-5">
|
||||
<BaseButton
|
||||
v-show="customerStore.totalCustomers"
|
||||
variant="primary-outline"
|
||||
@@ -213,7 +206,6 @@ import abilities from '@/scripts/admin/stub/abilities'
|
||||
|
||||
import CustomerDropdown from '@/scripts/admin/components/dropdowns/CustomerIndexDropdown.vue'
|
||||
import AstronautIcon from '@/scripts/components/icons/empty/AstronautIcon.vue'
|
||||
import BaseCsvExportButton from '@/scripts/components/BaseCsvExportButton.vue'
|
||||
|
||||
const companyStore = useCompanyStore()
|
||||
const dialogStore = useDialogStore()
|
||||
@@ -231,12 +223,6 @@ let filters = reactive({
|
||||
phone: '',
|
||||
})
|
||||
|
||||
const exportParams = computed(() => ({
|
||||
display_name: filters.display_name,
|
||||
contact_name: filters.contact_name,
|
||||
phone: filters.phone,
|
||||
}))
|
||||
|
||||
const showEmptyScreen = computed(
|
||||
() => !customerStore.totalCustomers && !isFetchingInitialData.value
|
||||
)
|
||||
|
||||
@@ -14,43 +14,33 @@
|
||||
</BaseBreadcrumb>
|
||||
|
||||
<template #actions>
|
||||
<div class="flex items-center justify-end space-x-4">
|
||||
<BaseCsvExportDropdown
|
||||
v-show="estimateStore.totalEstimateCount"
|
||||
v-if="userStore.hasAbilities(abilities.VIEW_ESTIMATE)"
|
||||
document-type="estimate"
|
||||
url="/api/v1/estimates/export"
|
||||
:params="exportParams"
|
||||
/>
|
||||
<BaseButton
|
||||
v-show="estimateStore.totalEstimateCount"
|
||||
variant="primary-outline"
|
||||
@click="toggleFilter"
|
||||
>
|
||||
{{ $t('general.filter') }}
|
||||
<template #right="slotProps">
|
||||
<BaseIcon
|
||||
v-if="!showFilters"
|
||||
:class="slotProps.class"
|
||||
name="FunnelIcon"
|
||||
/>
|
||||
<BaseIcon v-else name="XMarkIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
</BaseButton>
|
||||
|
||||
<BaseButton
|
||||
v-show="estimateStore.totalEstimateCount"
|
||||
variant="primary-outline"
|
||||
@click="toggleFilter"
|
||||
>
|
||||
{{ $t('general.filter') }}
|
||||
<template #right="slotProps">
|
||||
<BaseIcon
|
||||
v-if="!showFilters"
|
||||
:class="slotProps.class"
|
||||
name="FunnelIcon"
|
||||
/>
|
||||
<BaseIcon v-else name="XMarkIcon" :class="slotProps.class" />
|
||||
<router-link
|
||||
v-if="userStore.hasAbilities(abilities.CREATE_ESTIMATE)"
|
||||
to="estimates/create"
|
||||
>
|
||||
<BaseButton variant="primary" class="ml-4">
|
||||
<template #left="slotProps">
|
||||
<BaseIcon name="PlusIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
{{ $t('estimates.new_estimate') }}
|
||||
</BaseButton>
|
||||
|
||||
<router-link
|
||||
v-if="userStore.hasAbilities(abilities.CREATE_ESTIMATE)"
|
||||
to="estimates/create"
|
||||
>
|
||||
<BaseButton variant="primary">
|
||||
<template #left="slotProps">
|
||||
<BaseIcon name="PlusIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
{{ $t('estimates.new_estimate') }}
|
||||
</BaseButton>
|
||||
</router-link>
|
||||
</div>
|
||||
</router-link>
|
||||
</template>
|
||||
</BasePageHeader>
|
||||
|
||||
@@ -261,7 +251,6 @@ import ObservatoryIcon from '@/scripts/components/icons/empty/ObservatoryIcon.vu
|
||||
import EstimateDropDown from '@/scripts/admin/components/dropdowns/EstimateIndexDropdown.vue'
|
||||
import SendEstimateModal from '@/scripts/admin/components/modal-components/SendEstimateModal.vue'
|
||||
import BaseEstimateStatusLabel from "@/scripts/components/base/BaseEstimateStatusLabel.vue";
|
||||
import BaseCsvExportDropdown from '@/scripts/components/BaseCsvExportDropdown.vue'
|
||||
|
||||
const estimateStore = useEstimateStore()
|
||||
const dialogStore = useDialogStore()
|
||||
@@ -292,14 +281,6 @@ let filters = reactive({
|
||||
estimate_number: '',
|
||||
})
|
||||
|
||||
const exportParams = computed(() => ({
|
||||
customer_id: filters.customer_id,
|
||||
status: filters.status,
|
||||
from_date: filters.from_date,
|
||||
to_date: filters.to_date,
|
||||
estimate_number: filters.estimate_number,
|
||||
}))
|
||||
|
||||
const showEmptyScreen = computed(
|
||||
() => !estimateStore.totalEstimateCount && !isRequestOngoing.value
|
||||
)
|
||||
|
||||
@@ -10,41 +10,33 @@
|
||||
</BaseBreadcrumb>
|
||||
|
||||
<template #actions>
|
||||
<div class="flex items-center justify-end space-x-4">
|
||||
<BaseCsvExportButton
|
||||
v-show="expenseStore.totalExpenses"
|
||||
v-if="userStore.hasAbilities(abilities.VIEW_EXPENSE)"
|
||||
url="/api/v1/expenses/export"
|
||||
:params="exportParams"
|
||||
/>
|
||||
<BaseButton
|
||||
v-show="expenseStore.totalExpenses"
|
||||
variant="primary-outline"
|
||||
@click="toggleFilter"
|
||||
>
|
||||
{{ $t('general.filter') }}
|
||||
<template #right="slotProps">
|
||||
<BaseIcon
|
||||
v-if="!showFilters"
|
||||
name="FunnelIcon"
|
||||
:class="slotProps.class"
|
||||
/>
|
||||
<BaseIcon v-else name="XMarkIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
</BaseButton>
|
||||
|
||||
<BaseButton
|
||||
v-show="expenseStore.totalExpenses"
|
||||
variant="primary-outline"
|
||||
@click="toggleFilter"
|
||||
>
|
||||
{{ $t('general.filter') }}
|
||||
<template #right="slotProps">
|
||||
<BaseIcon
|
||||
v-if="!showFilters"
|
||||
name="FunnelIcon"
|
||||
:class="slotProps.class"
|
||||
/>
|
||||
<BaseIcon v-else name="XMarkIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
</BaseButton>
|
||||
|
||||
<BaseButton
|
||||
v-if="userStore.hasAbilities(abilities.CREATE_EXPENSE)"
|
||||
variant="primary"
|
||||
@click="$router.push('expenses/create')"
|
||||
>
|
||||
<template #left="slotProps">
|
||||
<BaseIcon name="PlusIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
{{ $t('expenses.add_expense') }}
|
||||
</BaseButton>
|
||||
</div>
|
||||
<BaseButton
|
||||
v-if="userStore.hasAbilities(abilities.CREATE_EXPENSE)"
|
||||
class="ml-4"
|
||||
variant="primary"
|
||||
@click="$router.push('expenses/create')"
|
||||
>
|
||||
<template #left="slotProps">
|
||||
<BaseIcon name="PlusIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
{{ $t('expenses.add_expense') }}
|
||||
</BaseButton>
|
||||
</template>
|
||||
</BasePageHeader>
|
||||
|
||||
@@ -243,7 +235,6 @@ import abilities from '@/scripts/admin/stub/abilities'
|
||||
import UFOIcon from '@/scripts/components/icons/empty/UFOIcon.vue'
|
||||
import DuplicateExpenseModal from '@/scripts/admin/components/modal-components/DuplicateExpenseModal.vue'
|
||||
import ExpenseDropdown from '@/scripts/admin/components/dropdowns/ExpenseIndexDropdown.vue'
|
||||
import BaseCsvExportButton from '@/scripts/components/BaseCsvExportButton.vue'
|
||||
|
||||
const companyStore = useCompanyStore()
|
||||
const expenseStore = useExpenseStore()
|
||||
@@ -261,13 +252,6 @@ const filters = reactive({
|
||||
customer_id: '',
|
||||
})
|
||||
|
||||
const exportParams = computed(() => ({
|
||||
expense_category_id: filters.expense_category_id,
|
||||
from_date: filters.from_date,
|
||||
to_date: filters.to_date,
|
||||
customer_id: filters.customer_id,
|
||||
}))
|
||||
|
||||
const { t } = useI18n()
|
||||
let tableComponent = ref(null)
|
||||
|
||||
|
||||
@@ -8,43 +8,33 @@
|
||||
</BaseBreadcrumb>
|
||||
|
||||
<template #actions>
|
||||
<div class="flex items-center justify-end space-x-4">
|
||||
<BaseCsvExportDropdown
|
||||
v-show="invoiceStore.invoiceTotalCount"
|
||||
v-if="userStore.hasAbilities(abilities.VIEW_INVOICE)"
|
||||
document-type="invoice"
|
||||
url="/api/v1/invoices/export"
|
||||
:params="exportParams"
|
||||
/>
|
||||
<BaseButton
|
||||
v-show="invoiceStore.invoiceTotalCount"
|
||||
variant="primary-outline"
|
||||
@click="toggleFilter"
|
||||
>
|
||||
{{ $t('general.filter') }}
|
||||
<template #right="slotProps">
|
||||
<BaseIcon
|
||||
v-if="!showFilters"
|
||||
name="FunnelIcon"
|
||||
:class="slotProps.class"
|
||||
/>
|
||||
<BaseIcon v-else name="XMarkIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
</BaseButton>
|
||||
|
||||
<BaseButton
|
||||
v-show="invoiceStore.invoiceTotalCount"
|
||||
variant="primary-outline"
|
||||
@click="toggleFilter"
|
||||
>
|
||||
{{ $t('general.filter') }}
|
||||
<template #right="slotProps">
|
||||
<BaseIcon
|
||||
v-if="!showFilters"
|
||||
name="FunnelIcon"
|
||||
:class="slotProps.class"
|
||||
/>
|
||||
<BaseIcon v-else name="XMarkIcon" :class="slotProps.class" />
|
||||
<router-link
|
||||
v-if="userStore.hasAbilities(abilities.CREATE_INVOICE)"
|
||||
to="invoices/create"
|
||||
>
|
||||
<BaseButton variant="primary" class="ml-4">
|
||||
<template #left="slotProps">
|
||||
<BaseIcon name="PlusIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
{{ $t('invoices.new_invoice') }}
|
||||
</BaseButton>
|
||||
|
||||
<router-link
|
||||
v-if="userStore.hasAbilities(abilities.CREATE_INVOICE)"
|
||||
to="invoices/create"
|
||||
>
|
||||
<BaseButton variant="primary">
|
||||
<template #left="slotProps">
|
||||
<BaseIcon name="PlusIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
{{ $t('invoices.new_invoice') }}
|
||||
</BaseButton>
|
||||
</router-link>
|
||||
</div>
|
||||
</router-link>
|
||||
</template>
|
||||
</BasePageHeader>
|
||||
|
||||
@@ -289,7 +279,6 @@ import MoonwalkerIcon from '@/scripts/components/icons/empty/MoonwalkerIcon.vue'
|
||||
import InvoiceDropdown from '@/scripts/admin/components/dropdowns/InvoiceIndexDropdown.vue'
|
||||
import SendInvoiceModal from '@/scripts/admin/components/modal-components/SendInvoiceModal.vue'
|
||||
import BaseInvoiceStatusLabel from "@/scripts/components/base/BaseInvoiceStatusLabel.vue";
|
||||
import BaseCsvExportDropdown from '@/scripts/components/BaseCsvExportDropdown.vue'
|
||||
// Stores
|
||||
const invoiceStore = useInvoiceStore()
|
||||
const dialogStore = useDialogStore()
|
||||
@@ -336,14 +325,6 @@ let filters = reactive({
|
||||
invoice_number: '',
|
||||
})
|
||||
|
||||
const exportParams = computed(() => ({
|
||||
customer_id: filters.customer_id,
|
||||
status: filters.status,
|
||||
from_date: filters.from_date,
|
||||
to_date: filters.to_date,
|
||||
invoice_number: filters.invoice_number,
|
||||
}))
|
||||
|
||||
const showEmptyScreen = computed(
|
||||
() => !invoiceStore.invoiceTotalCount && !isRequestOngoing.value
|
||||
)
|
||||
|
||||
@@ -7,14 +7,7 @@
|
||||
</BaseBreadcrumb>
|
||||
|
||||
<template #actions>
|
||||
<div class="flex items-center justify-end space-x-4">
|
||||
<BaseCsvExportButton
|
||||
v-show="itemStore.totalItems"
|
||||
v-if="userStore.hasAbilities(abilities.VIEW_ITEM)"
|
||||
url="/api/v1/items/export"
|
||||
:params="exportParams"
|
||||
/>
|
||||
|
||||
<div class="flex items-center justify-end space-x-5">
|
||||
<BaseButton
|
||||
v-show="itemStore.totalItems"
|
||||
variant="primary-outline"
|
||||
@@ -206,7 +199,6 @@ import { useCompanyStore } from '@/scripts/admin/stores/company'
|
||||
import { useUserStore } from '@/scripts/admin/stores/user'
|
||||
import ItemDropdown from '@/scripts/admin/components/dropdowns/ItemIndexDropdown.vue'
|
||||
import SatelliteIcon from '@/scripts/components/icons/empty/SatelliteIcon.vue'
|
||||
import BaseCsvExportButton from '@/scripts/components/BaseCsvExportButton.vue'
|
||||
import abilities from '@/scripts/admin/stub/abilities'
|
||||
|
||||
const utils = inject('utils')
|
||||
@@ -227,12 +219,6 @@ const filters = reactive({
|
||||
price: '',
|
||||
})
|
||||
|
||||
const exportParams = computed(() => ({
|
||||
search: filters.name,
|
||||
unit_id: filters.unit_id !== null ? filters.unit_id : '',
|
||||
price: filters.price ? Math.round(filters.price * 100) : '',
|
||||
}))
|
||||
|
||||
const table = ref(null)
|
||||
|
||||
const showEmptyScreen = computed(
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
<template>
|
||||
<BaseButton
|
||||
v-if="show"
|
||||
variant="primary-outline"
|
||||
:disabled="loading"
|
||||
@click="onExport"
|
||||
>
|
||||
<template #left="slotProps">
|
||||
<BaseIcon name="ArrowDownTrayIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
{{ label ?? $t('general.export_csv') }}
|
||||
</BaseButton>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { downloadCsvExport } from '@/scripts/helpers/csv-export'
|
||||
|
||||
const props = defineProps({
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
params: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
async function onExport() {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
await downloadCsvExport(props.url, props.params)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,90 +0,0 @@
|
||||
<template>
|
||||
<BaseDropdown v-if="show" width-class="w-64">
|
||||
<template #activator>
|
||||
<BaseButton variant="primary-outline" :disabled="loading">
|
||||
<template #left="slotProps">
|
||||
<BaseIcon name="ArrowDownTrayIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
{{ $t('general.export') }}
|
||||
<template #right="slotProps">
|
||||
<BaseIcon name="ChevronDownIcon" :class="slotProps.class" />
|
||||
</template>
|
||||
</BaseButton>
|
||||
</template>
|
||||
|
||||
<BaseDropdownItem @click.prevent="exportDocument">
|
||||
<BaseIcon
|
||||
name="DocumentTextIcon"
|
||||
class="w-5 h-5 mr-3 text-gray-400 group-hover:text-gray-500"
|
||||
/>
|
||||
{{ documentOptionLabel }}
|
||||
</BaseDropdownItem>
|
||||
|
||||
<BaseDropdownItem @click.prevent="exportLines">
|
||||
<BaseIcon
|
||||
name="QueueListIcon"
|
||||
class="w-5 h-5 mr-3 text-gray-400 group-hover:text-gray-500"
|
||||
/>
|
||||
{{ linesOptionLabel }}
|
||||
</BaseDropdownItem>
|
||||
</BaseDropdown>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { downloadCsvExport } from '@/scripts/helpers/csv-export'
|
||||
|
||||
const props = defineProps({
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
params: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
documentType: {
|
||||
type: String,
|
||||
required: true,
|
||||
validator: (value) => ['invoice', 'estimate'].includes(value),
|
||||
},
|
||||
})
|
||||
|
||||
const { t } = useI18n()
|
||||
const loading = ref(false)
|
||||
|
||||
const documentOptionLabel = computed(() =>
|
||||
props.documentType === 'estimate'
|
||||
? t('general.export_one_line_per_estimate')
|
||||
: t('general.export_one_line_per_invoice'),
|
||||
)
|
||||
|
||||
const linesOptionLabel = computed(() =>
|
||||
props.documentType === 'estimate'
|
||||
? t('general.export_one_line_per_estimate_item')
|
||||
: t('general.export_one_line_per_invoice_item'),
|
||||
)
|
||||
|
||||
async function exportDocument() {
|
||||
await runExport(props.params)
|
||||
}
|
||||
|
||||
async function exportLines() {
|
||||
await runExport({ ...props.params, format: 'lines' })
|
||||
}
|
||||
|
||||
async function runExport(params) {
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
await downloadCsvExport(props.url, params)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
30
resources/scripts/helpers/csv-export.js
vendored
30
resources/scripts/helpers/csv-export.js
vendored
@@ -1,30 +0,0 @@
|
||||
import http from '@/scripts/http/index.js'
|
||||
|
||||
export async function downloadCsvExport(url, params = {}) {
|
||||
const response = await http.get(url, {
|
||||
params,
|
||||
responseType: 'blob',
|
||||
})
|
||||
|
||||
const contentDisposition = response.headers['content-disposition']
|
||||
let filename = 'export.csv'
|
||||
|
||||
if (contentDisposition) {
|
||||
const match = contentDisposition.match(/filename="?([^";]+)"?/)
|
||||
|
||||
if (match) {
|
||||
filename = match[1]
|
||||
}
|
||||
}
|
||||
|
||||
const blobUrl = window.URL.createObjectURL(
|
||||
new Blob([response.data], { type: 'text/csv;charset=utf-8' }),
|
||||
)
|
||||
const link = document.createElement('a')
|
||||
link.href = blobUrl
|
||||
link.setAttribute('download', filename)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(blobUrl)
|
||||
}
|
||||
Reference in New Issue
Block a user