Add initial Code
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
<!-- TODO: Mengenfeld Komma als decimal point -->
|
||||
<!-- Enter in LineItem = neue Zeile -->
|
||||
<script setup lang="ts">
|
||||
|
||||
import { ref, watch, HTMLAttributes } from 'vue'
|
||||
import draggable from 'vuedraggable';
|
||||
import { cn, toCurrency } from '@/lib/utils';
|
||||
import { LineItem } from '@/types';
|
||||
import { newLineItem } from '@/types/index.d'
|
||||
import { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, } from '@/components/ui/table';
|
||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue, } from "@/components/ui/select"
|
||||
import { NumberField, NumberFieldContent, NumberFieldDecrement, NumberFieldIncrement, NumberFieldInput, } from '@/components/ui/number-field'
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { CirclePlus, GripVertical, Trash2, Plus, TextSelect } from 'lucide-vue-next';
|
||||
import Textarea from '../ui/textarea/Textarea.vue';
|
||||
import Button from '../ui/button/Button.vue';
|
||||
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, } from '@/components/ui/empty'
|
||||
import NumberInput from '../ui/number-input/NumberInput.vue';
|
||||
import { GrowingTextarea } from '../ui/growing-textarea';
|
||||
|
||||
const props = defineProps<{
|
||||
lineItems: LineItem[],
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:lineItems', value: LineItem[]): void
|
||||
}>()
|
||||
|
||||
const units = ref(['Stück', 'Stunden', 'Tage', 'pauschal'])
|
||||
const items = ref(props.lineItems.sort(function (a, b) { return a.position - b.position })) // items only uses props.lineItems as the initial value;
|
||||
watch(items, (newItems) => {
|
||||
emit('update:lineItems', newItems)
|
||||
}, { deep: true })
|
||||
|
||||
const newItem = () => {
|
||||
const position = items.value.length + 1
|
||||
let item = newLineItem()
|
||||
item.position = position
|
||||
items.value.push(item)
|
||||
}
|
||||
|
||||
const deleteItem = (lineItem: LineItem) => {
|
||||
const index = items.value.indexOf(lineItem)
|
||||
if (index > -1) {
|
||||
items.value.splice(index, 1)
|
||||
}
|
||||
recalculatePositions()
|
||||
}
|
||||
|
||||
const recalculatePositions = () => {
|
||||
for (let i = 0; i < items.value.length; i++) {
|
||||
items.value[i].position = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<div :class="cn(props.class)">
|
||||
|
||||
<div class="backdrop-blur mt-8 bg-background/80 sticky top-[100px] z-1">
|
||||
<Table class="table-fixed">
|
||||
<TableHeader>
|
||||
<TableRow class="hover:bg-transparent dark:hover:bg-transparent border-b-1">
|
||||
<TableHead class="w-6 px-0"></TableHead>
|
||||
<TableHead class="w-8 px-0 text-center">Pos.</TableHead>
|
||||
<TableHead>Posten</TableHead>
|
||||
<TableHead class="w-1/8">Einh.</TableHead>
|
||||
<TableHead class="w-20 text-center">Menge</TableHead>
|
||||
<TableHead class="w-1/8 text-right pr-5">Einzel</TableHead>
|
||||
<TableHead class="w-1/8 text-right">Total</TableHead>
|
||||
<TableHead class="w-16"></TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<Table v-if="items.length > 0" class="table-fixed">
|
||||
|
||||
<draggable v-model="items" tag="tbody" item-key="position" handle=".handle" ghostClass="ghost"
|
||||
@end="recalculatePositions">
|
||||
<template #item="{ element }">
|
||||
|
||||
<TableRow>
|
||||
<TableCell class="px-0 handle px-1 cursor-move w-6">
|
||||
<GripVertical :size="18" :strokeWidth="1.5" class="text-muted-foreground" />
|
||||
</TableCell>
|
||||
|
||||
<!-- Pos. -->
|
||||
<TableCell class="text-center position w-8">{{ element.position }}</TableCell>
|
||||
|
||||
<!-- Posten -->
|
||||
<TableCell>
|
||||
<Input v-model="element.title" placeholder="Posten"
|
||||
class="font-bold h-6 py-0 px-1 m-0 bg-transparent dark:bg-transparent hover:bg-background/66 dark:hover:bg-background/66 border-none hover:border-1 dark:hover:border-1 placeholder:text-muted-foreground/30 shadow-none mb-1" />
|
||||
<!-- <Textarea v-model="element.description" placeholder="Beschreibung"
|
||||
class="py-0 min-h-4 px-1 m-0 bg-transparent dark:bg-transparent border-none placeholder:text-muted-foreground/30 shadow-none" /> -->
|
||||
<GrowingTextarea v-model="element.description" placeholder="Beschreibung"
|
||||
class="font-light bg-transparent dark:bg-transparent hover:bg-background/66 dark:hover:bg-background/66 py-0 px-1 m-0 border-none shadow-none" />
|
||||
|
||||
</TableCell>
|
||||
|
||||
<!-- Einh. -->
|
||||
<TableCell class="w-1/8 text-center">
|
||||
<Select v-model="element.unit">
|
||||
<SelectTrigger class="shadow-none bg-transparent dark:bg-transparent hover:bg-background/66 dark:hover:bg-background/66 border-none pr-0 py-0 pl-1 w-full h-6!">
|
||||
<SelectValue placeholder="Einheit" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem v-for="unit in units" :value="unit">
|
||||
{{ unit }}
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</TableCell>
|
||||
|
||||
<!-- Anz. -->
|
||||
<TableCell class="w-20 text-center">
|
||||
<NumberField v-model="element.quantity" :step="0.5" :format-options="{}">
|
||||
<NumberFieldContent>
|
||||
<NumberFieldDecrement class="text-muted-foreground p-1 bg-transparent dark:bg-transparent hover:bg-background/66 dark:hover:bg-background/66" />
|
||||
<NumberFieldInput class="h-6 border-none bg-transparent dark:bg-transparent hover:bg-background/66 dark:hover:bg-background/66" />
|
||||
<NumberFieldIncrement class="text-muted-foreground p-1 bg-transparent dark:bg-transparent hover:bg-background/66 dark:hover:bg-background/66" />
|
||||
</NumberFieldContent>
|
||||
</NumberField>
|
||||
</TableCell>
|
||||
|
||||
<!-- Preis -->
|
||||
<TableCell class="w-1/8 text-right tabular-nums">
|
||||
<NumberInput :modelValue="Number(element.price)" class="bg-transparent dark:bg-transparent hover:bg-background/66 dark:hover:bg-background/66 h-6 rounded" />
|
||||
</TableCell>
|
||||
|
||||
<!-- Total -->
|
||||
<TableCell class="w-1/8 text-right tabular-nums font-bold">{{ toCurrency(element.price * element.quantity)
|
||||
}}
|
||||
</TableCell>
|
||||
|
||||
<!-- Buttons -->
|
||||
<TableCell class="w-16 text-right">
|
||||
<Button :variant="'ghost'" :size="'sm'" @click="deleteItem(element)"
|
||||
class="has-[>svg]:px-1 text-muted-foreground hover:text-destructive">
|
||||
<Trash2 :size="18" />
|
||||
</Button>
|
||||
<Button :variant="'ghost'" :size="'sm'" @click="" class="has-[>svg]:px-1 text-muted-foreground">
|
||||
<CirclePlus />
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
</template>
|
||||
</draggable>
|
||||
|
||||
<TableFooter class="bg-transparent">
|
||||
<TableRow class="hover:bg-transparent dark:hover:bg-transparent">
|
||||
<TableCell colspan="8" class="text-center">
|
||||
|
||||
<Button class="mt-2" variant="ghost" @click="newItem">
|
||||
<Plus /> Neue Zeile
|
||||
</Button>
|
||||
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
|
||||
</Table>
|
||||
|
||||
<Empty v-if="items.length < 1">
|
||||
<EmptyHeader>
|
||||
<EmptyMedia variant="icon">
|
||||
<TextSelect class="text-muted-foreground" stroke-width="1.5" />
|
||||
</EmptyMedia>
|
||||
<EmptyTitle>Diese Rechnung ist leer</EmptyTitle>
|
||||
<EmptyDescription>Erstelle hier deinen ersten Posten</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
<Button variant="action" @click="newItem">
|
||||
<Plus /> Neue Zeile
|
||||
</Button>
|
||||
</Empty>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
tr.ghost {
|
||||
background: var(--color-muted);
|
||||
color: var(--color-muted-foreground);
|
||||
}
|
||||
|
||||
tr.ghost .position {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
tr.ghost .handle svg {
|
||||
stroke: transparent;
|
||||
fill: transparent;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user