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:
@@ -24,13 +24,14 @@ import { Input } from '@/components/ui/input';
|
|||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'
|
||||||
import { StatusBadge, statusBadgeLabels, statusBadgeTextColor, StatusBadgeVariants } from '@/components/ui/status-badge'
|
import { StatusBadge, statusBadgeLabels, statusBadgeTextColor, StatusBadgeVariants } from '@/components/ui/status-badge'
|
||||||
import LineItemTable from '@/components/documents/LineItemTable.vue'
|
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 { alertStore } from "@/stores/alertStore"
|
||||||
import { Calendar } from "@/components/ui/calendar"
|
import { Calendar } from "@/components/ui/calendar"
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
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 { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger, } from '@/components/ui/sheet'
|
||||||
import { GrowingTextarea } from '../ui/growing-textarea'
|
import { GrowingTextarea } from '../ui/growing-textarea'
|
||||||
|
import { toast } from "vue-sonner"
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
invoiceData: Invoice | null,
|
invoiceData: Invoice | null,
|
||||||
@@ -47,6 +48,7 @@ const isLoading = ref(false);
|
|||||||
const importContact = ref(newContact() as Contact)
|
const importContact = ref(newContact() as Contact)
|
||||||
const importCustomer = ref(newCustomer() as Customer)
|
const importCustomer = ref(newCustomer() as Customer)
|
||||||
const alert = alertStore()
|
const alert = alertStore()
|
||||||
|
const reminderLoading = ref(false)
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const response = await axios.get('/api/paymentterms')
|
const response = await axios.get('/api/paymentterms')
|
||||||
@@ -95,6 +97,7 @@ watch(invoice,
|
|||||||
postalCode: invoice.value.customer?.billingAddress?.postalCode || "",
|
postalCode: invoice.value.customer?.billingAddress?.postalCode || "",
|
||||||
countryCode: invoice.value.customer?.billingAddress?.countryCode || "",
|
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 : "",
|
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 : "",
|
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,
|
paymentTerms: invoice.value.customer?.paymentTerms || paymentTermsData.value.length > 0 ? paymentTermsData.value[2] : null,
|
||||||
@@ -190,24 +193,23 @@ const saveChanges = () => {
|
|||||||
|
|
||||||
const cancelChanges = (event: Event | null) => {
|
const cancelChanges = (event: Event | null) => {
|
||||||
if (isDirty.value) {
|
if (isDirty.value) {
|
||||||
alert.title = "Wirklich schließen?"
|
alert.show(
|
||||||
alert.message = "Es gibt ungespeicherte Änderungen, die dann verloren gehen."
|
"Wirklich schließen?",
|
||||||
alert.cancelText = "Abbrechen"
|
"Es gibt ungespeicherte Änderungen, die dann verloren gehen.",
|
||||||
alert.onCancel = () => {
|
{
|
||||||
console.log('cancel')
|
actionText: "Änderungen verwerfen",
|
||||||
event?.preventDefault()
|
actionVariant: "destructive",
|
||||||
event.returnValue = true
|
onAction: () => {
|
||||||
alert.open = false
|
emit('cancel')
|
||||||
}
|
isOpen.value = false
|
||||||
alert.actionText = "Schließen"
|
},
|
||||||
// alert.actionVariant = "destructive"
|
onCancel: () => {
|
||||||
alert.onAction = () => {
|
if(!event) return
|
||||||
console.log('action')
|
event.preventDefault()
|
||||||
emit('cancel')
|
event.returnValue = true
|
||||||
isOpen.value = false
|
}
|
||||||
alert.open = false
|
}
|
||||||
}
|
)
|
||||||
alert.open = true
|
|
||||||
} else {
|
} else {
|
||||||
emit('cancel')
|
emit('cancel')
|
||||||
isOpen.value = false
|
isOpen.value = false
|
||||||
@@ -230,19 +232,46 @@ const downloadXml = function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deleteInvoice = function () {
|
const deleteInvoice = function () {
|
||||||
let confirm = window.confirm('Möchtest Du diese Rechnung wirklich löschen?')
|
alert.show(
|
||||||
if (!confirm) return
|
"Möchtest Du diese Rechnung wirklich löschen?",
|
||||||
emit('delete', invoice.value?.id)
|
(invoice.value?.paymentStatus == "draft") ? null : "Nach GoBD musst Du alle Belege und Daten in unveränderter Form aufbewahren",
|
||||||
isOpen.value = false
|
{
|
||||||
|
actionText: "Löschen",
|
||||||
|
actionVariant: "destructive",
|
||||||
|
onAction: () => {
|
||||||
|
emit('delete', invoice.value?.id)
|
||||||
|
isOpen.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const remind = function () {
|
const remind = function () {
|
||||||
// await axios call
|
|
||||||
// make button spin
|
|
||||||
// success -> set new status and save
|
|
||||||
// error -> toast
|
|
||||||
if (!invoice.value) return;
|
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 = () => {
|
const updateTotalAmount = () => {
|
||||||
@@ -364,7 +393,7 @@ const updateTotalAmount = () => {
|
|||||||
|
|
||||||
<div id="document">
|
<div id="document">
|
||||||
<div id="document-header"
|
<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">
|
<div class="grow">
|
||||||
|
|
||||||
<h1 class="text-xl text-primary-foreground font-bold" v-if="invoice.id > 0">
|
<h1 class="text-xl text-primary-foreground font-bold" v-if="invoice.id > 0">
|
||||||
@@ -483,7 +512,12 @@ const updateTotalAmount = () => {
|
|||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
<Button v-if="['due', 'reminded'].includes(invoice.paymentStatus)"
|
<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>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|
||||||
|
|||||||
@@ -70,13 +70,11 @@ onMounted(() => {
|
|||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>{{ alert.title }}</AlertDialogTitle>
|
<AlertDialogTitle>{{ alert.title }}</AlertDialogTitle>
|
||||||
<AlertDialogDescription>{{ alert.message }}</AlertDialogDescription>
|
<AlertDialogDescription v-if="alert.message">{{ alert.message }}</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<Button v-if="alert.onCancel" @click="alert.onCancel">{{ alert.cancelText }}</Button>
|
<Button @click="alert.cancel">{{ alert.cancelText }}</Button>
|
||||||
<Button :variant="alert.actionVariant" v-if="alert.onAction" @click="alert.onAction">{{
|
<Button :variant="alert.actionVariant" @click="alert.action">{{ alert.actionText }}</Button>
|
||||||
alert.actionText
|
|
||||||
}}</Button>
|
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
|
|||||||
@@ -1,11 +1,20 @@
|
|||||||
|
import { options } from '@coders-tm/vue-number-format';
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
|
export interface AlertOptions {
|
||||||
|
cancelText?: string;
|
||||||
|
onCancel?: () => void;
|
||||||
|
actionText?: string;
|
||||||
|
actionVariant?: "action" | "destructive";
|
||||||
|
onAction?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
export const alertStore = defineStore('alert', {
|
export const alertStore = defineStore('alert', {
|
||||||
state: () => {
|
state: () => {
|
||||||
return {
|
return {
|
||||||
open: false,
|
open: false,
|
||||||
title: "",
|
title: "",
|
||||||
message: "",
|
message: "" as string | null,
|
||||||
cancelText: "Abbrechen",
|
cancelText: "Abbrechen",
|
||||||
onCancel: () => { },
|
onCancel: () => { },
|
||||||
actionText: "Ok",
|
actionText: "Ok",
|
||||||
@@ -13,5 +22,25 @@ export const alertStore = defineStore('alert', {
|
|||||||
onAction: () => { }
|
onAction: () => { }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {}
|
|
||||||
|
actions: {
|
||||||
|
show(title: string, message: string | null, options?: AlertOptions) {
|
||||||
|
this.title = title;
|
||||||
|
this.message = message;
|
||||||
|
this.cancelText = options?.cancelText ? options.cancelText : "Abbrechen"
|
||||||
|
this.onCancel = options?.onCancel ? options.onCancel : () => { }
|
||||||
|
this.actionText = options?.actionText ? options.actionText : "Ok"
|
||||||
|
this.actionVariant = options?.actionVariant ? options.actionVariant : "action"
|
||||||
|
this.onAction = options?.onAction ? options.onAction : () => { }
|
||||||
|
this.open = true;
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
this.onCancel();
|
||||||
|
this.open = false;
|
||||||
|
},
|
||||||
|
action() {
|
||||||
|
this.onAction();
|
||||||
|
this.open = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
+2
-2
@@ -28,8 +28,8 @@
|
|||||||
|
|
||||||
Route::get('/invoices/{id}/remind', function ($id) {
|
Route::get('/invoices/{id}/remind', function ($id) {
|
||||||
$invoice = InvoiceController::single($id);
|
$invoice = InvoiceController::single($id);
|
||||||
// Mail::to('daniel@vollstock.de')->send(new Reminder($invoice));
|
Mail::to('daniel@vollstock.de')->send(new Reminder($invoice));
|
||||||
return new Reminder($invoice);
|
// return new Reminder($invoice);
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::get('/offers/{id}/confirm', function ($id) {
|
Route::get('/offers/{id}/confirm', function ($id) {
|
||||||
|
|||||||
Reference in New Issue
Block a user