From 5c0e761dfa0f7f35d2f8f4f11418b402089f5e7e Mon Sep 17 00:00:00 2001 From: Darko Gjorgjijoski Date: Mon, 6 Apr 2026 22:56:49 +0200 Subject: [PATCH] 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. --- .../estimates/components/EstimateDropdown.vue | 35 ++++++++--- .../invoices/components/InvoiceDropdown.vue | 62 ++++++++++++++++--- 2 files changed, 77 insertions(+), 20 deletions(-) diff --git a/resources/scripts-v2/features/company/estimates/components/EstimateDropdown.vue b/resources/scripts-v2/features/company/estimates/components/EstimateDropdown.vue index 24750cbf..f41eee21 100644 --- a/resources/scripts-v2/features/company/estimates/components/EstimateDropdown.vue +++ b/resources/scripts-v2/features/company/estimates/components/EstimateDropdown.vue @@ -63,7 +63,7 @@ - + (), { 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'), diff --git a/resources/scripts-v2/features/company/invoices/components/InvoiceDropdown.vue b/resources/scripts-v2/features/company/invoices/components/InvoiceDropdown.vue index a5c6cb9b..6a835f03 100644 --- a/resources/scripts-v2/features/company/invoices/components/InvoiceDropdown.vue +++ b/resources/scripts-v2/features/company/invoices/components/InvoiceDropdown.vue @@ -54,7 +54,7 @@ - + - + + + + + {{ $t('invoices.convert_to_estimate') }} + + (), { @@ -138,11 +149,13 @@ const props = withDefaults(defineProps(), { 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) +}