Add initial Code

This commit is contained in:
2025-10-20 08:57:51 +02:00
parent d204098d8e
commit 9da301c4f1
447 changed files with 34393 additions and 0 deletions
+152
View File
@@ -0,0 +1,152 @@
<!-- TODO: Firmen-Logos -->
<!-- TODO: In Zwischenablage Button für Adresse -->
<!-- TODO: Hover-Menü für Kontakte: Anrufen, E-Mail schreiben -->
<!-- TODO: Kundennummer einführen (mit Schema, zunächst Pseudoschema, später konfigurierbar) -->
<script setup lang="ts">
import AppLayout from '@/layouts/AppLayout.vue'
import { customers } from '@/routes'
import { Address, type BreadcrumbItem } from '@/types'
import { Head } from '@inertiajs/vue3'
import { ref, onMounted, computed, useTemplateRef } from 'vue'
import api from '@/axios'
import { Customer, Contact } from '@/types'
import { randomInt, bgColorForString } from '@/lib/utils'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import { Copy, Delete, Phone, Plus, Search } from "lucide-vue-next"
import Fuse from 'fuse.js';
import { getInitials } from '@/composables/useInitials';
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from '@/components/ui/card'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
const breadcrumbs: BreadcrumbItem[] = [
{
title: 'Kunden',
href: customers().url,
},
]
const customersData = ref([] as Customer[])
const searchQuery = ref('')
const searchField = ref()
onMounted(async () => {
try {
const response = await api.get('/customers');
customersData.value = (response.data as Customer[]).toSorted((a, b) => a.companyName.localeCompare(b.companyName));;
searchField.value = document.getElementById('search')
searchField.value.focus()
} catch (error) {
// TODO: toast
console.log(error);
}
})
const fuse = computed(() => {
return new Fuse(customersData.value, {
keys: ['companyName', 'firstName', 'lastName', 'email', 'phone'],
});
})
const filteredCustomers = computed(() => {
if (!searchQuery.value) {
return customersData.value;
}
return fuse.value.search(searchQuery.value).map(result => result.item);
})
const addressToClipbard = async function (address: Address) {
try {
await navigator.clipboard.writeText(
address.lineOne + '\n' +
(address.lineTwo ? address.lineTwo + '\n' : '') +
address.postalCode + ' ' + address.city
)
} catch (error) {
if (error instanceof Error)
console.error(error.message)
}
}
</script>
<template>
<Head title="Dashboard" />
<AppLayout :breadcrumbs="breadcrumbs">
<div class="flex h-full flex-1 flex-col gap-4 overflow-x-auto p-4 bg-slate-50 dark:bg-neutral-900/40">
<div class="flex">
<div class="relative w-full max-w-sm items-center mx-auto mb-6">
<Input ref="search-field" id="search" type="text" placeholder="Suchen" class="px-8"
v-model="searchQuery" autofocus />
<span class="absolute start-0 inset-y-0 flex items-center justify-center px-2">
<Search class="size-4 text-muted-foreground" :stroke-width="1.5" />
</span>
<span class="absolute end-0 inset-y-0 flex items-center justify-center px-0">
<Button :size="'sm'" :variant="'ghost'" @click="searchQuery = ''; searchField.focus()">
<Delete class="size-4 text-muted-foreground" :stroke-width="1.5" />
</Button>
</span>
</div>
<Button :size="'sm'" :variant="'action'" @click="newCustomer">
<Plus /> Neu
</Button>
</div>
<div class="columns-xs gap-4">
<Card v-for="customer in filteredCustomers" :key="customer.id"
class="mb-4 shadow-xl break-inside-avoid hover:bg-accent">
<CardHeader>
<CardTitle>{{ customer.companyName }}</CardTitle>
<CardDescription>Kategorie / Umsatz</CardDescription>
</CardHeader>
<CardContent class="text-sm">
<address class="relative not-italic my-2">
{{ customer.billingAddress.lineOne }}<br />
{{ customer.billingAddress.lineTwo }}<br v-if="customer.billingAddress.lineTwo" />
{{ customer.billingAddress.postalCode }} {{ customer.billingAddress.city }}
<Button class="absolute end-0 top-0" size="sm" variant="ghost"
@click="addressToClipbard(customer.billingAddress)">
<Copy :stroke-width="1.5" />
</Button>
</address>
<a href="https://www.tooloop.de">www.tooloop.de</a><br />
<a href="tel://+4982165079983">+49 821 650799-83</a><br />
</CardContent>
<CardFooter>
<TooltipProvider>
<Tooltip v-for="contact in customer.contacts">
<TooltipTrigger>
<Avatar class="mr-2 md:-mr-2 border-2 size-10 md:size-12">
<AvatarImage :src="contact.avatar" />
<AvatarFallback
:class="bgColorForString(getInitials(contact.firstName + ' ' + contact.lastName))">
{{ getInitials(contact.firstName + ' ' + contact.lastName) }}
</AvatarFallback>
</Avatar>
</TooltipTrigger>
<TooltipContent>
<p>{{ contact.firstName + ' ' + contact.lastName }}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</CardFooter>
</Card>
</div>
</div>
</AppLayout>
</template>