Add confirm dialog to reminder button.

Add a show function in alertStore so we can use alerts in a defined way everywhere.
#12 #33
This commit is contained in:
2025-10-30 10:15:02 +01:00
parent 4701efddd8
commit 058f7af9f6
4 changed files with 100 additions and 39 deletions
@@ -24,13 +24,14 @@ 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, Trash2, BookUser, User, CodeXml, CalendarIcon, MessageCircleQuestion, X, CircleX, Logs, ListCheck, ClipboardCheck, ClipboardList } from "lucide-vue-next"
import { Eye, FileText, CircleEllipsis, Trash, Trash2, BookUser, User, CodeXml, CalendarIcon, MessageCircleQuestion, X, CircleX, Logs, ListCheck, ClipboardCheck, ClipboardList, Loader, Loader2 } 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 { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger, } from '@/components/ui/sheet'
import { GrowingTextarea } from '../ui/growing-textarea'
import { toast } from "vue-sonner"
const props = defineProps<{
invoiceData: Invoice | null,
@@ -47,6 +48,7 @@ const isLoading = ref(false);
const importContact = ref(newContact() as Contact)
const importCustomer = ref(newCustomer() as Customer)
const alert = alertStore()
const reminderLoading = ref(false)
onMounted(async () => {
const response = await axios.get('/api/paymentterms')
@@ -95,6 +97,7 @@ watch(invoice,
postalCode: invoice.value.customer?.billingAddress?.postalCode || "",
countryCode: invoice.value.customer?.billingAddress?.countryCode || "",
},
contactSalutation: invoice.value.customer?.contacts && invoice.value.customer.contacts.length > 0 ? invoice.value.customer.contacts[0].salutation : "",
contactFirstName: invoice.value.customer?.contacts && invoice.value.customer.contacts.length > 0 ? invoice.value.customer.contacts[0].firstName : "",
contactLastName: invoice.value.customer?.contacts && invoice.value.customer.contacts.length > 0 ? invoice.value.customer.contacts[0].lastName : "",
paymentTerms: invoice.value.customer?.paymentTerms || paymentTermsData.value.length > 0 ? paymentTermsData.value[2] : null,
@@ -190,24 +193,23 @@ const saveChanges = () => {
const cancelChanges = (event: Event | null) => {
if (isDirty.value) {
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.open = false
}
alert.actionText = "Schließen"
// alert.actionVariant = "destructive"
alert.onAction = () => {
console.log('action')
emit('cancel')
isOpen.value = false
alert.open = false
}
alert.open = true
alert.show(
"Wirklich schließen?",
"Es gibt ungespeicherte Änderungen, die dann verloren gehen.",
{
actionText: "Änderungen verwerfen",
actionVariant: "destructive",
onAction: () => {
emit('cancel')
isOpen.value = false
},
onCancel: () => {
if(!event) return
event.preventDefault()
event.returnValue = true
}
}
)
} else {
emit('cancel')
isOpen.value = false
@@ -230,19 +232,46 @@ const downloadXml = function () {
}
const deleteInvoice = function () {
let confirm = window.confirm('Möchtest Du diese Rechnung wirklich löschen?')
if (!confirm) return
emit('delete', invoice.value?.id)
isOpen.value = false
alert.show(
"Möchtest Du diese Rechnung wirklich löschen?",
(invoice.value?.paymentStatus == "draft") ? null : "Nach GoBD musst Du alle Belege und Daten in unveränderter Form aufbewahren",
{
actionText: "Löschen",
actionVariant: "destructive",
onAction: () => {
emit('delete', invoice.value?.id)
isOpen.value = false
}
}
)
}
const remind = function () {
// await axios call
// make button spin
// success -> set new status and save
// error -> toast
if (!invoice.value) return;
window?.open('/api/invoices/' + invoice.value.id + '/remind', '_blank')?.focus();
alert.show(
"Zahlungserinnerung senden?",
"E-mail an " + invoice.value.customer?.contacts[0].email,
{
actionText: "Senden",
onAction: async () => {
// make button spin and disable button
reminderLoading.value = true
// await axios call
await axios.get('/api/invoices/' + invoice.value.id + '/remind')
.then(function (response) {
toast.success("Zahlungserinnerung gesendet", { description: "daniel@vollstock.de" })
})
.catch(function (error) {
toast.error(error.title, { description: error.message })
})
.finally(() => {
reminderLoading.value = false
})
}
}
)
}
const updateTotalAmount = () => {
@@ -364,7 +393,7 @@ const updateTotalAmount = () => {
<div id="document">
<div id="document-header"
class="block sticky top-0 py-4 bg-slate-100 bg-white dark:bg-neutral-800 z-1 flex items-end gap-12">
class="sticky top-0 py-4 bg-white dark:bg-neutral-800 z-1 flex items-end gap-12">
<div class="grow">
<h1 class="text-xl text-primary-foreground font-bold" v-if="invoice.id > 0">
@@ -483,7 +512,12 @@ const updateTotalAmount = () => {
</Select>
<Button v-if="['due', 'reminded'].includes(invoice.paymentStatus)"
:size="'sm'" :variant="'destructive'" @click="remind">Mahnen</Button>
:size="'sm'" :variant="'destructive'" @click="remind"
:disabled="reminderLoading">
<Loader2 class="h-4 w-4 transition-[width] ease-in-out animate-spin"
:class="{ 'w-0!': !reminderLoading }" />
Mahnen
</Button>
</TableCell>
</TableRow>