Ergänze Entwurfsstatus für Termine und Plantafel
This commit is contained in:
8
backend/db/migrations/0034_events_color.sql
Normal file
8
backend/db/migrations/0034_events_color.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
ALTER TABLE "events" ADD COLUMN "color" text;
|
||||||
|
|
||||||
|
UPDATE "events" AS e
|
||||||
|
SET "color" = COALESCE(t."calendarConfig"->'quickEntry'->>'color', '#2563eb')
|
||||||
|
FROM "tenants" AS t
|
||||||
|
WHERE e."tenant" = t."id"
|
||||||
|
AND e."quick" = true
|
||||||
|
AND e."color" IS NULL;
|
||||||
5
backend/db/migrations/0038_events_state.sql
Normal file
5
backend/db/migrations/0038_events_state.sql
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
ALTER TABLE "events" ADD COLUMN "state" text DEFAULT 'Final' NOT NULL;
|
||||||
|
|
||||||
|
UPDATE "events"
|
||||||
|
SET "state" = 'Final'
|
||||||
|
WHERE "state" IS NULL;
|
||||||
@@ -243,29 +243,36 @@
|
|||||||
{
|
{
|
||||||
"idx": 34,
|
"idx": 34,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
|
"when": 1777420800000,
|
||||||
|
"tag": "0034_events_color",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 35,
|
||||||
|
"version": "7",
|
||||||
"when": 1778191200000,
|
"when": 1778191200000,
|
||||||
"tag": "0035_contract_history",
|
"tag": "0035_contract_history",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 35,
|
"idx": 36,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1778194800000,
|
"when": 1778194800000,
|
||||||
"tag": "0036_allowed_contracttypes",
|
"tag": "0036_allowed_contracttypes",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 36,
|
"idx": 37,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1778840100000,
|
"when": 1778840100000,
|
||||||
"tag": "0037_outgoing_sepa_mandates",
|
"tag": "0037_outgoing_sepa_mandates",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idx": 37,
|
"idx": 38,
|
||||||
"version": "7",
|
"version": "7",
|
||||||
"when": 1778840200000,
|
"when": 1779158400000,
|
||||||
"tag": "0034_profile_availability_note",
|
"tag": "0038_events_state",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ export const events = pgTable(
|
|||||||
|
|
||||||
eventtype: text("eventtype").default("Umsetzung"),
|
eventtype: text("eventtype").default("Umsetzung"),
|
||||||
quick: boolean("quick").notNull().default(false),
|
quick: boolean("quick").notNull().default(false),
|
||||||
|
state: text("state").notNull().default("Final"),
|
||||||
|
color: text("color"),
|
||||||
|
|
||||||
project: bigint("project", { mode: "number" }), // FK follows when projects.ts exists
|
project: bigint("project", { mode: "number" }), // FK follows when projects.ts exists
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ const resources = ref([])
|
|||||||
const events = ref([])
|
const events = ref([])
|
||||||
const profiles = ref([])
|
const profiles = ref([])
|
||||||
const inventoryitems = ref([])
|
const inventoryitems = ref([])
|
||||||
|
const isDraftModeActive = ref(false)
|
||||||
|
const isFinalizeDraftsModalOpen = ref(false)
|
||||||
|
const finalizingDrafts = ref(false)
|
||||||
const savingQuickConfig = ref(false)
|
const savingQuickConfig = ref(false)
|
||||||
const isQuickConfigModalOpen = ref(false)
|
const isQuickConfigModalOpen = ref(false)
|
||||||
const quickConfigWindowEl = ref(null)
|
const quickConfigWindowEl = ref(null)
|
||||||
@@ -273,6 +276,14 @@ const visibleEvents = computed(() => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const visibleDraftEventIds = computed(() =>
|
||||||
|
[...new Set(
|
||||||
|
visibleEvents.value
|
||||||
|
.filter((event) => event.state === "Entwurf" && event.entrytype === "event" && event.eventId)
|
||||||
|
.map((event) => event.eventId)
|
||||||
|
)]
|
||||||
|
)
|
||||||
|
|
||||||
const calendarOptions = computed(() => ({
|
const calendarOptions = computed(() => ({
|
||||||
schedulerLicenseKey: "CC-Attribution-NonCommercial-NoDerivatives",
|
schedulerLicenseKey: "CC-Attribution-NonCommercial-NoDerivatives",
|
||||||
locale: deLocale,
|
locale: deLocale,
|
||||||
@@ -388,10 +399,15 @@ function resolveEventTitle(event, projectsById) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function resolveRenderedEventColor(event) {
|
function resolveRenderedEventColor(event) {
|
||||||
if (event?.quick) return activeQuickEntryConfig.value.color
|
if (event?.quick) return event?.color || activeQuickEntryConfig.value.color
|
||||||
return resolveEventColor(event.eventtype)
|
return resolveEventColor(event.eventtype)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveDisplayedEventTitle(event, projectsById) {
|
||||||
|
const baseTitle = resolveEventTitle(event, projectsById)
|
||||||
|
return event?.state === "Entwurf" ? `[Entwurf] ${baseTitle}` : baseTitle
|
||||||
|
}
|
||||||
|
|
||||||
function getProfileLabel(profile) {
|
function getProfileLabel(profile) {
|
||||||
return profile?.full_name || profile?.fullName || [profile?.first_name, profile?.last_name].filter(Boolean).join(" ") || profile?.email || `Profil ${profile?.id || ""}`.trim()
|
return profile?.full_name || profile?.fullName || [profile?.first_name, profile?.last_name].filter(Boolean).join(" ") || profile?.email || `Profil ${profile?.id || ""}`.trim()
|
||||||
}
|
}
|
||||||
@@ -527,13 +543,16 @@ function buildEvents({ rawEvents, projectsById }) {
|
|||||||
]
|
]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: resolveEventTitle(event, projectsById),
|
title: resolveDisplayedEventTitle(event, projectsById),
|
||||||
start: event.startDate,
|
start: event.startDate,
|
||||||
end: event.endDate,
|
end: event.endDate,
|
||||||
resourceIds,
|
resourceIds,
|
||||||
|
color: event.color || null,
|
||||||
|
state: event.state || "Final",
|
||||||
backgroundColor: resolveRenderedEventColor(event),
|
backgroundColor: resolveRenderedEventColor(event),
|
||||||
borderColor: resolveRenderedEventColor(event),
|
borderColor: resolveRenderedEventColor(event),
|
||||||
textColor: "#ffffff",
|
textColor: "#ffffff",
|
||||||
|
classNames: event.state === "Entwurf" ? ["planning-board-draft-event"] : [],
|
||||||
entrytype: "event",
|
entrytype: "event",
|
||||||
eventId: event.id
|
eventId: event.id
|
||||||
}
|
}
|
||||||
@@ -755,6 +774,8 @@ async function createQuickEvent(info) {
|
|||||||
const payload = {
|
const payload = {
|
||||||
name: activeQuickEntryConfig.value.name,
|
name: activeQuickEntryConfig.value.name,
|
||||||
quick: true,
|
quick: true,
|
||||||
|
state: isDraftModeActive.value ? "Entwurf" : "Final",
|
||||||
|
color: activeQuickEntryConfig.value.color,
|
||||||
startDate: info.startStr,
|
startDate: info.startStr,
|
||||||
endDate: info.endStr,
|
endDate: info.endStr,
|
||||||
profiles: resourceIds
|
profiles: resourceIds
|
||||||
@@ -786,6 +807,54 @@ async function createQuickEvent(info) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleDraftMode() {
|
||||||
|
if (isDraftModeActive.value) {
|
||||||
|
if (!visibleDraftEventIds.value.length) {
|
||||||
|
isDraftModeActive.value = false
|
||||||
|
toast.add({ title: "Entwurfsmodus deaktiviert", color: "green" })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isFinalizeDraftsModalOpen.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isDraftModeActive.value = true
|
||||||
|
toast.add({ title: "Entwurfsmodus aktiviert", description: "Neue Schichten werden als Entwurf angelegt.", color: "green" })
|
||||||
|
}
|
||||||
|
|
||||||
|
async function finalizeVisibleDrafts() {
|
||||||
|
if (finalizingDrafts.value || !visibleDraftEventIds.value.length) return
|
||||||
|
|
||||||
|
finalizingDrafts.value = true
|
||||||
|
const draftCount = visibleDraftEventIds.value.length
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Promise.all(
|
||||||
|
visibleDraftEventIds.value.map((eventId) =>
|
||||||
|
$api(`/api/resource/events/${eventId}`, {
|
||||||
|
method: "PUT",
|
||||||
|
body: { state: "Final" }
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
isDraftModeActive.value = false
|
||||||
|
isFinalizeDraftsModalOpen.value = false
|
||||||
|
toast.add({ title: "Entwürfe finalisiert", description: `${draftCount} Termine wurden finalisiert.`, color: "green" })
|
||||||
|
await loadPlanningBoard()
|
||||||
|
} catch (error) {
|
||||||
|
console.error("finalizeVisibleDrafts failed", error)
|
||||||
|
toast.add({
|
||||||
|
title: "Entwürfe konnten nicht finalisiert werden",
|
||||||
|
description: error?.message || "Bitte erneut versuchen.",
|
||||||
|
color: "red"
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
finalizingDrafts.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
@@ -983,6 +1052,14 @@ onMounted(() => {
|
|||||||
>
|
>
|
||||||
Quick-Einträge
|
Quick-Einträge
|
||||||
</UButton>
|
</UButton>
|
||||||
|
<UButton
|
||||||
|
:color="isDraftModeActive ? 'amber' : 'neutral'"
|
||||||
|
:variant="isDraftModeActive ? 'solid' : 'outline'"
|
||||||
|
icon="i-heroicons-document-duplicate"
|
||||||
|
@click="toggleDraftMode"
|
||||||
|
>
|
||||||
|
{{ isDraftModeActive ? "Entwurfsmodus aktiv" : "Entwurfsmodus" }}
|
||||||
|
</UButton>
|
||||||
<UButton
|
<UButton
|
||||||
color="amber"
|
color="amber"
|
||||||
variant="soft"
|
variant="soft"
|
||||||
@@ -1211,6 +1288,53 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<UModal v-model:open="isFinalizeDraftsModalOpen">
|
||||||
|
<template #content>
|
||||||
|
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center justify-between gap-3">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-base font-semibold leading-6 text-gray-900 dark:text-white">
|
||||||
|
Entwürfe finalisieren
|
||||||
|
</h3>
|
||||||
|
<p class="text-sm text-muted">
|
||||||
|
Beim Beenden des Entwurfsmodus können die aktuell sichtbaren Entwürfe finalisiert werden.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<UButton
|
||||||
|
color="gray"
|
||||||
|
variant="ghost"
|
||||||
|
icon="i-heroicons-x-mark-20-solid"
|
||||||
|
class="-my-1"
|
||||||
|
@click="isFinalizeDraftsModalOpen = false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<UAlert
|
||||||
|
color="amber"
|
||||||
|
variant="soft"
|
||||||
|
icon="i-heroicons-exclamation-triangle"
|
||||||
|
:title="`${visibleDraftEventIds.length} sichtbare Entwürfe gefunden`"
|
||||||
|
description="Diese Termine werden beim Finalisieren auf den Status Final gesetzt."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<UButton color="gray" variant="soft" @click="isFinalizeDraftsModalOpen = false">
|
||||||
|
Abbrechen
|
||||||
|
</UButton>
|
||||||
|
<UButton color="primary" :loading="finalizingDrafts" @click="finalizeVisibleDrafts">
|
||||||
|
Entwürfe finalisieren
|
||||||
|
</UButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UCard>
|
||||||
|
</template>
|
||||||
|
</UModal>
|
||||||
|
|
||||||
<UModal v-model:open="isAbsenceModalOpen">
|
<UModal v-model:open="isAbsenceModalOpen">
|
||||||
<template #content>
|
<template #content>
|
||||||
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
||||||
@@ -1506,3 +1630,13 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.planning-board-draft-event) {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.planning-board-draft-event .fc-timeline-event) {
|
||||||
|
border-style: dashed;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -3089,6 +3089,19 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
inputType: "bool",
|
inputType: "bool",
|
||||||
sortable: true
|
sortable: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "state",
|
||||||
|
label: "Status",
|
||||||
|
inputType: "select",
|
||||||
|
selectManualOptions: ["Entwurf", "Final"],
|
||||||
|
sortable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "color",
|
||||||
|
label: "Farbe",
|
||||||
|
inputType: "text",
|
||||||
|
sortable: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "startDate",
|
key: "startDate",
|
||||||
label: "Start",
|
label: "Start",
|
||||||
|
|||||||
Reference in New Issue
Block a user