Add External Link
Fix Plantafel
This commit is contained in:
2
backend/db/migrations/0027_product_supplier_link.sql
Normal file
2
backend/db/migrations/0027_product_supplier_link.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE "products"
|
||||||
|
ADD COLUMN "supplierLink" text;
|
||||||
@@ -190,6 +190,13 @@
|
|||||||
"when": 1774393202000,
|
"when": 1774393202000,
|
||||||
"tag": "0026_statementallocation_depreciation_method",
|
"tag": "0026_statementallocation_depreciation_method",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 27,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1774602000000,
|
||||||
|
"tag": "0027_product_supplier_link",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ export const products = pgTable("products", {
|
|||||||
vendor_allocation: jsonb("vendorAllocation").default([]),
|
vendor_allocation: jsonb("vendorAllocation").default([]),
|
||||||
|
|
||||||
article_number: text("articleNumber"),
|
article_number: text("articleNumber"),
|
||||||
|
supplier_link: text("supplierLink"),
|
||||||
|
|
||||||
barcodes: text("barcodes").array().notNull().default([]),
|
barcodes: text("barcodes").array().notNull().default([]),
|
||||||
|
|
||||||
|
|||||||
48
frontend/components/columnRenderings/externalLink.vue
Normal file
48
frontend/components/columnRenderings/externalLink.vue
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
row: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
keyName: {
|
||||||
|
type: String,
|
||||||
|
default: ""
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const resolvedKey = computed(() => {
|
||||||
|
if (props.keyName) return props.keyName
|
||||||
|
if (typeof props.row?.supplier_link === "string") return "supplier_link"
|
||||||
|
if (typeof props.row?.link === "string") return "link"
|
||||||
|
if (typeof props.row?.url === "string") return "url"
|
||||||
|
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
const normalizedUrl = computed(() => {
|
||||||
|
const rawValue = resolvedKey.value ? props.row?.[resolvedKey.value] : null
|
||||||
|
|
||||||
|
if (!rawValue || typeof rawValue !== "string") return null
|
||||||
|
|
||||||
|
const trimmedValue = rawValue.trim()
|
||||||
|
if (!trimmedValue) return null
|
||||||
|
|
||||||
|
if (/^https?:\/\//i.test(trimmedValue)) return trimmedValue
|
||||||
|
|
||||||
|
return `https://${trimmedValue}`
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<a
|
||||||
|
v-if="normalizedUrl"
|
||||||
|
:href="normalizedUrl"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="text-primary hover:underline break-all"
|
||||||
|
>
|
||||||
|
{{ resolvedKey ? row[resolvedKey] : "" }}
|
||||||
|
</a>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</template>
|
||||||
@@ -51,6 +51,7 @@ const itemInfo = ref({
|
|||||||
dateOfPerformance: null,
|
dateOfPerformance: null,
|
||||||
paymentDays: auth.activeTenantData.standardPaymentDays,
|
paymentDays: auth.activeTenantData.standardPaymentDays,
|
||||||
payment_type: "transfer",
|
payment_type: "transfer",
|
||||||
|
availableInPortal: false,
|
||||||
customSurchargePercentage: 0,
|
customSurchargePercentage: 0,
|
||||||
created_by: auth.user.id,
|
created_by: auth.user.id,
|
||||||
title: null,
|
title: null,
|
||||||
@@ -111,6 +112,18 @@ const serialIntervalItems = ['wöchentlich', '2 - wöchentlich', 'monatlich', 'v
|
|||||||
const serialDateDirectionItems = ['Rückwirkend', 'Im Voraus']
|
const serialDateDirectionItems = ['Rückwirkend', 'Im Voraus']
|
||||||
const taxPercentItems = [19, 7, 0]
|
const taxPercentItems = [19, 7, 0]
|
||||||
const selectedCustomer = computed(() => customers.value.find(i => i.id === itemInfo.value.customer) || null)
|
const selectedCustomer = computed(() => customers.value.find(i => i.id === itemInfo.value.customer) || null)
|
||||||
|
const normalizeExternalUrl = (value) => {
|
||||||
|
if (!value || typeof value !== "string") return null
|
||||||
|
|
||||||
|
const trimmedValue = value.trim()
|
||||||
|
if (!trimmedValue) return null
|
||||||
|
|
||||||
|
if (/^https?:\/\//i.test(trimmedValue)) return trimmedValue
|
||||||
|
|
||||||
|
return `https://${trimmedValue}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSelectedProduct = (row) => products.value.find((item) => item.id === row.product) || null
|
||||||
const formatNumberLikeValue = (value) => {
|
const formatNumberLikeValue = (value) => {
|
||||||
if (value === null || typeof value === 'undefined' || value === '') return '-'
|
if (value === null || typeof value === 'undefined' || value === '') return '-'
|
||||||
if (typeof value === 'string' || typeof value === 'number') return String(value)
|
if (typeof value === 'string' || typeof value === 'number') return String(value)
|
||||||
@@ -1438,7 +1451,8 @@ const saveSerialInvoice = async () => {
|
|||||||
contactPerson: itemInfo.value.contactPerson,
|
contactPerson: itemInfo.value.contactPerson,
|
||||||
serialConfig: itemInfo.value.serialConfig,
|
serialConfig: itemInfo.value.serialConfig,
|
||||||
letterhead: itemInfo.value.letterhead,
|
letterhead: itemInfo.value.letterhead,
|
||||||
taxType:itemInfo.value.taxType
|
taxType:itemInfo.value.taxType,
|
||||||
|
availableInPortal: itemInfo.value.availableInPortal
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = null
|
let data = null
|
||||||
@@ -1527,6 +1541,7 @@ const saveDocument = async (state, resetup = false) => {
|
|||||||
agriculture: itemInfo.value.agriculture,
|
agriculture: itemInfo.value.agriculture,
|
||||||
letterhead: itemInfo.value.letterhead,
|
letterhead: itemInfo.value.letterhead,
|
||||||
usedAdvanceInvoices: itemInfo.value.usedAdvanceInvoices,
|
usedAdvanceInvoices: itemInfo.value.usedAdvanceInvoices,
|
||||||
|
availableInPortal: itemInfo.value.availableInPortal,
|
||||||
customSurchargePercentage: itemInfo.value.customSurchargePercentage,
|
customSurchargePercentage: itemInfo.value.customSurchargePercentage,
|
||||||
report: documentReport.value
|
report: documentReport.value
|
||||||
}
|
}
|
||||||
@@ -2234,6 +2249,12 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
class="w-full"
|
class="w-full"
|
||||||
/>
|
/>
|
||||||
</UFormField>
|
</UFormField>
|
||||||
|
<UFormField
|
||||||
|
v-if="['invoices', 'advanceInvoices', 'cancellationInvoices'].includes(itemInfo.type)"
|
||||||
|
label="Im Kundenportal anzeigen:"
|
||||||
|
>
|
||||||
|
<USwitch v-model="itemInfo.availableInPortal" />
|
||||||
|
</UFormField>
|
||||||
<UFormField
|
<UFormField
|
||||||
label="Objekt:"
|
label="Objekt:"
|
||||||
>
|
>
|
||||||
@@ -2589,24 +2610,39 @@ const setRowData = async (row, service = {sellingPriceComposed: {}}, product = {
|
|||||||
v-else-if="row.mode === 'normal'"
|
v-else-if="row.mode === 'normal'"
|
||||||
>
|
>
|
||||||
<InputGroup class="w-full">
|
<InputGroup class="w-full">
|
||||||
<USelectMenu
|
<div class="w-full min-w-0">
|
||||||
:disabled="itemInfo.type === 'cancellationInvoices'"
|
<USelectMenu
|
||||||
class="w-full min-w-0"
|
:disabled="itemInfo.type === 'cancellationInvoices'"
|
||||||
:items="products"
|
class="w-full min-w-0"
|
||||||
:color="row.product ? 'primary' : 'error'"
|
:items="products"
|
||||||
label-key="name"
|
:color="row.product ? 'primary' : 'error'"
|
||||||
value-key="id"
|
label-key="name"
|
||||||
:search-input="{ placeholder: 'Suche ...' }"
|
value-key="id"
|
||||||
:filter-fields="['name']"
|
:search-input="{ placeholder: 'Suche ...' }"
|
||||||
v-model="row.product"
|
:filter-fields="['name']"
|
||||||
@update:model-value="() => setRowData(row)"
|
v-model="row.product"
|
||||||
>
|
@update:model-value="() => setRowData(row)"
|
||||||
<template #default>
|
>
|
||||||
<span class="truncate">{{
|
<template #default>
|
||||||
products.find(i => i.id === row.product) ? products.find(i => i.id === row.product).name : "Kein Produkt ausgewählt"
|
<span class="truncate">{{
|
||||||
}}</span>
|
getSelectedProduct(row) ? getSelectedProduct(row).name : "Kein Produkt ausgewählt"
|
||||||
</template>
|
}}</span>
|
||||||
</USelectMenu>
|
</template>
|
||||||
|
</USelectMenu>
|
||||||
|
<div
|
||||||
|
v-if="normalizeExternalUrl(getSelectedProduct(row)?.supplier_link)"
|
||||||
|
class="mt-1 text-xs"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
:href="normalizeExternalUrl(getSelectedProduct(row)?.supplier_link)"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="text-primary hover:underline break-all"
|
||||||
|
>
|
||||||
|
Lieferantenlink öffnen
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<EntityModalButtons
|
<EntityModalButtons
|
||||||
type="products"
|
type="products"
|
||||||
:id="row.product"
|
:id="row.product"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import deLocale from "@fullcalendar/core/locales/de"
|
|||||||
import FullCalendar from "@fullcalendar/vue3"
|
import FullCalendar from "@fullcalendar/vue3"
|
||||||
import interactionPlugin from "@fullcalendar/interaction"
|
import interactionPlugin from "@fullcalendar/interaction"
|
||||||
import resourceTimelinePlugin from "@fullcalendar/resource-timeline"
|
import resourceTimelinePlugin from "@fullcalendar/resource-timeline"
|
||||||
|
import { parseDate } from "@internationalized/date"
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
@@ -13,6 +14,10 @@ const { list: listStaffTimeSpans, createEntry, update: updateStaffTimeEntry } =
|
|||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const savingAbsence = ref(false)
|
const savingAbsence = ref(false)
|
||||||
const selectedType = ref("all")
|
const selectedType = ref("all")
|
||||||
|
const calendarRef = ref(null)
|
||||||
|
const calendarView = ref("resourceTimelineWeek")
|
||||||
|
const calendarCurrentDate = ref($dayjs().format("YYYY-MM-DD"))
|
||||||
|
const calendarTitle = ref("")
|
||||||
const visibleRange = ref({
|
const visibleRange = ref({
|
||||||
from: $dayjs().startOf("month").format("YYYY-MM-DD"),
|
from: $dayjs().startOf("month").format("YYYY-MM-DD"),
|
||||||
to: $dayjs().endOf("month").format("YYYY-MM-DD")
|
to: $dayjs().endOf("month").format("YYYY-MM-DD")
|
||||||
@@ -38,17 +43,72 @@ const setAbsenceDateToToday = (field) => {
|
|||||||
absenceForm[field] = $dayjs().format("YYYY-MM-DD")
|
absenceForm[field] = $dayjs().format("YYYY-MM-DD")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const startDateValue = computed({
|
||||||
|
get: () => {
|
||||||
|
if (!absenceForm.startDate) return null
|
||||||
|
|
||||||
|
try {
|
||||||
|
return parseDate(absenceForm.startDate)
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set: (value) => {
|
||||||
|
absenceForm.startDate = value ? value.toString() : ""
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const endDateValue = computed({
|
||||||
|
get: () => {
|
||||||
|
if (!absenceForm.endDate) return null
|
||||||
|
|
||||||
|
try {
|
||||||
|
return parseDate(absenceForm.endDate)
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set: (value) => {
|
||||||
|
absenceForm.endDate = value ? value.toString() : ""
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const resourceTypeOptions = [
|
const resourceTypeOptions = [
|
||||||
{ label: "Alle Ressourcen", value: "all" },
|
{ label: "Alle Ressourcen", value: "all" },
|
||||||
{ label: "Profile", value: "Profile" },
|
{ label: "Profile", value: "Profile" },
|
||||||
{ label: "Inventarartikel", value: "Inventarartikel" }
|
{ label: "Inventarartikel", value: "Inventarartikel" }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const calendarViewOptions = [
|
||||||
|
{ label: "Tag", value: "resourceTimelineDay" },
|
||||||
|
{ label: "Woche", value: "resourceTimelineWeek" },
|
||||||
|
{ label: "Monat", value: "resourceTimelineMonth" }
|
||||||
|
]
|
||||||
|
|
||||||
const absenceTypeOptions = [
|
const absenceTypeOptions = [
|
||||||
{ label: "Urlaub", value: "vacation" },
|
{ label: "Urlaub", value: "vacation" },
|
||||||
{ label: "Krank", value: "sick" }
|
{ label: "Krank", value: "sick" }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const calendarPickerValue = computed({
|
||||||
|
get: () => {
|
||||||
|
if (!calendarCurrentDate.value) return null
|
||||||
|
|
||||||
|
try {
|
||||||
|
return parseDate(calendarCurrentDate.value)
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set: (value) => {
|
||||||
|
calendarCurrentDate.value = value ? value.toString() : ""
|
||||||
|
if (value) {
|
||||||
|
const api = calendarRef.value?.getApi?.()
|
||||||
|
api?.gotoDate(value.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const profileOptions = computed(() =>
|
const profileOptions = computed(() =>
|
||||||
profiles.value
|
profiles.value
|
||||||
.filter((profile) => !profile.archived && profile.user_id)
|
.filter((profile) => !profile.archived && profile.user_id)
|
||||||
@@ -83,12 +143,9 @@ const calendarOptions = computed(() => ({
|
|||||||
schedulerLicenseKey: "CC-Attribution-NonCommercial-NoDerivatives",
|
schedulerLicenseKey: "CC-Attribution-NonCommercial-NoDerivatives",
|
||||||
locale: deLocale,
|
locale: deLocale,
|
||||||
plugins: [resourceTimelinePlugin, interactionPlugin],
|
plugins: [resourceTimelinePlugin, interactionPlugin],
|
||||||
initialView: "resourceTimelineWeek",
|
initialView: calendarView.value,
|
||||||
headerToolbar: {
|
initialDate: calendarCurrentDate.value,
|
||||||
left: "prev,next today",
|
headerToolbar: false,
|
||||||
center: "title",
|
|
||||||
right: "resourceTimelineDay,resourceTimelineWeek,resourceTimelineMonth"
|
|
||||||
},
|
|
||||||
resourceAreaWidth: "280px",
|
resourceAreaWidth: "280px",
|
||||||
resourceGroupField: "type",
|
resourceGroupField: "type",
|
||||||
resourceOrder: "type,title",
|
resourceOrder: "type,title",
|
||||||
@@ -149,6 +206,10 @@ const calendarOptions = computed(() => ({
|
|||||||
const nextTo = $dayjs(info.end).subtract(1, "day").format("YYYY-MM-DD")
|
const nextTo = $dayjs(info.end).subtract(1, "day").format("YYYY-MM-DD")
|
||||||
const nextKey = `${nextFrom}:${nextTo}`
|
const nextKey = `${nextFrom}:${nextTo}`
|
||||||
|
|
||||||
|
calendarView.value = info.view.type
|
||||||
|
calendarCurrentDate.value = $dayjs(info.view.currentStart).format("YYYY-MM-DD")
|
||||||
|
calendarTitle.value = info.view.title
|
||||||
|
|
||||||
if (nextKey === lastRangeKey.value) return
|
if (nextKey === lastRangeKey.value) return
|
||||||
|
|
||||||
lastRangeKey.value = nextKey
|
lastRangeKey.value = nextKey
|
||||||
@@ -264,6 +325,31 @@ function resetAbsenceForm() {
|
|||||||
absenceForm.description = ""
|
absenceForm.description = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCalendarApi() {
|
||||||
|
return calendarRef.value?.getApi?.()
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeCalendarView(view) {
|
||||||
|
const api = getCalendarApi()
|
||||||
|
if (!api || !view) return
|
||||||
|
calendarView.value = view
|
||||||
|
api.changeView(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveCalendar(direction) {
|
||||||
|
const api = getCalendarApi()
|
||||||
|
if (!api) return
|
||||||
|
|
||||||
|
if (direction === "prev") api.prev()
|
||||||
|
if (direction === "next") api.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
function moveCalendarToday() {
|
||||||
|
const api = getCalendarApi()
|
||||||
|
if (!api) return
|
||||||
|
api.today()
|
||||||
|
}
|
||||||
|
|
||||||
function openAbsenceModal(type = "vacation", preset = {}) {
|
function openAbsenceModal(type = "vacation", preset = {}) {
|
||||||
absenceForm.mode = preset.entry ? "edit" : "create"
|
absenceForm.mode = preset.entry ? "edit" : "create"
|
||||||
absenceForm.entry = preset.entry || null
|
absenceForm.entry = preset.entry || null
|
||||||
@@ -404,12 +490,49 @@ onMounted(() => {
|
|||||||
<div class="flex flex-wrap items-center gap-3">
|
<div class="flex flex-wrap items-center gap-3">
|
||||||
<USelectMenu
|
<USelectMenu
|
||||||
v-model="selectedType"
|
v-model="selectedType"
|
||||||
:options="resourceTypeOptions"
|
:items="resourceTypeOptions"
|
||||||
value-attribute="value"
|
value-key="value"
|
||||||
option-attribute="label"
|
label-key="label"
|
||||||
:clearable="false"
|
:clearable="false"
|
||||||
class="min-w-[220px]"
|
class="min-w-[220px]"
|
||||||
/>
|
/>
|
||||||
|
<USelectMenu
|
||||||
|
v-model="calendarView"
|
||||||
|
:items="calendarViewOptions"
|
||||||
|
value-key="value"
|
||||||
|
label-key="label"
|
||||||
|
:clearable="false"
|
||||||
|
class="min-w-[160px]"
|
||||||
|
@update:model-value="changeCalendarView"
|
||||||
|
/>
|
||||||
|
<UPopover>
|
||||||
|
<UButton
|
||||||
|
color="neutral"
|
||||||
|
variant="outline"
|
||||||
|
icon="i-heroicons-calendar-days"
|
||||||
|
class="min-w-[180px] justify-between"
|
||||||
|
>
|
||||||
|
{{ calendarCurrentDate ? $dayjs(calendarCurrentDate).format("DD.MM.YYYY") : "Datum wählen" }}
|
||||||
|
</UButton>
|
||||||
|
|
||||||
|
<template #content>
|
||||||
|
<div class="p-2">
|
||||||
|
<UCalendar v-model="calendarPickerValue" />
|
||||||
|
<div class="flex justify-end border-t border-default pt-2">
|
||||||
|
<UButton color="neutral" variant="ghost" size="sm" @click="moveCalendarToday">
|
||||||
|
Heute
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UPopover>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<UButton color="neutral" variant="ghost" icon="i-heroicons-chevron-left" @click="moveCalendar('prev')" />
|
||||||
|
<UButton color="neutral" variant="ghost" icon="i-heroicons-chevron-right" @click="moveCalendar('next')" />
|
||||||
|
</div>
|
||||||
|
<span class="text-sm font-medium text-highlighted">
|
||||||
|
{{ calendarTitle }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #right>
|
<template #right>
|
||||||
@@ -449,6 +572,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
<FullCalendar
|
<FullCalendar
|
||||||
v-else
|
v-else
|
||||||
|
ref="calendarRef"
|
||||||
:options="calendarOptions"
|
:options="calendarOptions"
|
||||||
/>
|
/>
|
||||||
</UDashboardPanelContent>
|
</UDashboardPanelContent>
|
||||||
@@ -475,33 +599,82 @@ onMounted(() => {
|
|||||||
<UFormField label="Profil">
|
<UFormField label="Profil">
|
||||||
<USelectMenu
|
<USelectMenu
|
||||||
v-model="absenceForm.userId"
|
v-model="absenceForm.userId"
|
||||||
:options="profileOptions"
|
:items="profileOptions"
|
||||||
value-attribute="value"
|
value-key="value"
|
||||||
option-attribute="label"
|
label-key="label"
|
||||||
searchable
|
class="w-full"
|
||||||
|
:search-input="{ placeholder: 'Profil suchen...' }"
|
||||||
|
:filter-fields="['label']"
|
||||||
/>
|
/>
|
||||||
</UFormField>
|
</UFormField>
|
||||||
|
|
||||||
<UFormField label="Typ">
|
<UFormField label="Typ">
|
||||||
<USelectMenu
|
<USelectMenu
|
||||||
v-model="absenceForm.type"
|
v-model="absenceForm.type"
|
||||||
:options="absenceTypeOptions"
|
:items="absenceTypeOptions"
|
||||||
value-attribute="value"
|
value-key="value"
|
||||||
option-attribute="label"
|
label-key="label"
|
||||||
|
class="w-full"
|
||||||
/>
|
/>
|
||||||
</UFormField>
|
</UFormField>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<UFormField label="Start">
|
<UFormField label="Start">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<UInput v-model="absenceForm.startDate" type="date" class="flex-1" />
|
<UPopover>
|
||||||
<UButton color="gray" variant="soft" label="Heute" @click="setAbsenceDateToToday('startDate')" />
|
<UButton
|
||||||
|
color="neutral"
|
||||||
|
variant="outline"
|
||||||
|
icon="i-heroicons-calendar-days"
|
||||||
|
class="min-w-0 flex-1 justify-between"
|
||||||
|
>
|
||||||
|
<span class="truncate text-left">
|
||||||
|
{{ absenceForm.startDate ? $dayjs(absenceForm.startDate).format("DD.MM.YYYY") : "Kein Datum" }}
|
||||||
|
</span>
|
||||||
|
</UButton>
|
||||||
|
|
||||||
|
<template #content>
|
||||||
|
<div class="p-2">
|
||||||
|
<UCalendar v-model="startDateValue" />
|
||||||
|
<div class="flex justify-end border-t border-default pt-2">
|
||||||
|
<UButton color="neutral" variant="ghost" size="sm" @click="setAbsenceDateToToday('startDate')">
|
||||||
|
Heute
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UPopover>
|
||||||
|
|
||||||
|
<UButton color="neutral" variant="soft" label="Heute" @click="setAbsenceDateToToday('startDate')" />
|
||||||
</div>
|
</div>
|
||||||
</UFormField>
|
</UFormField>
|
||||||
<UFormField label="Ende">
|
<UFormField label="Ende">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<UInput v-model="absenceForm.endDate" type="date" class="flex-1" />
|
<UPopover>
|
||||||
<UButton color="gray" variant="soft" label="Heute" @click="setAbsenceDateToToday('endDate')" />
|
<UButton
|
||||||
|
color="neutral"
|
||||||
|
variant="outline"
|
||||||
|
icon="i-heroicons-calendar-days"
|
||||||
|
class="min-w-0 flex-1 justify-between"
|
||||||
|
>
|
||||||
|
<span class="truncate text-left">
|
||||||
|
{{ absenceForm.endDate ? $dayjs(absenceForm.endDate).format("DD.MM.YYYY") : "Kein Datum" }}
|
||||||
|
</span>
|
||||||
|
</UButton>
|
||||||
|
|
||||||
|
<template #content>
|
||||||
|
<div class="p-2">
|
||||||
|
<UCalendar v-model="endDateValue" />
|
||||||
|
<div class="flex justify-end border-t border-default pt-2">
|
||||||
|
<UButton color="neutral" variant="ghost" size="sm" @click="setAbsenceDateToToday('endDate')">
|
||||||
|
Heute
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UPopover>
|
||||||
|
|
||||||
|
<UButton color="neutral" variant="soft" label="Heute" @click="setAbsenceDateToToday('endDate')" />
|
||||||
</div>
|
</div>
|
||||||
</UFormField>
|
</UFormField>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import startDateTime from "~/components/columnRenderings/startDateTime.vue"
|
|||||||
import endDateTime from "~/components/columnRenderings/endDateTime.vue"
|
import endDateTime from "~/components/columnRenderings/endDateTime.vue"
|
||||||
import serviceCategories from "~/components/columnRenderings/serviceCategories.vue"
|
import serviceCategories from "~/components/columnRenderings/serviceCategories.vue"
|
||||||
import productcategoriesWithLoad from "~/components/columnRenderings/productcategoriesWithLoad.vue"
|
import productcategoriesWithLoad from "~/components/columnRenderings/productcategoriesWithLoad.vue"
|
||||||
|
import externalLink from "~/components/columnRenderings/externalLink.vue"
|
||||||
import phase from "~/components/columnRenderings/phase.vue"
|
import phase from "~/components/columnRenderings/phase.vue"
|
||||||
import vehiclesWithLoad from "~/components/columnRenderings/vehiclesWithLoad.vue"
|
import vehiclesWithLoad from "~/components/columnRenderings/vehiclesWithLoad.vue"
|
||||||
import inventoryitemsWithLoad from "~/components/columnRenderings/inventoryitemsWithLoad.vue"
|
import inventoryitemsWithLoad from "~/components/columnRenderings/inventoryitemsWithLoad.vue"
|
||||||
@@ -1380,6 +1381,12 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
label: "Beschreibung",
|
label: "Beschreibung",
|
||||||
inputType:"textarea"
|
inputType:"textarea"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "supplier_link",
|
||||||
|
label: "Link zum Lieferanten",
|
||||||
|
inputType: "text",
|
||||||
|
component: externalLink
|
||||||
|
},
|
||||||
],
|
],
|
||||||
showTabs: [
|
showTabs: [
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user