bytedream 5935b0d561
All checks were successful
delpoy / build-and-deploy (push) Successful in 48s
stick header and filter bar to top
2024-12-28 01:29:05 +01:00

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>