pp-planer/resources/js/Components/CcliPasteDialog.vue
2026-05-11 10:26:10 +02:00

249 lines
8.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import { ref, onMounted } from 'vue'
import { router } from '@inertiajs/vue3'
const props = defineProps({
open: { type: Boolean, default: false },
mode: { type: String, default: 'songdb' }, // 'songdb' | 'service-form' | 'pair-translation'
serviceSongId: { type: Number, default: null },
pairWithSongId: { type: Number, default: null },
prefilledText: { type: String, default: null },
})
const emit = defineEmits(['close', 'imported', 'paired'])
const pasteText = ref('')
const preview = ref(null)
const error = ref(null)
const loading = ref(false)
const existingSongId = ref(null)
onMounted(() => {
if (props.prefilledText) {
pasteText.value = props.prefilledText
doPreview()
}
})
function getCsrfToken() {
const match = document.cookie.split('; ').find(r => r.startsWith('XSRF-TOKEN='))
return match ? decodeURIComponent(match.split('=')[1]) : ''
}
async function doPreview() {
if (!pasteText.value.trim()) return
loading.value = true
error.value = null
preview.value = null
existingSongId.value = null
try {
const res = await fetch(route('api.ccli.preview'), {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-XSRF-TOKEN': getCsrfToken() },
body: JSON.stringify({ raw_text: pasteText.value }),
})
const data = await res.json()
if (!res.ok) {
error.value = data.message || 'Fehler beim Verarbeiten des Textes.'
} else {
preview.value = data
}
} catch {
error.value = 'Netzwerkfehler. Bitte versuche es erneut.'
} finally {
loading.value = false
}
}
async function doImport(importMode) {
loading.value = true
error.value = null
existingSongId.value = null
const modeMap = {
edit: 'create',
stay: 'create',
assign: 'assign-to-service-song',
pair: 'pair-with-song',
}
const targetMap = {
assign: props.serviceSongId,
pair: props.pairWithSongId,
}
try {
const res = await fetch(route('api.ccli.import'), {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-XSRF-TOKEN': getCsrfToken() },
body: JSON.stringify({
raw_text: pasteText.value,
mode: modeMap[importMode],
target_id: targetMap[importMode] ?? null,
}),
})
const data = await res.json()
if (res.status === 409) {
existingSongId.value = data.existing_song_id
error.value = data.message
return
}
if (!res.ok) {
error.value = data.message || 'Import fehlgeschlagen.'
return
}
if (importMode === 'edit') {
router.visit('/songs/' + data.song_id)
} else if (importMode === 'pair') {
router.visit('/songs/' + props.pairWithSongId + '/translate?prefilled=true')
} else {
emit('imported', data.song_id, importMode)
emit('close')
}
} catch {
error.value = 'Netzwerkfehler. Bitte versuche es erneut.'
} finally {
loading.value = false
}
}
</script>
<template>
<div v-if="open" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
<div class="bg-white rounded-lg shadow-xl w-full max-w-2xl mx-4 p-6">
<!-- Header -->
<div class="flex items-center justify-between mb-4">
<h2 class="text-lg font-semibold text-gray-900">
{{ mode === 'pair-translation' ? 'Übersetzung aus SongSelect übernehmen' : 'Song aus SongSelect importieren' }}
</h2>
<button
data-testid="ccli-close-button"
@click="$emit('close')"
class="text-gray-400 hover:text-gray-600"
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<!-- Instructions -->
<ol class="text-sm text-gray-600 mb-4 space-y-1 list-decimal list-inside">
<li>Öffne die Liedseite auf <strong>songselect.ccli.com</strong></li>
<li>Markiere alles (<kbd class="px-1 py-0.5 bg-gray-100 rounded text-xs">Strg+A</kbd>) und kopiere (<kbd class="px-1 py-0.5 bg-gray-100 rounded text-xs">Strg+C</kbd>)</li>
<li>Füge den Text unten ein und klicke <strong>Vorschau</strong></li>
</ol>
<!-- Textarea -->
<textarea
data-testid="ccli-paste-textarea"
v-model="pasteText"
rows="10"
class="w-full border border-gray-300 rounded-md p-3 font-mono text-sm resize-y focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Liedtext hier einfügen..."
/>
<!-- Preview button + spinner -->
<div class="mt-3 flex items-center gap-3">
<button
data-testid="ccli-preview-button"
@click="doPreview"
:disabled="!pasteText.trim() || loading"
class="px-4 py-2 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
>
Vorschau
</button>
<span
v-if="loading"
data-testid="ccli-loading-spinner"
class="inline-block animate-spin text-blue-600 text-xl"
>⟳</span>
</div>
<!-- Error message -->
<div
v-if="error"
data-testid="ccli-error-message"
class="mt-3 p-3 bg-red-50 border border-red-200 rounded-md text-sm text-red-700"
>
{{ error }}
<a
v-if="existingSongId"
:href="'/songs/' + existingSongId"
data-testid="ccli-existing-song-link"
class="ml-2 underline font-medium"
>Vorhandenen Song bearbeiten</a>
</div>
<!-- Preview pane -->
<div v-if="preview" class="mt-4 p-4 bg-gray-50 rounded-md border border-gray-200">
<div class="grid grid-cols-2 gap-2 text-sm mb-3">
<div><span class="text-gray-500">Titel:</span> <strong>{{ preview.title }}</strong></div>
<div><span class="text-gray-500">Autor:</span> {{ preview.author || '' }}</div>
<div><span class="text-gray-500">CCLI-Nr.:</span> {{ preview.ccliId || '' }}</div>
<div><span class="text-gray-500">Jahr:</span> {{ preview.year || '' }}</div>
</div>
<div class="text-xs text-gray-500">
Sektionen: {{ preview.sections?.map(s => s.label).join(', ') }}
</div>
</div>
<!-- Action buttons (shown after preview) -->
<div v-if="preview" class="mt-4 flex gap-3">
<!-- songdb mode -->
<template v-if="mode === 'songdb'">
<button
data-testid="ccli-import-edit-button"
@click="doImport('edit')"
:disabled="loading"
class="px-4 py-2 bg-green-600 text-white rounded-md text-sm font-medium hover:bg-green-700 disabled:opacity-50"
>
Importieren &amp; Bearbeiten
</button>
<button
data-testid="ccli-import-stay-button"
@click="doImport('stay')"
:disabled="loading"
class="px-4 py-2 bg-gray-600 text-white rounded-md text-sm font-medium hover:bg-gray-700 disabled:opacity-50"
>
Importieren
</button>
</template>
<!-- service-form mode -->
<template v-else-if="mode === 'service-form'">
<button
data-testid="ccli-import-edit-button"
@click="doImport('edit')"
:disabled="loading"
class="px-4 py-2 bg-green-600 text-white rounded-md text-sm font-medium hover:bg-green-700 disabled:opacity-50"
>
Importieren &amp; Bearbeiten
</button>
<button
data-testid="ccli-import-assign-button"
@click="doImport('assign')"
:disabled="loading"
class="px-4 py-2 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700 disabled:opacity-50"
>
Importieren &amp; Zuweisen
</button>
</template>
<!-- pair-translation mode -->
<template v-else-if="mode === 'pair-translation'">
<button
data-testid="ccli-pair-translation-button"
@click="doImport('pair')"
:disabled="loading"
class="px-4 py-2 bg-purple-600 text-white rounded-md text-sm font-medium hover:bg-purple-700 disabled:opacity-50"
>
Übersetzung übernehmen
</button>
</template>
</div>
</div>
</div>
</template>