mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-04-16 01:34:08 +00:00
Fix copy PDF URL and dropdown action conditions
Copy PDF URL now checks window.isSecureContext before using navigator.clipboard, falls back to textarea+execCommand on HTTP, and shows a success notification. Invoice dropdown: Mark as Sent uses its own condition instead of reusing Send, Resend hidden in detail view. Estimate dropdown: Mark as Accepted/Rejected hidden when already in the other terminal state, Convert to Invoice hidden on rejected estimates. Added Convert to Estimate action for invoices.
This commit is contained in:
@@ -63,7 +63,7 @@
|
||||
</BaseDropdownItem>
|
||||
|
||||
<!-- Convert into Invoice -->
|
||||
<BaseDropdownItem v-if="canCreateInvoice" @click="convertToInvoice">
|
||||
<BaseDropdownItem v-if="canCreateInvoice && row.status !== 'REJECTED'" @click="convertToInvoice">
|
||||
<BaseIcon
|
||||
name="DocumentTextIcon"
|
||||
class="w-5 h-5 mr-3 text-subtle group-hover:text-muted"
|
||||
@@ -106,7 +106,7 @@
|
||||
|
||||
<!-- Mark as Accepted -->
|
||||
<BaseDropdownItem
|
||||
v-if="row.status !== 'ACCEPTED' && canEdit"
|
||||
v-if="row.status !== 'ACCEPTED' && row.status !== 'REJECTED' && canEdit"
|
||||
@click="onMarkAsAccepted"
|
||||
>
|
||||
<BaseIcon
|
||||
@@ -118,7 +118,7 @@
|
||||
|
||||
<!-- Mark as Rejected -->
|
||||
<BaseDropdownItem
|
||||
v-if="row.status !== 'REJECTED' && canEdit"
|
||||
v-if="row.status !== 'REJECTED' && row.status !== 'ACCEPTED' && canEdit"
|
||||
@click="onMarkAsRejected"
|
||||
>
|
||||
<BaseIcon
|
||||
@@ -137,6 +137,7 @@ import { useRoute, useRouter } from 'vue-router'
|
||||
import { useEstimateStore } from '../store'
|
||||
import { useDialogStore } from '../../../../stores/dialog.store'
|
||||
import { useModalStore } from '../../../../stores/modal.store'
|
||||
import { useNotificationStore } from '../../../../stores/notification.store'
|
||||
import type { Estimate } from '../../../../types/domain/estimate'
|
||||
|
||||
interface TableRef {
|
||||
@@ -167,6 +168,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
const estimateStore = useEstimateStore()
|
||||
const dialogStore = useDialogStore()
|
||||
const modalStore = useModalStore()
|
||||
const notificationStore = useNotificationStore()
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -289,16 +291,29 @@ function onMarkAsRejected(): void {
|
||||
|
||||
function copyPdfUrl(): void {
|
||||
const pdfUrl = `${window.location.origin}/estimates/pdf/${props.row.unique_hash}`
|
||||
navigator.clipboard.writeText(pdfUrl).catch(() => {
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.value = pdfUrl
|
||||
document.body.appendChild(textarea)
|
||||
textarea.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(textarea)
|
||||
copyToClipboard(pdfUrl)
|
||||
notificationStore.showNotification({
|
||||
type: 'success',
|
||||
message: t('general.copied_pdf_url_clipboard'),
|
||||
})
|
||||
}
|
||||
|
||||
function copyToClipboard(text: string): void {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard.writeText(text)
|
||||
return
|
||||
}
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.value = text
|
||||
textarea.style.position = 'fixed'
|
||||
textarea.style.opacity = '0'
|
||||
document.body.appendChild(textarea)
|
||||
textarea.focus()
|
||||
textarea.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(textarea)
|
||||
}
|
||||
|
||||
function cloneEstimateData(): void {
|
||||
dialogStore.openDialog({
|
||||
title: t('general.are_you_sure'),
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
</BaseDropdownItem>
|
||||
|
||||
<!-- Resend Invoice -->
|
||||
<BaseDropdownItem v-if="canReSendInvoice" @click="sendInvoice">
|
||||
<BaseDropdownItem v-if="canReSendInvoice && !isDetailView" @click="sendInvoice">
|
||||
<BaseIcon
|
||||
name="PaperAirplaneIcon"
|
||||
class="w-5 h-5 mr-3 text-subtle group-hover:text-muted"
|
||||
@@ -76,7 +76,7 @@
|
||||
</router-link>
|
||||
|
||||
<!-- Mark as Sent -->
|
||||
<BaseDropdownItem v-if="canSendInvoice" @click="onMarkAsSent">
|
||||
<BaseDropdownItem v-if="row.status === 'DRAFT' && !isDetailView && canSend" @click="onMarkAsSent">
|
||||
<BaseIcon
|
||||
name="CheckCircleIcon"
|
||||
class="w-5 h-5 mr-3 text-subtle group-hover:text-muted"
|
||||
@@ -93,6 +93,15 @@
|
||||
{{ $t('invoices.clone_invoice') }}
|
||||
</BaseDropdownItem>
|
||||
|
||||
<!-- Convert to Estimate -->
|
||||
<BaseDropdownItem v-if="canCreateEstimate" @click="convertToEstimate">
|
||||
<BaseIcon
|
||||
name="DocumentIcon"
|
||||
class="w-5 h-5 mr-3 text-subtle group-hover:text-muted"
|
||||
/>
|
||||
{{ $t('invoices.convert_to_estimate') }}
|
||||
</BaseDropdownItem>
|
||||
|
||||
<!-- Delete Invoice -->
|
||||
<BaseDropdownItem v-if="canDelete" @click="removeInvoice">
|
||||
<BaseIcon
|
||||
@@ -111,6 +120,7 @@ import { useRoute, useRouter } from 'vue-router'
|
||||
import { useInvoiceStore } from '../store'
|
||||
import { useDialogStore } from '../../../../stores/dialog.store'
|
||||
import { useModalStore } from '../../../../stores/modal.store'
|
||||
import { useNotificationStore } from '../../../../stores/notification.store'
|
||||
import type { Invoice } from '../../../../types/domain/invoice'
|
||||
|
||||
interface TableRef {
|
||||
@@ -127,6 +137,7 @@ interface Props {
|
||||
canDelete?: boolean
|
||||
canSend?: boolean
|
||||
canCreatePayment?: boolean
|
||||
canCreateEstimate?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -138,11 +149,13 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
canDelete: false,
|
||||
canSend: false,
|
||||
canCreatePayment: false,
|
||||
canCreateEstimate: false,
|
||||
})
|
||||
|
||||
const invoiceStore = useInvoiceStore()
|
||||
const dialogStore = useDialogStore()
|
||||
const modalStore = useModalStore()
|
||||
const notificationStore = useNotificationStore()
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -205,6 +218,23 @@ function cloneInvoiceData(): void {
|
||||
})
|
||||
}
|
||||
|
||||
function convertToEstimate(): void {
|
||||
dialogStore.openDialog({
|
||||
title: t('general.are_you_sure'),
|
||||
message: t('invoices.confirm_convert_to_estimate'),
|
||||
yesLabel: t('general.ok'),
|
||||
noLabel: t('general.cancel'),
|
||||
variant: 'primary',
|
||||
hideNoButton: false,
|
||||
size: 'lg',
|
||||
}).then(async (res: boolean) => {
|
||||
if (res) {
|
||||
const response = await invoiceStore.convertToEstimate({ id: props.row.id })
|
||||
router.push(`/admin/estimates/${response.data.data.id}/edit`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function onMarkAsSent(): void {
|
||||
dialogStore.openDialog({
|
||||
title: t('general.are_you_sure'),
|
||||
@@ -234,14 +264,26 @@ function sendInvoice(): void {
|
||||
|
||||
function copyPdfUrl(): void {
|
||||
const pdfUrl = `${window.location.origin}/invoices/pdf/${props.row.unique_hash}`
|
||||
navigator.clipboard.writeText(pdfUrl).catch(() => {
|
||||
// Fallback
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.value = pdfUrl
|
||||
document.body.appendChild(textarea)
|
||||
textarea.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(textarea)
|
||||
copyToClipboard(pdfUrl)
|
||||
notificationStore.showNotification({
|
||||
type: 'success',
|
||||
message: t('general.copied_pdf_url_clipboard'),
|
||||
})
|
||||
}
|
||||
|
||||
function copyToClipboard(text: string): void {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard.writeText(text)
|
||||
return
|
||||
}
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.value = text
|
||||
textarea.style.position = 'fixed'
|
||||
textarea.style.opacity = '0'
|
||||
document.body.appendChild(textarea)
|
||||
textarea.focus()
|
||||
textarea.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(textarea)
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user