From 4701efddd8c15ac3976f9ab97db2a51566ae918b Mon Sep 17 00:00:00 2001 From: Daniel Stock Date: Wed, 29 Oct 2025 18:04:09 +0100 Subject: [PATCH] Move alertDialog to AppLayout fixes #33 --- package-lock.json | 136 ++++++++++++++++++ package.json | 1 + resources/js/app.ts | 3 + .../js/components/documents/InvoiceDialog.vue | 58 +++----- .../ui/alert-dialog/AlertDialogContent.vue | 2 +- resources/js/layouts/AppLayout.vue | 24 +++- resources/js/stores/alertStore.ts | 17 +++ 7 files changed, 200 insertions(+), 41 deletions(-) create mode 100644 resources/js/stores/alertStore.ts diff --git a/package-lock.json b/package-lock.json index 17cc1d0..0fbd92f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "fuse.js": "^7.1.0", "laravel-vite-plugin": "^2.0.0", "lucide-vue-next": "^0.468.0", + "pinia": "^3.0.3", "reka-ui": "^2.6.0", "tailwind-merge": "^3.2.0", "tailwindcss": "^4.1.1", @@ -1978,6 +1979,39 @@ "he": "^1.2.0" } }, + "node_modules/@vue/devtools-api": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.7.tgz", + "integrity": "sha512-lwOnNBH2e7x1fIIbVT7yF5D+YWhqELm55/4ZKf45R9T8r9dE2AIOy8HKjfqzGsoTHFbWbr337O4E0A0QADnjBg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.7" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.7.tgz", + "integrity": "sha512-wgoZtxcTta65cnZ1Q6MbAfePVFxfM+gq0saaeytoph7nEa7yMXoi6sCPy4ufO111B9msnw0VOWjPEFCXuAKRHA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.7", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.7.tgz", + "integrity": "sha512-+udSj47aRl5aKb0memBvcUG9koarqnxNM5yjuREvqwK6T3ap4mn3Zqqc17QrBFTqSMjr3HK1cvStEZpMDpfdyw==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, "node_modules/@vue/eslint-config-typescript": { "version": "14.6.0", "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-14.6.0.tgz", @@ -2231,6 +2265,15 @@ "dev": true, "license": "MIT" }, + "node_modules/birpc": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.6.1.tgz", + "integrity": "sha512-LPnFhlDpdSH6FJhJyn4M0kFO7vtQ5iPw24FnG0y21q09xC7e8+1LeR31S1MAIrDAHp4m7aas4bEkTDTvMAtebQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -2482,6 +2525,21 @@ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3406,6 +3464,12 @@ "he": "bin/he" } }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3486,6 +3550,18 @@ "node": ">=0.12.0" } }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3968,6 +4044,12 @@ "node": ">= 18" } }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, "node_modules/mkdirp": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", @@ -4144,6 +4226,12 @@ "node": ">=8" } }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4162,6 +4250,27 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pinia": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.3.tgz", + "integrity": "sha512-ttXO/InUULUXkMHpTdp9Fj4hLpD/2AoJdmAbAeW2yu1iy1k+pkFekQXw5VpC0/5p51IOR/jDaDRfRWRnMMsGOA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.2" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -4450,6 +4559,12 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, "node_modules/rollup": { "version": "4.50.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.50.2.tgz", @@ -4673,6 +4788,15 @@ "node": ">=0.10.0" } }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -4714,6 +4838,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/superjson": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.5.tgz", + "integrity": "sha512-zWPTX96LVsA/eVYnqOM2+ofcdPqdS1dAF1LN4TS2/MWuUpfitd9ctTa87wt4xrYnZnkLtS69xpBdSxVBP5Rm6w==", + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", diff --git a/package.json b/package.json index c9b4209..b2a8789 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "fuse.js": "^7.1.0", "laravel-vite-plugin": "^2.0.0", "lucide-vue-next": "^0.468.0", + "pinia": "^3.0.3", "reka-ui": "^2.6.0", "tailwind-merge": "^3.2.0", "tailwindcss": "^4.1.1", diff --git a/resources/js/app.ts b/resources/js/app.ts index edfee74..64cf867 100644 --- a/resources/js/app.ts +++ b/resources/js/app.ts @@ -4,6 +4,7 @@ import { createInertiaApp } from '@inertiajs/vue3'; import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'; import type { DefineComponent } from 'vue'; import { createApp, h } from 'vue'; +import { createPinia } from 'pinia'; import { initializeTheme } from './composables/useAppearance'; const appName = import.meta.env.VITE_APP_NAME || 'Laravel'; @@ -12,8 +13,10 @@ createInertiaApp({ title: (title) => (title ? `${title} - ${appName}` : appName), resolve: (name) => resolvePageComponent(`./pages/${name}.vue`, import.meta.glob('./pages/**/*.vue')), setup({ el, App, props, plugin }) { + let pinia = createPinia() createApp({ render: () => h(App, props) }) .use(plugin) + .use(pinia) .mount(el); }, progress: { diff --git a/resources/js/components/documents/InvoiceDialog.vue b/resources/js/components/documents/InvoiceDialog.vue index ee1bad8..12c61aa 100644 --- a/resources/js/components/documents/InvoiceDialog.vue +++ b/resources/js/components/documents/InvoiceDialog.vue @@ -12,7 +12,7 @@ import { ref, computed, watch, onMounted, onUpdated, useTemplateRef } from "vue" import { Customer, Invoice, Contact, PaymentTerms, Address } from "@/types" import { newCustomer, newContact, newBillingData } from '@/types/index.d' -import { toCurrency, toLocalDate, toShortISOString, cn, calcDueDate, toFixedRounded } from '@/lib/utils'; +import { toCurrency, toLocalDate, toShortISOString, cn, calcDueDate, toFixedRounded } from '@/lib/utils' import axios from 'axios' import { type DateValue, DateFormatter, getLocalTimeZone, parseDate, fromDate } from "@internationalized/date" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog" @@ -24,13 +24,13 @@ import { Input } from '@/components/ui/input'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { StatusBadge, statusBadgeLabels, statusBadgeTextColor, StatusBadgeVariants } from '@/components/ui/status-badge' import LineItemTable from '@/components/documents/LineItemTable.vue' -import { Eye, FileText, CircleEllipsis, Trash, BookUser, User, CodeXml, CalendarIcon, MessageCircleQuestion, X, CircleX, Logs, ListCheck, ClipboardCheck, ClipboardList } from "lucide-vue-next" -import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from '@/components/ui/alert-dialog' +import { Eye, FileText, CircleEllipsis, Trash, Trash2, BookUser, User, CodeXml, CalendarIcon, MessageCircleQuestion, X, CircleX, Logs, ListCheck, ClipboardCheck, ClipboardList } from "lucide-vue-next" +import { alertStore } from "@/stores/alertStore" import { Calendar } from "@/components/ui/calendar" import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover" -import { exportPdf, exportXml } from "@/routes/invoice"; +import { exportPdf, exportXml } from "@/routes/invoice" import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger, } from '@/components/ui/sheet' -import { GrowingTextarea } from '../ui/growing-textarea'; +import { GrowingTextarea } from '../ui/growing-textarea' const props = defineProps<{ invoiceData: Invoice | null, @@ -46,8 +46,7 @@ const isDirty = ref(false); const isLoading = ref(false); const importContact = ref(newContact() as Contact) const importCustomer = ref(newCustomer() as Customer) -const alert = ref({ open: false, title: "", message: "", cancelText: "", onCancel: () => { }, confirmText: "", onConfirm: () => { } }) - +const alert = alertStore() onMounted(async () => { const response = await axios.get('/api/paymentterms') @@ -191,31 +190,30 @@ const saveChanges = () => { const cancelChanges = (event: Event | null) => { if (isDirty.value) { - alert.value.title = "Wirklich schließen?" - alert.value.message = "Es gibt ungespeicherte Änderungen, die dann verloren gehen." - alert.value.cancelText = "Abbrechen" - alert.value.onCancel = () => { + alert.title = "Wirklich schließen?" + alert.message = "Es gibt ungespeicherte Änderungen, die dann verloren gehen." + alert.cancelText = "Abbrechen" + alert.onCancel = () => { + console.log('cancel') event?.preventDefault() event.returnValue = true - alert.value.open = false + alert.open = false } - alert.value.confirmText = "Schließen" - alert.value.onConfirm = () => { + alert.actionText = "Schließen" + // alert.actionVariant = "destructive" + alert.onAction = () => { + console.log('action') emit('cancel') isOpen.value = false - alert.value.open = false + alert.open = false } - alert.value.open = true + alert.open = true } else { emit('cancel') isOpen.value = false } } -const confirmCancel = () => { - return window.confirm('Es gibt ungespeicherte Änderungen. Möchtest Du die Seite wirklich verlassen?'); -} - const preview = function () { if (!invoice.value) return; window?.open('/invoice/' + invoice.value.id, '_blank')?.focus(); @@ -383,7 +381,8 @@ const updateTotalAmount = () => {
{{ toCurrency(invoice.totalAmount) }} - {{ toCurrency(toFixedRounded(Number(invoice.totalAmount * 1.19), 2)) }} + {{ toCurrency(toFixedRounded(Number(invoice.totalAmount * + 1.19), 2)) }}
@@ -582,23 +581,6 @@ const updateTotalAmount = () => { :disabled="!isDirty">Speichern - - - - {{ alert.title }} - - {{ alert.message }} - - - - - - - - - diff --git a/resources/js/components/ui/alert-dialog/AlertDialogContent.vue b/resources/js/components/ui/alert-dialog/AlertDialogContent.vue index 73e4fcb..3fbbb50 100644 --- a/resources/js/components/ui/alert-dialog/AlertDialogContent.vue +++ b/resources/js/components/ui/alert-dialog/AlertDialogContent.vue @@ -28,7 +28,7 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits) v-bind="forwarded" :class=" cn( - 'fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg', + 'fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-bottom-[5%] data-[state=open]:slide-in-from-bottom-[5%] sm:rounded-lg', props.class, ) " diff --git a/resources/js/layouts/AppLayout.vue b/resources/js/layouts/AppLayout.vue index 75f3a84..c70ae5b 100644 --- a/resources/js/layouts/AppLayout.vue +++ b/resources/js/layouts/AppLayout.vue @@ -2,14 +2,18 @@ import AppLayout from '@/layouts/app/AppSidebarLayout.vue'; // import AppLayout from '@/layouts/app/AppHeaderLayout.vue'; import type { BreadcrumbItemType } from '@/types'; -import { computed, onMounted } from 'vue'; +import { ref, onMounted } from 'vue'; import 'vue-sonner/style.css' import { Toaster } from 'vue-sonner' +import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from '@/components/ui/alert-dialog' import { Info, CircleAlert, CircleCheck, LoaderCircle, Ban } from "lucide-vue-next" +import { Button } from '@/components/ui/button' +import { alertStore } from '@/stores/alertStore'; interface Props { breadcrumbs?: BreadcrumbItemType[]; } +const alert = alertStore() withDefaults(defineProps(), { breadcrumbs: () => [], @@ -24,7 +28,6 @@ onMounted(() => { diff --git a/resources/js/stores/alertStore.ts b/resources/js/stores/alertStore.ts new file mode 100644 index 0000000..63a571b --- /dev/null +++ b/resources/js/stores/alertStore.ts @@ -0,0 +1,17 @@ +import { defineStore } from 'pinia' + +export const alertStore = defineStore('alert', { + state: () => { + return { + open: false, + title: "", + message: "", + cancelText: "Abbrechen", + onCancel: () => { }, + actionText: "Ok", + actionVariant: "action" as "action" | "destructive", + onAction: () => { } + } + }, + actions: {} +}) \ No newline at end of file