@@ -116,13 +220,32 @@ function onUploaded() {
-
![]()
+ class="group/thumb relative"
+ >
+
![]()
+
+
+
+
+
+
+
@@ -178,7 +229,8 @@ const deleteArrangement = () => {
Löschen
@@ -193,17 +245,20 @@ const deleteArrangement = () => {
@@ -215,25 +270,30 @@ const deleteArrangement = () => {
Gruppenfolge
+ (nicht editierbar)
{{ group.name }}
({
+ id: MASTER_ID,
+ name: 'MASTER',
+ is_default: false,
+ is_master: true,
+ groups: props.availableGroups.map((g) => ({
+ ...g,
+ slides: g.slides ?? [],
+ })),
+}))
+
+// All arrangements with MASTER prepended (for select dropdown)
+const allArrangements = computed(() => [
+ masterArrangement.value,
+ ...props.arrangements,
+])
+
+const isMasterSelected = computed(() => currentArrangementId.value === MASTER_ID)
+
// Local copy of arrangements so changes survive switching between arrangements
const localArrangements = ref(JSON.parse(JSON.stringify(props.arrangements)))
@@ -109,12 +131,13 @@ watch(
)
const currentArrangementId = ref(
- props.selectedArrangementId ?? props.arrangements.find((a) => a.is_default)?.id ?? props.arrangements[0]?.id ?? null,
+ props.selectedArrangementId ?? props.arrangements.find((a) => a.is_default)?.id ?? props.arrangements[0]?.id ?? MASTER_ID,
)
-const currentArrangement = computed(() =>
- localArrangements.value.find((a) => a.id === Number(currentArrangementId.value)) ?? null,
-)
+const currentArrangement = computed(() => {
+ if (currentArrangementId.value === MASTER_ID) return masterArrangement.value
+ return localArrangements.value.find((a) => a.id === Number(currentArrangementId.value)) ?? null
+})
const arrangementGroups = ref([])
const hoveredIndex = ref(null)
@@ -122,12 +145,16 @@ const hoveredIndex = ref(null)
watch(
currentArrangementId,
(id) => {
+ if (id === MASTER_ID) {
+ arrangementGroups.value = props.availableGroups.map((g, i) => ({ ...g, slides: g.slides ?? [], _uid: `${g.id}-master-${i}-${Date.now()}` }))
+ return
+ }
const arr = localArrangements.value.find((a) => a.id === Number(id))
if (arr?.groups?.length) {
arrangementGroups.value = arr.groups.map((g, i) => ({ ...g, _uid: `${g.id}-${i}-${Date.now()}` }))
} else {
// Fallback: show all available groups in order (Master)
- arrangementGroups.value = props.availableGroups.map((g, i) => ({ ...g, _uid: `${g.id}-master-${i}-${Date.now()}` }))
+ arrangementGroups.value = props.availableGroups.map((g, i) => ({ ...g, slides: g.slides ?? [], _uid: `${g.id}-master-${i}-${Date.now()}` }))
}
},
{ immediate: true },
@@ -209,29 +236,59 @@ function createArrangement() {
const name = window.prompt('Name für das neue Arrangement:')
if (!name?.trim()) return
pendingAutoSelect.value = true
- router.post(`/songs/${props.songId}/arrangements`, { name: name.trim() }, { preserveScroll: true })
+ router.post(`/songs/${props.songId}/arrangements`, { name: name.trim() }, {
+ preserveScroll: true,
+ onSuccess: () => {
+ router.reload({ preserveScroll: true })
+ },
+ })
}
function cloneArrangement() {
if (!currentArrangementId.value) return
+
+ // Cloning from MASTER = creating a new arrangement (store() already uses all groups in master order)
+ if (isMasterSelected.value) {
+ const name = window.prompt('Name für das geklonte Arrangement:', 'MASTER Kopie')
+ if (!name?.trim()) return
+ pendingAutoSelect.value = true
+ router.post(`/songs/${props.songId}/arrangements`, { name: name.trim() }, {
+ preserveScroll: true,
+ onSuccess: () => {
+ router.reload({ preserveScroll: true })
+ },
+ })
+ return
+ }
+
const name = window.prompt('Name für das geklonte Arrangement:', `${currentArrangement.value?.name ?? ''} Kopie`)
if (!name?.trim()) return
pendingAutoSelect.value = true
- router.post(`/arrangements/${currentArrangementId.value}/clone`, { name: name.trim() }, { preserveScroll: true })
+ router.post(`/arrangements/${currentArrangementId.value}/clone`, { name: name.trim() }, {
+ preserveScroll: true,
+ onSuccess: () => {
+ router.reload({ preserveScroll: true })
+ },
+ })
}
function deleteArrangement() {
- if (!currentArrangementId.value) return
+ if (!currentArrangementId.value || isMasterSelected.value) return
if (props.arrangements.length <= 1) {
alert('Das letzte Arrangement kann nicht gelöscht werden.')
return
}
if (!confirm('Arrangement wirklich löschen?')) return
- router.delete(`/arrangements/${currentArrangementId.value}`, { preserveScroll: true })
+ router.delete(`/arrangements/${currentArrangementId.value}`, {
+ preserveScroll: true,
+ onSuccess: () => {
+ router.reload({ preserveScroll: true })
+ },
+ })
}
function saveArrangement() {
- if (!currentArrangement.value) return
+ if (!currentArrangement.value || isMasterSelected.value) return
// Update local arrangements copy so changes survive switching
const localArr = localArrangements.value.find((a) => a.id === currentArrangement.value.id)
@@ -260,6 +317,7 @@ function saveArrangement() {
/* ── Group operations ── */
function duplicateGroup(index) {
+ if (isMasterSelected.value) return
const item = arrangementGroups.value[index]
const newItem = { ...item, _uid: `${item.id}-dup-${Date.now()}` }
arrangementGroups.value.splice(index + 1, 0, newItem)
@@ -267,6 +325,7 @@ function duplicateGroup(index) {
}
function addGroupAt(index, group) {
+ if (isMasterSelected.value) return
const newItem = {
id: group.id,
name: group.name,
@@ -280,6 +339,7 @@ function addGroupAt(index, group) {
}
function removeGroupAt(index) {
+ if (isMasterSelected.value) return
arrangementGroups.value.splice(index, 1)
saveArrangement()
}
@@ -287,7 +347,7 @@ function removeGroupAt(index) {
/* ── Selection emit ── */
watch(currentArrangementId, (id) => {
- emit('arrangement-selected', Number(id))
+ emit('arrangement-selected', id === MASTER_ID ? null : Number(id))
})
/* ── Close on backdrop ── */
@@ -343,7 +403,7 @@ function closeOnBackdrop(e) {
class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
>
diff --git a/resources/js/Components/SongEditModal.vue b/resources/js/Components/SongEditModal.vue
index f948f96..42a2420 100644
--- a/resources/js/Components/SongEditModal.vue
+++ b/resources/js/Components/SongEditModal.vue
@@ -491,6 +491,7 @@ onUnmounted(() => {
:song-id="songData.id"
:arrangements="arrangements"
:available-groups="availableGroups"
+ @arrangements-changed="fetchSong"
/>
diff --git a/resources/js/Pages/Services/Edit.vue b/resources/js/Pages/Services/Edit.vue
index aba4b82..82e046c 100644
--- a/resources/js/Pages/Services/Edit.vue
+++ b/resources/js/Pages/Services/Edit.vue
@@ -65,10 +65,16 @@ function goBack() {
router.get(route('services.index'))
}
+const informationBlockRef = ref(null)
+
function refreshPage() {
router.reload({ preserveScroll: true })
}
+function scrollToInfoBlock() {
+ informationBlockRef.value?.scrollIntoView({ behavior: 'smooth', block: 'start' })
+}
+
/* ── Agenda helpers ──────────────────────────────────────── */
const arrangementDialogItem = ref(null)
@@ -349,34 +355,9 @@ async function downloadService() {
-
-
-
-
-
-
-
- Information
- Info-Folien fuer alle kommenden Services
-
-
-
-
-
-
-
-
+
Ablauf
@@ -427,6 +408,7 @@ async function downloadService() {
:service-id="service.id"
:service-date="service.date"
@slides-updated="refreshPage"
+ @scroll-to-info="scrollToInfoBlock"
/>
@@ -434,6 +416,31 @@ async function downloadService() {
+
+
+
+
+
+
+
+ Information
+ Info-Folien für alle kommenden Services
+
+
+
+
+
+
+
|