Add InvoiceService and initial invoice dashboard widget

This commit is contained in:
2026-05-27 11:12:26 +02:00
parent 56bf75bf74
commit 167bee78f8
7 changed files with 198 additions and 24 deletions
+35 -19
View File
@@ -2,31 +2,38 @@
import Heading from '@/components/Heading.vue';
import { onMounted, ref } from "vue"
import AppLayout from '@/layouts/AppLayout.vue';
import { Trophy, UserCheck2, X, ChevronRight } from 'lucide-vue-next';
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from '@/components/ui/card'
import Button from '@/components/ui/crm-button/Button.vue';
import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card'
import { invoices } from '@/routes';
import { toRoundedCurrency } from '@/lib/utils'
import { toRoundedCurrency, toLocalDate } from '@/lib/utils'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '@/components/ui/tooltip'
import { Label } from '@/components/ui/label'
import { Switch } from '@/components/ui/switch'
import { Link, usePage } from '@inertiajs/vue3';
import { usePage, router } from '@inertiajs/vue3';
import axios, { AxiosError } from "axios";
import { toast } from "vue-sonner";
import { AppPageProps, Todo } from "@/types";
import { SalesStatistics, Todo, Invoice } from "@/types";
import Todos from '@/components/Todos.vue';
import InvoiceService from '@/services/InvoiceService';
import { Table, TableRow, TableCell } from '@/components/ui/crm-table';
const salesStatistics = ref({
interface Props {
dueInvoices: Invoice[];
}
const props = defineProps<Props>();
const salesStatistics = ref<SalesStatistics>({
year: new Date().getFullYear(),
totalRevenue: 0,
paid: 0,
paidPercent: 0,
draft: 0,
draftPercent: 0,
issued: 0,
issuedPercent: 0,
due: 0,
duePercent: 0,
reminded: 0,
remindedPercent: 0
})
const salesTarget = ref(60000) // TODO: aus settings
const todos = ref<Todo[]>([])
const showCompleted = ref(false)
@@ -48,8 +55,7 @@ onMounted(async () => {
// Load sales statistics
try {
let response = await axios.get('/api/invoices/salesStatistics')
salesStatistics.value = response.data
salesStatistics.value = await InvoiceService.getSalesStatistics()
} catch (error) {
toast.error('Fehler beim Laden der Daten', { description: (error as AxiosError).message })
}
@@ -67,7 +73,7 @@ onMounted(async () => {
<!-- Nachrichten -->
<Card class="break-inside-avoid mb-12">
<!-- <Card class="break-inside-avoid mb-12">
<CardHeader>
<CardTitle>Benachrichtigungen</CardTitle>
<CardDescription>Card Description</CardDescription>
@@ -104,11 +110,11 @@ onMounted(async () => {
<CardFooter>
<Button>Alles leeren</Button>
</CardFooter>
</Card>
</Card> -->
<!-- Aufgaben -->
<Card class="break-inside-avoid mb-8">
<Card class="break-inside-avoid mb-8 ">
<CardHeader class="flex justify-between flex-wrap">
<div>
<CardTitle class="mb-1.5">Aufgaben</CardTitle>
@@ -120,7 +126,7 @@ onMounted(async () => {
<Label for="show-completed" class="text-muted-foreground">Erledigte</Label>
</div>
</CardHeader>
<CardContent>
<CardContent class="max-h-[600px] overflow-y-auto">
<Todos :modelValue="todos" :show-completed="showCompleted" :show-todoable="true" />
</CardContent>
</Card>
@@ -132,11 +138,21 @@ onMounted(async () => {
<CardDescription>Card Description</CardDescription>
</CardHeader>
<CardContent>
<Table class="text-sm">
<TableRow v-for="invoice in props.dueInvoices" @mouseover="router.prefetch(invoices())" @click="router.visit(invoices())">
<TableCell class="align-top">
<span class="whitespace-nowrap" v-if="invoice.dueDate">{{ toLocalDate(invoice.dueDate)
}}</span>
</TableCell>
<TableCell>
<span class="font-semibold">{{ invoice.nr }}</span> {{ invoice.title }}<br>
<span class="text-muted-foreground">{{ invoice.billingData?.companyName }}</span>
</TableCell>
<TableCell class="align-top text-right">{{ toRoundedCurrency(invoice.totalAmount) }}
</TableCell>
</TableRow>
</Table>
</CardContent>
<CardFooter>
<Link :href="invoices()" prefetch><Button>Zu den Rechnungen</Button></Link>
</CardFooter>
</Card>