313 lines
16 KiB
Vue
313 lines
16 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed, watch, onMounted, onUpdated, useTemplateRef } from "vue"
|
|
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from '@/components/ui/dialog';
|
|
import { Button } from '@/components/ui/button'
|
|
import { Customer } from '@/types'
|
|
import { Check, Ellipsis, Trash } from "lucide-vue-next"
|
|
|
|
|
|
const props = defineProps<{
|
|
modelValue: boolean
|
|
customerData: Customer | null,
|
|
}>()
|
|
|
|
const emit = defineEmits(['update:modelValue', 'update:open', 'save', 'cancel', 'delete'])
|
|
|
|
const customer = ref<Customer | null>(props.customerData)
|
|
const isDirty = ref(false);
|
|
const isLoading = ref(false);
|
|
|
|
const isOpen = computed({
|
|
get: () => props.modelValue,
|
|
set: (value) => {
|
|
emit('update:modelValue', value)
|
|
}
|
|
})
|
|
|
|
// watch for new external invoice data
|
|
watch(() => props.customerData as Customer,
|
|
(newValue, oldValue) => {
|
|
if (newValue == oldValue) return
|
|
customer.value = newValue
|
|
|
|
// Set initial state for a newly opened document
|
|
isDirty.value = false
|
|
isLoading.value = true
|
|
|
|
// console.log("customerData", "Dirty: " + isDirty.value, "loading: " + isLoading.value)
|
|
}
|
|
)
|
|
|
|
watch(customer, (newValue) => {
|
|
// console.log(newValue)
|
|
})
|
|
|
|
const saveChanges = () => {
|
|
// if (invoice.value) {
|
|
// emit('save', invoice.value)
|
|
// isOpen.value = false
|
|
// }
|
|
}
|
|
|
|
const cancelChanges = (event: Event | null) => {
|
|
// if (isDirty.value) {
|
|
// alert.value.title = "Wirklich schließen?"
|
|
// alert.value.message = "Es gibt ungespeicherte Änderungen, die dann verloren gehen."
|
|
// alert.value.cancelText = "Abbrechen"
|
|
// alert.value.onCancel = () => {
|
|
// event?.preventDefault()
|
|
// event.returnValue = true
|
|
// alert.value.open = false
|
|
// }
|
|
// alert.value.confirmText = "Schließen"
|
|
// alert.value.onConfirm = () => {
|
|
// emit('cancel')
|
|
// isOpen.value = false
|
|
// alert.value.open = false
|
|
// }
|
|
// alert.value.open = true
|
|
// } else
|
|
{
|
|
emit('cancel')
|
|
isOpen.value = false
|
|
}
|
|
}
|
|
|
|
const handleLogoUpload = (event: Event) => {
|
|
const target = event.target as HTMLInputElement;
|
|
if (target.files && target.files[0]) {
|
|
const file = target.files[0];
|
|
|
|
// Hier könntest du die Datei validieren (Größe, Typ, etc.)
|
|
if (file.size > 2 * 1024 * 1024) { // 2MB
|
|
alert('Die Datei ist zu groß. Maximal 2MB erlaubt.');
|
|
return;
|
|
}
|
|
|
|
// Hier könntest du die Datei hochladen und den Pfad speichern
|
|
// customer.value.logo = ... // Pfad zur hochgeladenen Datei
|
|
|
|
// Für die Vorschau:
|
|
const reader = new FileReader();
|
|
reader.onload = (e) => {
|
|
if (customer.value) {
|
|
customer.value.logo = e.target?.result as string;
|
|
}
|
|
};
|
|
reader.readAsDataURL(file);
|
|
}
|
|
};
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<Dialog id="customer-dialog" v-model:open="isOpen">
|
|
<DialogContent
|
|
class="sm:max-w-[min((100%-2rem),1152px)] grid-rows-[auto_minmax(0,1fr)_auto] p-0 h-[calc(100dvh-2rem)]"
|
|
@escapeKeyDown="cancelChanges" @interactOutside="cancelChanges">
|
|
|
|
<DialogHeader class="px-3 pt-3 flex flex-row justify-end">
|
|
<DialogTitle class="sr-only">Kunde</DialogTitle>
|
|
|
|
<div v-if="customer && customer.id > 0" class="hidden md:flex mr-4 gap-2">
|
|
<Button :size="'sm'" variant="action" @click="" class="hidden" :class="{ flex: isDirty }">
|
|
<Check :strokeWidth="1.5" class="text-current" />
|
|
<span>Speichern</span>
|
|
</Button>
|
|
<Button :size="'sm'" variant="destructive" @click="">
|
|
<Trash :strokeWidth="1.5" class="text-current" />
|
|
<span>Löschen</span>
|
|
</Button>
|
|
<Button :size="'sm'" variant="ghost" @click="">
|
|
<Ellipsis class="text-muted-foreground" />
|
|
</Button>
|
|
</div>
|
|
</DialogHeader>
|
|
|
|
|
|
<div class="overflow-y-auto px-6">
|
|
<div
|
|
class="block sticky top-0 py-4 bg-slate-100 bg-white dark:bg-neutral-800 z-1 flex items-end gap-12">
|
|
<div class="grow">
|
|
<DialogTitle class="text-xl text-primary-foreground font-bold">Edit profile</DialogTitle>
|
|
<DialogDescription>
|
|
Make changes to your profile here. Click save when you're done.
|
|
</DialogDescription>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div class="flex-none md:flex gap-12 mt-6 p-6 bg-slate-100 dark:bg-neutral-900 rounded-lg">
|
|
<div class="space-y-4 w-full">
|
|
|
|
<!-- Logo Upload -->
|
|
<div>
|
|
<div class="flex items-center gap-4">
|
|
<div v-if="customer.logo" class="w-24 h-24 border rounded-md overflow-hidden">
|
|
<img :src="'/storage/uploads/' + customer.logo" alt="Aktuelles Logo"
|
|
class="w-full h-full object-contain">
|
|
</div>
|
|
|
|
<!-- Logo hochladen -->
|
|
<div class="flex-1">
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Neues
|
|
Logo hochladen</label>
|
|
<input type="file" @change="handleLogoUpload" accept="image/*"
|
|
class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-neutral-800 dark:border-neutral-700">
|
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Max. 2MB, JPG, PNG oder GIF
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Firmenname -->
|
|
<div>
|
|
<label
|
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Firmenname</label>
|
|
<input type="text" v-model="customer.companyName"
|
|
class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-neutral-800 dark:border-neutral-700">
|
|
</div>
|
|
|
|
<!-- Kunden-Nummer -->
|
|
<div>
|
|
<label
|
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Kunden-Nummer</label>
|
|
<input type="text" v-model="customer.customerNr"
|
|
class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-neutral-800 dark:border-neutral-700">
|
|
</div>
|
|
|
|
<!-- USt-IdNr -->
|
|
<div>
|
|
<label
|
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">USt-IdNr</label>
|
|
<input type="text" v-model="customer.vatId"
|
|
class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-neutral-800 dark:border-neutral-700">
|
|
</div>
|
|
|
|
<!-- Website -->
|
|
<div>
|
|
<label
|
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Website</label>
|
|
<input type="url" v-model="customer.url"
|
|
class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-neutral-800 dark:border-neutral-700">
|
|
</div>
|
|
|
|
<!-- Telefon -->
|
|
<div>
|
|
<label
|
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Telefon</label>
|
|
<input type="tel" v-model="customer.phone"
|
|
class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-neutral-800 dark:border-neutral-700">
|
|
</div>
|
|
|
|
<!-- Rechnungsadresse -->
|
|
<div class="mt-6">
|
|
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-3">Rechnungsadresse</h3>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label
|
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Straße</label>
|
|
<input type="text" v-model="customer.billingAddress.lineOne"
|
|
class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-neutral-800 dark:border-neutral-700">
|
|
</div>
|
|
|
|
<div>
|
|
<label
|
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Zusatzzeile</label>
|
|
<input type="text" v-model="customer.billingAddress.lineTwo"
|
|
class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-neutral-800 dark:border-neutral-700">
|
|
</div>
|
|
|
|
<div>
|
|
<label
|
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">PLZ</label>
|
|
<input type="text" v-model="customer.billingAddress.postalCode"
|
|
class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-neutral-800 dark:border-neutral-700">
|
|
</div>
|
|
|
|
<div>
|
|
<label
|
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Stadt</label>
|
|
<input type="text" v-model="customer.billingAddress.city"
|
|
class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-neutral-800 dark:border-neutral-700">
|
|
</div>
|
|
|
|
<div>
|
|
<label
|
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Land</label>
|
|
<input type="text" v-model="customer.billingAddress.country"
|
|
class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-neutral-800 dark:border-neutral-700">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Zahlungsbedingungen -->
|
|
<div class="mt-6">
|
|
<h3 class="text-lg font-medium text-gray-900 dark:text-gray-100 mb-3">Zahlungsbedingungen
|
|
</h3>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label
|
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Bezeichnung</label>
|
|
<input type="text" v-model="customer.paymentTerms.name"
|
|
class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-neutral-800 dark:border-neutral-700">
|
|
</div>
|
|
|
|
<div>
|
|
<label
|
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Beschreibung</label>
|
|
<input type="text" v-model="customer.paymentTerms.description"
|
|
class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-neutral-800 dark:border-neutral-700">
|
|
</div>
|
|
|
|
<div>
|
|
<label
|
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Tage</label>
|
|
<input type="number" v-model="customer.paymentTerms.days"
|
|
class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-neutral-800 dark:border-neutral-700">
|
|
</div>
|
|
|
|
<div class="flex items-center mt-5">
|
|
<input type="checkbox" v-model="customer.paymentTerms.isFixed"
|
|
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded dark:bg-neutral-800 dark:border-neutral-700">
|
|
<label class="ml-2 block text-sm text-gray-700 dark:text-gray-300">Fix</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Status -->
|
|
<div class="mt-6">
|
|
<label
|
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Status</label>
|
|
<select v-model="customer.status"
|
|
class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-neutral-800 dark:border-neutral-700">
|
|
<option value="active">Aktiv</option>
|
|
<option value="inactive">Inaktiv</option>
|
|
<option value="prospect">Interessent</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Notizen -->
|
|
<div class="mt-6">
|
|
<label
|
|
class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Notizen</label>
|
|
<textarea v-model="customer.notes" rows="4"
|
|
class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-neutral-800 dark:border-neutral-700"></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="px-4 mt-6">
|
|
Contacts hier
|
|
</div>
|
|
</div>
|
|
|
|
<DialogFooter></DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</template>
|
|
|
|
<style></style> |