From efc9545ae64fe983130ebf37f63cce0977e590a8 Mon Sep 17 00:00:00 2001 From: Marius Mutu Date: Fri, 13 Mar 2026 17:31:41 +0200 Subject: [PATCH] feat(frontend): Portal public client + PDF download + Facturi view - DevizPublicView: standalone public page /p/:token (no auth, no layout) - Loads order/tenant/lines from backend API - Accept/Reject buttons with feedback banners - Mobile-first design with service branding - usePdf composable: fetch PDF blob from backend and trigger browser download - PdfDownloadButton component: reusable button for deviz/invoice PDF download - InvoicesView: table with invoice list from wa-sqlite, PDF download per row - OrderDetailView: added PDF Deviz download button (visible when not DRAFT) - Router: added /invoices route, portal /p/:token uses layout: 'none' - App.vue: supports layout: 'none' for standalone pages - AppLayout: added Facturi link in sidebar nav Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/App.vue | 3 +- .../components/orders/PdfDownloadButton.vue | 44 ++++ frontend/src/composables/usePdf.js | 34 +++ frontend/src/layouts/AppLayout.vue | 1 + frontend/src/router/index.js | 3 +- frontend/src/views/client/DevizPublicView.vue | 207 +++++++++++++++++- frontend/src/views/orders/InvoicesView.vue | 76 +++++++ frontend/src/views/orders/OrderDetailView.vue | 8 + 8 files changed, 371 insertions(+), 5 deletions(-) create mode 100644 frontend/src/components/orders/PdfDownloadButton.vue create mode 100644 frontend/src/composables/usePdf.js create mode 100644 frontend/src/views/orders/InvoicesView.vue diff --git a/frontend/src/App.vue b/frontend/src/App.vue index c8c3abf..28ed6f0 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,5 +1,6 @@ diff --git a/frontend/src/components/orders/PdfDownloadButton.vue b/frontend/src/components/orders/PdfDownloadButton.vue new file mode 100644 index 0000000..efcc5bf --- /dev/null +++ b/frontend/src/components/orders/PdfDownloadButton.vue @@ -0,0 +1,44 @@ + + + diff --git a/frontend/src/composables/usePdf.js b/frontend/src/composables/usePdf.js new file mode 100644 index 0000000..6ff5bae --- /dev/null +++ b/frontend/src/composables/usePdf.js @@ -0,0 +1,34 @@ +const API_URL = import.meta.env.VITE_API_URL || '/api' + +export function usePdf() { + function getToken() { + return localStorage.getItem('token') + } + + async function downloadPdf(url, filename) { + const token = getToken() + const res = await fetch(`${API_URL}${url}`, { + headers: token ? { Authorization: `Bearer ${token}` } : {}, + }) + if (!res.ok) throw new Error('Eroare la descarcarea PDF-ului') + const blob = await res.blob() + const objectUrl = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = objectUrl + a.download = filename + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + URL.revokeObjectURL(objectUrl) + } + + async function downloadDevizPdf(orderId, nrComanda) { + await downloadPdf(`/orders/${orderId}/pdf/deviz`, `deviz-${nrComanda || orderId}.pdf`) + } + + async function downloadInvoicePdf(invoiceId, nrFactura) { + await downloadPdf(`/invoices/${invoiceId}/pdf`, `factura-${nrFactura || invoiceId}.pdf`) + } + + return { downloadDevizPdf, downloadInvoicePdf } +} diff --git a/frontend/src/layouts/AppLayout.vue b/frontend/src/layouts/AppLayout.vue index 985c67d..e5e51f7 100644 --- a/frontend/src/layouts/AppLayout.vue +++ b/frontend/src/layouts/AppLayout.vue @@ -88,6 +88,7 @@ const mobileMenuOpen = ref(false) const navItems = [ { path: '/dashboard', label: 'Dashboard' }, { path: '/orders', label: 'Comenzi' }, + { path: '/invoices', label: 'Facturi' }, { path: '/vehicles', label: 'Vehicule' }, { path: '/appointments', label: 'Programari' }, { path: '/catalog', label: 'Catalog' }, diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index c275e3c..84694c8 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -13,8 +13,9 @@ const router = createRouter({ { path: '/vehicles', component: () => import('../views/vehicles/VehiclesListView.vue'), meta: { requiresAuth: true } }, { path: '/appointments', component: () => import('../views/appointments/AppointmentsView.vue'), meta: { requiresAuth: true } }, { path: '/catalog', component: () => import('../views/catalog/CatalogView.vue'), meta: { requiresAuth: true } }, + { path: '/invoices', component: () => import('../views/orders/InvoicesView.vue'), meta: { requiresAuth: true } }, { path: '/settings', component: () => import('../views/settings/SettingsView.vue'), meta: { requiresAuth: true } }, - { path: '/p/:token', component: () => import('../views/client/DevizPublicView.vue') }, + { path: '/p/:token', component: () => import('../views/client/DevizPublicView.vue'), meta: { layout: 'none' } }, { path: '/', redirect: '/dashboard' }, ], scrollBehavior: () => ({ top: 0 }) diff --git a/frontend/src/views/client/DevizPublicView.vue b/frontend/src/views/client/DevizPublicView.vue index 495306c..d592ba0 100644 --- a/frontend/src/views/client/DevizPublicView.vue +++ b/frontend/src/views/client/DevizPublicView.vue @@ -1,6 +1,207 @@ + + diff --git a/frontend/src/views/orders/InvoicesView.vue b/frontend/src/views/orders/InvoicesView.vue new file mode 100644 index 0000000..a9af6d1 --- /dev/null +++ b/frontend/src/views/orders/InvoicesView.vue @@ -0,0 +1,76 @@ + + + diff --git a/frontend/src/views/orders/OrderDetailView.vue b/frontend/src/views/orders/OrderDetailView.vue index 7b01327..b55cfa8 100644 --- a/frontend/src/views/orders/OrderDetailView.vue +++ b/frontend/src/views/orders/OrderDetailView.vue @@ -6,6 +6,13 @@

{{ order.nr_comanda }}

+