From ae960b6a4efc78632d2c6ec9d14faa6c5534fa92 Mon Sep 17 00:00:00 2001 From: Daniel Stock Date: Fri, 14 Nov 2025 11:55:41 +0100 Subject: [PATCH] Show invoice buttons depending of payment status. Fixes #54 --- resources/css/app.css | 48 +- .../DialogCloseButton/DialogCloseButton.vue | 83 ++++ .../js/components/documents/DocumentTable.vue | 6 +- .../js/components/documents/InvoiceDialog.vue | 422 +++++++++--------- .../js/components/documents/LineItemTable.vue | 24 +- resources/js/components/ui/button/index.ts | 4 +- .../ui/send-mail-dialog/SendMailDialog.vue | 6 +- .../js/components/ui/status-badge/index.ts | 4 +- 8 files changed, 356 insertions(+), 241 deletions(-) create mode 100644 resources/js/components/DialogCloseButton/DialogCloseButton.vue diff --git a/resources/css/app.css b/resources/css/app.css index 90f00a7..451e92b 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -39,6 +39,9 @@ @theme inline { --color-destructive: var(--destructive); --color-destructive-foreground: var(--destructive-foreground); + --color-success: var(--success); + --color-success-foreground: var(--success-foreground); + --color-border: var(--border); --color-input: var(--input); --color-ring: var(--ring); @@ -66,17 +69,17 @@ @theme inline { --color-main: var(--main-background); - /* - https://typescale.com/ - Major Third - */ - --text-xs: 0.64rem; - --text-sm: 0.8rem; - --text-base: 1rem; - --text-lg: 1.125rem; - --text-xl: 1.563rem; - --text-2xl: 1.953rem; + /* https://typescale.com/ /* + /* Major Third */ + --text-5xl: 3.815rem; + --text-4xl: 3.052rem; --text-3xl: 2.441rem; + --text-2xl: 1.953rem; + --text-xl: 1.563rem; + --text-lg: 1.25rem; + --text-base: 1rem; + --text-sm: 0.8rem; + --text-xs: 0.64rem; --shadow-arrow: 0 2px 1px rgb(0 0 0 / 0.1) } @@ -108,6 +111,7 @@ @layer utilities { --font-sans: system-ui, sans-serif; font-size: 18px; background-color: var(--sidebar-background); + /* background: linear-gradient(45deg, var(--color-slate-100), var(--color-orange-100)); */ letter-spacing: 0.006em; } } @@ -130,6 +134,8 @@ :root { --accent-foreground: hsl(0 0% 9%); --destructive: var(--color-red-500); --destructive-foreground: hsl(0 0% 98%); + --success: var(--color-lime-400); + --success-foreground: var(--color-foreground); --border: hsl(0 0% 92.8%); --input: var(--color-zinc-100); --ring: hsl(0 0% 3.9%); @@ -154,7 +160,8 @@ :root { --status-paid: var(--color-lime-500); --status-due: var(--color-amber-300); --status-reminded: var(--color-destructive); - + --scrollbar-thumb: --alpha(black / 20%); + --scrollbar-track: transparent; } .dark { @@ -171,10 +178,12 @@ .dark { --secondary-foreground: hsl(0 0% 98%); --muted: var(--color-neutral-700); --muted-foreground: var(--color-neutral-400); - --accent: var(--color-neutral-900); + --accent: oklch(25% 0 0); --accent-foreground: hsl(0 0% 98%); --destructive: var(--color-red-600); --destructive-foreground: var(--color-red-200); + --success: var(--color-lime-900); + --success-foreground: var(--color-lime-400); --border: var(--color-neutral-700); --input: var(--color-neutral-700); --ring: var(--color-neutral-500); @@ -198,11 +207,13 @@ .dark { --status-paid: var(--color-lime-700); --status-due: var(--color-amber-900); --status-reminded: var(--color-destructive-foreground); + --scrollbar-thumb: --alpha(white / 35%); } @layer base { * { @apply border-border outline-ring/50; + scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track); } body { @@ -222,4 +233,17 @@ @layer components { .is-mac .visible-mac { display: inherit !important; } +} + +@layer utilities { + + /* Remove default dialog close button */ + [data-slot=dialog-content] button.ring-offset-background { + display: none; + } + + /* Backdrop */ + [data-slot=dialog-overlay] { + backdrop-filter: blur(var(--blur-sm)); + } } \ No newline at end of file diff --git a/resources/js/components/DialogCloseButton/DialogCloseButton.vue b/resources/js/components/DialogCloseButton/DialogCloseButton.vue new file mode 100644 index 0000000..bb69ac3 --- /dev/null +++ b/resources/js/components/DialogCloseButton/DialogCloseButton.vue @@ -0,0 +1,83 @@ + + + + + \ No newline at end of file diff --git a/resources/js/components/documents/DocumentTable.vue b/resources/js/components/documents/DocumentTable.vue index 9856b66..a990974 100644 --- a/resources/js/components/documents/DocumentTable.vue +++ b/resources/js/components/documents/DocumentTable.vue @@ -120,7 +120,7 @@ const calcTaxes = (amount: number) => { - + @@ -217,8 +217,8 @@ const calcTaxes = (amount: number) => { } .document-table td { - padding-top: 1em !important; - padding-bottom: 1em !important; + padding-top: 1.125em !important; + padding-bottom: 1.125em !important; } .document-table tfoot tr:first-child td { diff --git a/resources/js/components/documents/InvoiceDialog.vue b/resources/js/components/documents/InvoiceDialog.vue index 23651d5..39881d2 100644 --- a/resources/js/components/documents/InvoiceDialog.vue +++ b/resources/js/components/documents/InvoiceDialog.vue @@ -24,15 +24,14 @@ import { Input } from '@/components/ui/input'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { StatusBadge, statusBadgeLabels, statusTextStyle, 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, Loader, Loader2 } from "lucide-vue-next" +import { Eye, FileText, Trash2, BookUser, User, CodeXml, CalendarIcon, MessageCircleQuestion, Loader2, Ellipsis, Check, FileCheck, FileX, Ban } 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" import { SendMailDialog } from "../ui/send-mail-dialog" +import { Kbd, KbdGroup } from "../ui/kbd" +import DialogClose from "../ui/dialog/DialogClose.vue" +import DialogCloseButton from "../DialogCloseButton/DialogCloseButton.vue" const props = defineProps<{ invoiceData: Invoice | null, @@ -182,6 +181,14 @@ watch(importContact, { deep: true } ) +const billingContactEmail = computed(() => { + // TODO: use e-mail from billing data if set + // and fallback to primary contact email + if (invoice.value?.customer && invoice.value?.customer.contacts[0]) + return invoice.value?.customer?.contacts[0].email + else return "" +}) + onUpdated(() => { isLoading.value = false; // console.log("onUpdated", "Dirty: " + isDirty.value, "loading: " + isLoading.value) @@ -190,7 +197,7 @@ onUpdated(() => { const saveChanges = () => { if (invoice.value) { emit('save', invoice.value) - isOpen.value = false + // isOpen.value = false } } @@ -201,7 +208,6 @@ const cancelChanges = (event: Event | null) => { "Es gibt ungespeicherte Änderungen, die dann verloren gehen.", { actionText: "Änderungen verwerfen", - actionVariant: "destructive", onAction: () => { emit('cancel') isOpen.value = false @@ -234,6 +240,11 @@ const downloadXml = function () { window?.open('/invoice/' + invoice.value.id + '/xml'); } +const issueInvoice = function () { + if (!invoice.value) return; + invoice.value.paymentStatus = 'issued' +} + const deleteInvoice = function () { alert.show( "Möchtest Du diese Rechnung wirklich löschen?", @@ -249,34 +260,14 @@ const deleteInvoice = function () { ) } +const cancelInvoice = function () { + if (!invoice.value) return; + invoice.value.paymentStatus = 'cancelled' +} + const openReminderDialog = function () { if (!invoice.value) return - reminderDialogOpen.value = true - - // 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 sendReminder = async function (recipient: string | undefined) { @@ -314,141 +305,210 @@ const updateTotalAmount = () => { - \ No newline at end of file + \ No newline at end of file diff --git a/resources/js/components/documents/LineItemTable.vue b/resources/js/components/documents/LineItemTable.vue index 274106f..f6b6e67 100644 --- a/resources/js/components/documents/LineItemTable.vue +++ b/resources/js/components/documents/LineItemTable.vue @@ -1,5 +1,5 @@ - + - \ No newline at end of file diff --git a/resources/js/components/ui/status-badge/index.ts b/resources/js/components/ui/status-badge/index.ts index 175a2e1..05346b8 100644 --- a/resources/js/components/ui/status-badge/index.ts +++ b/resources/js/components/ui/status-badge/index.ts @@ -13,7 +13,7 @@ export const statusBadgeVariants = cva( issued: "bg-transparent border-sky-200 text-sky-600 dark:bg-sky-800 dark:text-sky-300 dark:border-0", paid: - "border-none bg-lime-400 dark:bg-lime-900 dark:text-lime-400", + "border-none bg-success text-success-foreground", due: "font-bold border-none bg-amber-300 text-amber-800 dark:bg-amber-900 dark:text-amber-500", reminded: @@ -25,7 +25,7 @@ export const statusBadgeVariants = cva( size: { default: '', sm: 'lg:aspect-1/1 lg:p-1, lg:rounded-full lg:w-auto lg:w-1 text-transparent dark:text-transparent', - lg: '', + lg: 'text-sm px-6!', icon: '', }, },