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,
|
||||
"version": "7",
|
||||
"when": 1777420800000,
|
||||
"tag": "0034_events_color",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 35,
|
||||
"version": "7",
|
||||
"when": 1778191200000,
|
||||
"tag": "0035_contract_history",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 35,
|
||||
"idx": 36,
|
||||
"version": "7",
|
||||
"when": 1778194800000,
|
||||
"tag": "0036_allowed_contracttypes",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 36,
|
||||
"idx": 37,
|
||||
"version": "7",
|
||||
"when": 1778840100000,
|
||||
"tag": "0037_outgoing_sepa_mandates",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 37,
|
||||
"idx": 38,
|
||||
"version": "7",
|
||||
"when": 1778840200000,
|
||||
"tag": "0034_profile_availability_note",
|
||||
"when": 1779158400000,
|
||||
"tag": "0038_events_state",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -32,6 +32,8 @@ export const events = pgTable(
|
||||
|
||||
eventtype: text("eventtype").default("Umsetzung"),
|
||||
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
|
||||
|
||||
|
||||
@@ -30,6 +30,9 @@ const resources = ref([])
|
||||
const events = ref([])
|
||||
const profiles = ref([])
|
||||
const inventoryitems = ref([])
|
||||
const isDraftModeActive = ref(false)
|
||||
const isFinalizeDraftsModalOpen = ref(false)
|
||||
const finalizingDrafts = ref(false)
|
||||
const savingQuickConfig = ref(false)
|
||||
const isQuickConfigModalOpen = ref(false)
|
||||
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(() => ({
|
||||
schedulerLicenseKey: "CC-Attribution-NonCommercial-NoDerivatives",
|
||||
locale: deLocale,
|
||||
@@ -388,10 +399,15 @@ function resolveEventTitle(event, projectsById) {
|
||||
}
|
||||
|
||||
function resolveRenderedEventColor(event) {
|
||||
if (event?.quick) return activeQuickEntryConfig.value.color
|
||||
if (event?.quick) return event?.color || activeQuickEntryConfig.value.color
|
||||
return resolveEventColor(event.eventtype)
|
||||
}
|
||||
|
||||
function resolveDisplayedEventTitle(event, projectsById) {
|
||||
const baseTitle = resolveEventTitle(event, projectsById)
|
||||
return event?.state === "Entwurf" ? `[Entwurf] ${baseTitle}` : baseTitle
|
||||
}
|
||||
|
||||
function getProfileLabel(profile) {
|
||||
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 {
|
||||
title: resolveEventTitle(event, projectsById),
|
||||
title: resolveDisplayedEventTitle(event, projectsById),
|
||||
start: event.startDate,
|
||||
end: event.endDate,
|
||||
resourceIds,
|
||||
color: event.color || null,
|
||||
state: event.state || "Final",
|
||||
backgroundColor: resolveRenderedEventColor(event),
|
||||
borderColor: resolveRenderedEventColor(event),
|
||||
textColor: "#ffffff",
|
||||
classNames: event.state === "Entwurf" ? ["planning-board-draft-event"] : [],
|
||||
entrytype: "event",
|
||||
eventId: event.id
|
||||
}
|
||||
@@ -755,6 +774,8 @@ async function createQuickEvent(info) {
|
||||
const payload = {
|
||||
name: activeQuickEntryConfig.value.name,
|
||||
quick: true,
|
||||
state: isDraftModeActive.value ? "Entwurf" : "Final",
|
||||
color: activeQuickEntryConfig.value.color,
|
||||
startDate: info.startStr,
|
||||
endDate: info.endStr,
|
||||
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 = {}) {
|
||||
absenceForm.mode = preset.entry ? "edit" : "create"
|
||||
absenceForm.entry = preset.entry || null
|
||||
@@ -983,6 +1052,14 @@ onMounted(() => {
|
||||
>
|
||||
Quick-Einträge
|
||||
</UButton>
|
||||
<UButton
|
||||
:color="isDraftModeActive ? 'amber' : 'neutral'"
|
||||
:variant="isDraftModeActive ? 'solid' : 'outline'"
|
||||
icon="i-heroicons-document-duplicate"
|
||||
@click="toggleDraftMode"
|
||||
>
|
||||
{{ isDraftModeActive ? "Entwurfsmodus aktiv" : "Entwurfsmodus" }}
|
||||
</UButton>
|
||||
<UButton
|
||||
color="amber"
|
||||
variant="soft"
|
||||
@@ -1211,6 +1288,53 @@ onMounted(() => {
|
||||
</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">
|
||||
<template #content>
|
||||
<UCard :ui="{ ring: '', divide: 'divide-y divide-gray-100 dark:divide-gray-800' }">
|
||||
@@ -1506,3 +1630,13 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</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",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "state",
|
||||
label: "Status",
|
||||
inputType: "select",
|
||||
selectManualOptions: ["Entwurf", "Final"],
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "color",
|
||||
label: "Farbe",
|
||||
inputType: "text",
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
key: "startDate",
|
||||
label: "Start",
|
||||
|
||||
Reference in New Issue
Block a user