202 lines
6.2 KiB
Svelte
202 lines
6.2 KiB
Svelte
<script lang="ts">
|
|
import type { Feedback } from '$lib/server/database';
|
|
import { browser } from '$app/environment';
|
|
import { env } from '$env/dynamic/public';
|
|
import { fly } from 'svelte/transition';
|
|
import HeaderBar from './HeaderBar.svelte';
|
|
import PaginationTableBody from '$lib/components/PaginationTable/PaginationTableBody.svelte';
|
|
import { MagnifyingGlass, Share } from 'svelte-heros-v2';
|
|
import { goto } from '$app/navigation';
|
|
import Input from '$lib/components/Input/Input.svelte';
|
|
import Textarea from '$lib/components/Input/Textarea.svelte';
|
|
import { onDestroy, onMount } from 'svelte';
|
|
|
|
let feedbacks: (typeof Feedback.prototype.dataValues)[] = $state([]);
|
|
let feedbacksPerRequest = 25;
|
|
let feedbackFilter = $state({ event: null, content: null, username: null });
|
|
let activeFeedback: typeof Feedback.prototype.dataValues | null = $state(null);
|
|
|
|
async function fetchFeedback(extendedFilter?: {
|
|
limit?: number;
|
|
from?: number;
|
|
hash?: string;
|
|
preview?: boolean;
|
|
}): Promise<Feedback[]> {
|
|
if (!browser) return [];
|
|
|
|
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/feedback`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
...feedbackFilter,
|
|
preview: extendedFilter?.preview ?? true,
|
|
hash: extendedFilter?.hash ?? undefined,
|
|
limit: extendedFilter?.limit ?? feedbacksPerRequest,
|
|
from: extendedFilter?.from ?? feedbacks.length
|
|
})
|
|
});
|
|
return await response.json();
|
|
}
|
|
|
|
async function openHashReport() {
|
|
if (!window.location.hash) return;
|
|
|
|
const requestedHash = window.location.hash.substring(1);
|
|
|
|
const hashFeedback = await fetchFeedback({ hash: requestedHash, preview: false });
|
|
if (!hashFeedback) {
|
|
await goto(window.location.href.split('#')[0], { replaceState: true });
|
|
return;
|
|
}
|
|
|
|
activeFeedback = hashFeedback[0];
|
|
}
|
|
|
|
onMount(async () => {
|
|
if (browser) window.addEventListener('hashchange', openHashReport);
|
|
});
|
|
onDestroy(() => {
|
|
if (browser) window.removeEventListener('hashchange', openHashReport);
|
|
});
|
|
</script>
|
|
|
|
<div class="h-full flex flex-row">
|
|
<div class="w-full flex flex-col overflow-hidden">
|
|
<HeaderBar
|
|
bind:feedbackFilter
|
|
onUpdate={() => fetchFeedback({ from: 0 }).then((r) => (feedbacks = r))}
|
|
/>
|
|
<hr class="divider my-1 mx-8 border-none" />
|
|
<div class="h-full overflow-scroll">
|
|
<table class="table table-fixed h-fit">
|
|
<thead>
|
|
<tr>
|
|
<th>Event</th>
|
|
<th>Titel</th>
|
|
<th>Nutzer</th>
|
|
<th>Datum</th>
|
|
<th>Inhalt</th>
|
|
</tr>
|
|
</thead>
|
|
<PaginationTableBody
|
|
onUpdate={async () =>
|
|
await fetchFeedback().then((feedback) => (feedbacks = [...feedbacks, ...feedback]))}
|
|
>
|
|
{#each feedbacks as feedback}
|
|
<tr
|
|
class="hover [&>*]:text-sm cursor-pointer"
|
|
class:bg-base-200={activeFeedback?.url_hash === feedback.url_hash}
|
|
onclick={async () => {
|
|
await goto(`${window.location.href.split('#')[0]}#${feedback.url_hash}`, {
|
|
replaceState: true
|
|
});
|
|
await openHashReport();
|
|
}}
|
|
>
|
|
<td title={feedback.event}>{feedback.event}</td>
|
|
<td class="overflow-hidden overflow-ellipsis">{feedback.title}</td>
|
|
<td class="flex">
|
|
{feedback.user?.username || ''}
|
|
{#if feedback.user}
|
|
<button
|
|
class="pl-1"
|
|
title="Nach Ersteller filtern"
|
|
onclick={(e) => {
|
|
e.stopPropagation();
|
|
feedbackFilter.username = feedback.user.username;
|
|
fetchFeedback({ from: 0 }).then((r) => (feedbacks = r));
|
|
}}
|
|
>
|
|
<MagnifyingGlass size="14" />
|
|
</button>
|
|
{/if}
|
|
</td>
|
|
<td
|
|
>{new Intl.DateTimeFormat('de-DE', {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
}).format(new Date(feedback.updatedAt))} Uhr</td
|
|
>
|
|
<td class="overflow-hidden overflow-ellipsis"
|
|
>{feedback.content}{feedback.content_stripped ? '...' : ''}</td
|
|
>
|
|
</tr>
|
|
{/each}
|
|
</PaginationTableBody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
{#if activeFeedback}
|
|
<div
|
|
class="relative flex flex-col w-2/5 h-[calc(100vh-3rem)] bg-base-200/50 px-4 py-6 overflow-scroll"
|
|
transition:fly={{ x: 200, duration: 200 }}
|
|
>
|
|
<div class="absolute right-2 top-2 flex justify-center">
|
|
<form class="dropdown dropdown-end">
|
|
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
<label tabindex="0" class="btn btn-sm btn-circle btn-ghost text-center">
|
|
<Share size="1rem" />
|
|
</label>
|
|
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
<ul
|
|
tabindex="0"
|
|
class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-max"
|
|
>
|
|
<li>
|
|
<button
|
|
onclick={() => {
|
|
navigator.clipboard.writeText(
|
|
`${window.location.protocol}//${window.location.host}${env.PUBLIC_BASE_PATH}/admin/reports#${activeFeedback.url_hash}`
|
|
);
|
|
}}
|
|
>
|
|
Internen Link kopieren
|
|
</button>
|
|
<button
|
|
onclick={() =>
|
|
navigator.clipboard.writeText(
|
|
`${window.location.protocol}//${window.location.host}${env.PUBLIC_BASE_PATH}/report/${activeFeedback.url_hash}`
|
|
)}>Öffentlichen Link kopieren</button
|
|
>
|
|
</li>
|
|
</ul>
|
|
</form>
|
|
<button
|
|
class="btn btn-sm btn-circle btn-ghost"
|
|
onclick={() => {
|
|
activeFeedback = null;
|
|
goto(window.location.href.split('#')[0], { replaceState: true });
|
|
}}>✕</button
|
|
>
|
|
</div>
|
|
<h3 class="font-roboto font-semibold text-2xl mb-2">Feedback</h3>
|
|
<div class="w-full">
|
|
<Input readonly={true} size="sm" value={activeFeedback.event} pickyWidth={false}>
|
|
{#snippet label()}
|
|
<span>Event</span>
|
|
{/snippet}
|
|
</Input>
|
|
<Input readonly={true} size="sm" value={activeFeedback.title} pickyWidth={false}>
|
|
{#snippet label()}
|
|
<span>Titel</span>
|
|
{/snippet}
|
|
</Input>
|
|
<Textarea readonly={true} rows={4} label="Inhalt" value={activeFeedback.content} />
|
|
<div class="divider mb-1"></div>
|
|
<Input
|
|
readonly={true}
|
|
size="sm"
|
|
value={activeFeedback.user?.username || ''}
|
|
pickyWidth={false}
|
|
>
|
|
{#snippet label()}
|
|
<span>Nutzer</span>
|
|
{/snippet}
|
|
</Input>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|