147 lines
3.9 KiB
Vue
147 lines
3.9 KiB
Vue
<script setup lang="ts">
|
|
|
|
definePageMeta({
|
|
requiresAuth: true
|
|
})
|
|
|
|
import { ref, onMounted } from "vue"
|
|
import { isUnauthorized } from "~/utils/auth"
|
|
|
|
const { apiFetch } = useApi()
|
|
const { servers, loadServers } = useServers()
|
|
|
|
const selectedServer = ref(null)
|
|
const query = ref("")
|
|
const results = ref([])
|
|
const loading = ref(false)
|
|
const error = ref("")
|
|
const expandedRow = ref<number | null>(null)
|
|
|
|
function toggleRow(id: number) {
|
|
expandedRow.value = expandedRow.value === id ? null : id
|
|
}
|
|
|
|
async function search() {
|
|
if (!selectedServer.value && !query.value) {
|
|
results.value = []
|
|
return
|
|
}
|
|
|
|
loading.value = true
|
|
error.value = ""
|
|
try {
|
|
const params: any = {}
|
|
if (selectedServer.value) {
|
|
params.server_id = selectedServer.value
|
|
}
|
|
if (query.value) {
|
|
params.q = query.value
|
|
}
|
|
const result = await apiFetch("/work-entries", {
|
|
query: params
|
|
})
|
|
if (isUnauthorized(result)) {
|
|
return result
|
|
}
|
|
results.value = result
|
|
} catch (e: any) {
|
|
error.value = e.message
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(async () => {
|
|
if (isUnauthorized(await loadServers())) return
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="space-y-6">
|
|
<h1 class="text-3xl font-bold">Worklog-Suche</h1>
|
|
|
|
<form @submit.prevent="search" class="p-4 bg-white shadow rounded space-y-4">
|
|
<select
|
|
v-model="selectedServer"
|
|
class="border p-2 rounded w-full"
|
|
>
|
|
<option :value="null">Server auswählen…</option>
|
|
<option
|
|
v-for="s in servers"
|
|
:key="s.id"
|
|
:value="s.id"
|
|
>
|
|
{{ s.hostname }}
|
|
</option>
|
|
</select>
|
|
|
|
<input
|
|
v-model="query"
|
|
type="text"
|
|
placeholder="Freitextsuche (optional)"
|
|
class="border p-2 rounded w-full"
|
|
/>
|
|
|
|
<button
|
|
type="submit"
|
|
class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
|
|
>
|
|
Suchen
|
|
</button>
|
|
</form>
|
|
|
|
<!-- Nur anzeigen, wenn ein Server gewählt wurde -->
|
|
<div v-if="selectedServer || query" class="p-4 bg-white shadow rounded">
|
|
<h2 class="text-xl font-semibold mb-4">Suchergebnisse</h2>
|
|
|
|
<div v-if="loading">Lade…</div>
|
|
<div v-if="error" class="text-red-600">{{ error }}</div>
|
|
|
|
<table v-if="results.length" class="w-full border-collapse">
|
|
<thead>
|
|
<tr class="border-b">
|
|
<th class="text-left p-2">Titel</th>
|
|
<th class="text-left p-2">Dauer</th>
|
|
<th class="text-left p-2">Erstellt am</th>
|
|
<th class="text-left p-2">Server</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<template v-for="entry in results" :key="entry.id">
|
|
<tr
|
|
@click="toggleRow(entry.id)"
|
|
class="border-b hover:bg-gray-50 cursor-pointer"
|
|
>
|
|
<td class="p-2">{{ entry.title }}</td>
|
|
<td class="p-2">{{ entry.duration_minutes ?? "—" }}</td>
|
|
<td class="p-2">{{ new Date(entry.created_at).toLocaleString() }}</td>
|
|
<td class="p-2">
|
|
<span
|
|
v-for="s in entry.servers"
|
|
:key="s.id"
|
|
class="inline-block bg-gray-200 px-2 py-1 rounded mr-1 text-sm"
|
|
>
|
|
{{ s.hostname }}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
<!-- Detailzeile -->
|
|
<tr v-if="expandedRow === entry.id" class="bg-gray-50">
|
|
<td colspan="4" class="p-4">
|
|
<h3 class="font-semibold mb-2">Beschreibung</h3>
|
|
<p class="whitespace-pre-line">
|
|
{{ entry.description || "Keine Beschreibung vorhanden." }}
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
</template>
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
<div v-else class="text-gray-500">
|
|
Keine Einträge gefunden.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|