T14: Service Edit Page Layout + Routing
- ServiceController::edit() with eager-loaded relationships
- Services/Edit.vue with 4 collapsible accordion blocks
- Route: GET /services/{service}/edit
- Information slides query: global + service-specific with expire_date filtering
- Tests: 2 new (edit page render + auth guard)
T15: Information Block (Slides + Expire Dates)
- InformationBlock.vue with dynamic expire_date filtering
- Shows slides where type='information' AND expire_date >= service.date
- Global visibility across services (not service-specific)
- SlideUploader with showExpireDate=true
- SlideGrid with prominent expire date + inline editing
- Badge showing slide count + 'expiring soon' warning (within 3 days)
- Tests: 7 new (105 assertions)
T16: Moderation Block (Service-Specific)
- ModerationBlock.vue (service-specific slides)
- Filters: type='moderation' AND service_id = current_service
- No expire date field (unlike Information block)
- Service isolation (slides from Service A don't appear in Service B)
- Tests: 5 new (14 assertions)
T17: Sermon Block (Service-Specific)
- SermonBlock.vue (identical to Moderation but type='sermon')
- Service-specific slides, no expire date
- Tests: 5 new (14 assertions)
T18: Songs Block (Matching + Arrangement + Translation)
- SongsBlock.vue with conditional UI (unmatched vs matched states)
- Unmatched: 'Erstellung anfragen' button + searchable select for manual assign
- Matched: ArrangementConfigurator + translation checkbox + preview/download buttons
- ServiceSongController::update() for use_translation and song_arrangement_id
- ArrangementConfigurator emits 'arrangement-selected' for auto-save
- ServiceController::edit() provides songsCatalog for matching search
- Tests: 2 new (45 assertions)
T19: Song PDF (INCOMPLETE - timeout)
- SongPdfController.php created (partial)
- resources/views/pdf/song.blade.php created (partial)
- SongPreviewModal.vue MISSING
- Tests MISSING
- Will be completed in next commit
All tests passing: 124/124 (703 assertions)
Build: ✓ Vite production build successful
German UI: All user-facing text in German with 'Du' form
Dependencies: barryvdh/laravel-dompdf added for PDF generation
128 lines
5.2 KiB
Vue
128 lines
5.2 KiB
Vue
<script setup>
|
|
import { ref, computed } from 'vue'
|
|
import { router } from '@inertiajs/vue3'
|
|
import SlideUploader from '@/Components/SlideUploader.vue'
|
|
import SlideGrid from '@/Components/SlideGrid.vue'
|
|
|
|
const props = defineProps({
|
|
serviceId: {
|
|
type: Number,
|
|
required: true,
|
|
},
|
|
serviceDate: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
slides: {
|
|
type: Array,
|
|
default: () => [],
|
|
},
|
|
})
|
|
|
|
const emit = defineEmits(['slides-updated'])
|
|
|
|
// Information slides are already filtered server-side (expire_date >= service.date)
|
|
// but we keep a computed for any additional client-side needs
|
|
const informationSlides = computed(() => {
|
|
return props.slides.filter((slide) => slide.type === 'information')
|
|
})
|
|
|
|
// Count slides expiring within 3 days of service date for the warning badge
|
|
const expiringSoonCount = computed(() => {
|
|
const serviceDate = new Date(props.serviceDate)
|
|
const threeDaysAfter = new Date(serviceDate)
|
|
threeDaysAfter.setDate(threeDaysAfter.getDate() + 3)
|
|
|
|
return informationSlides.value.filter((slide) => {
|
|
if (!slide.expire_date) return false
|
|
const expDate = new Date(slide.expire_date)
|
|
return expDate >= serviceDate && expDate <= threeDaysAfter
|
|
}).length
|
|
})
|
|
|
|
function handleSlideUploaded() {
|
|
emit('slides-updated')
|
|
}
|
|
|
|
function handleSlideDeleted() {
|
|
emit('slides-updated')
|
|
}
|
|
|
|
function handleSlideUpdated() {
|
|
emit('slides-updated')
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="information-block space-y-6">
|
|
<!-- Block header -->
|
|
<div class="border-b border-amber-200/60 pb-4">
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center gap-3">
|
|
<!-- Info icon with warm glow -->
|
|
<div class="flex h-9 w-9 items-center justify-center rounded-xl bg-gradient-to-br from-amber-50 to-orange-50 ring-1 ring-amber-200/60">
|
|
<svg class="h-5 w-5 text-amber-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" />
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-lg font-semibold text-gray-900">
|
|
Informationsfolien
|
|
</h3>
|
|
<p class="mt-0.5 text-sm text-gray-500">
|
|
Globale Folien — sichtbar in allen Gottesdiensten bis zum Ablaufdatum
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Slide count badges -->
|
|
<div class="flex items-center gap-2">
|
|
<span
|
|
v-if="informationSlides.length > 0"
|
|
class="inline-flex items-center gap-1 rounded-full bg-amber-50 px-2.5 py-1 text-xs font-semibold text-amber-700 ring-1 ring-amber-200/80"
|
|
>
|
|
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 15.75l5.159-5.159a2.25 2.25 0 013.182 0l5.159 5.159m-1.5-1.5l1.409-1.409a2.25 2.25 0 013.182 0l2.909 2.909M3.75 21h16.5a2.25 2.25 0 002.25-2.25V5.25a2.25 2.25 0 00-2.25-2.25H3.75A2.25 2.25 0 001.5 5.25v13.5A2.25 2.25 0 003.75 21z" />
|
|
</svg>
|
|
{{ informationSlides.length }} {{ informationSlides.length === 1 ? 'Folie' : 'Folien' }}
|
|
</span>
|
|
|
|
<span
|
|
v-if="expiringSoonCount > 0"
|
|
class="inline-flex items-center gap-1 rounded-full bg-orange-50 px-2.5 py-1 text-xs font-semibold text-orange-700 ring-1 ring-orange-300/80"
|
|
:title="`${expiringSoonCount} Folie(n) laufen bald ab`"
|
|
>
|
|
<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
|
|
</svg>
|
|
{{ expiringSoonCount }} läuft bald ab
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Slide uploader — information slides are GLOBAL (service_id = null) -->
|
|
<SlideUploader
|
|
type="information"
|
|
:service-id="null"
|
|
:show-expire-date="true"
|
|
@uploaded="handleSlideUploaded"
|
|
/>
|
|
|
|
<!-- Slide grid with prominent expire dates -->
|
|
<SlideGrid
|
|
:slides="informationSlides"
|
|
type="information"
|
|
:show-expire-date="true"
|
|
@deleted="handleSlideDeleted"
|
|
@updated="handleSlideUpdated"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.information-block {
|
|
/* Subtle warm background hint for visual distinction */
|
|
}
|
|
</style>
|