Compare commits
	
		
			No commits in common. "9e7aa15cedc99fbc25b7b521f70aaef5e57988d6" and "22b74987e53038dd1a9623584545b49e6a0da787" have entirely different histories.
		
	
	
		
			
				9e7aa15ced
			
			...
			
				22b74987e5
			
		
	
		
					 14 changed files with 69 additions and 317 deletions
				
			
		| 
						 | 
				
			
			@ -33,6 +33,7 @@ button {
 | 
			
		|||
.card2 {
 | 
			
		||||
  @apply bg-base-200;
 | 
			
		||||
  @apply rounded-xl;
 | 
			
		||||
  @apply mb-8;
 | 
			
		||||
  @apply flex flex-col;
 | 
			
		||||
  @apply border-solid border-base-content/30 border-[1px];
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -50,14 +51,10 @@ button {
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  .c-light {
 | 
			
		||||
    @apply bg-base-content/20 py-1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .c-vlight {
 | 
			
		||||
    @apply bg-base-content/10 py-1;
 | 
			
		||||
    @apply bg-base-content/20;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .c-primary {
 | 
			
		||||
    @apply bg-primary text-primary-content py-1;
 | 
			
		||||
    @apply bg-primary text-primary-content;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,7 +7,7 @@
 | 
			
		|||
  export let backHref: string | undefined = undefined;
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div class="flex flex-row">
 | 
			
		||||
<div class="mb-4 flex flex-row">
 | 
			
		||||
  <div class="flex flex-wrap items-center gap-2">
 | 
			
		||||
    {#if backHref}
 | 
			
		||||
      <a href={backHref} class="btn btn-sm btn-circle btn-ghost">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,15 +0,0 @@
 | 
			
		|||
<!-- Button showing the amount of entry versions -->
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
  import { mdiHistory } from "@mdi/js";
 | 
			
		||||
  import Icon from "./Icon.svelte";
 | 
			
		||||
 | 
			
		||||
  export let n: number;
 | 
			
		||||
  export let href: string;
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
{#if n > 1}
 | 
			
		||||
  <a {href} class="btn btn-xs btn-primary rounded-full">
 | 
			
		||||
    <Icon path={mdiHistory} size={1.2} />
 | 
			
		||||
    <span>{n}</span>
 | 
			
		||||
  </a>
 | 
			
		||||
{/if}
 | 
			
		||||
| 
						 | 
				
			
			@ -39,14 +39,6 @@ export async function getEntry(id: number): Promise<Entry> {
 | 
			
		|||
  return mapEntry(entry);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getEntryNVersions(id: number): Promise<number> {
 | 
			
		||||
  return prisma.entryVersion.count({ where: { entry_id: id } });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getEntryNExecutions(id: number): Promise<number> {
 | 
			
		||||
  return prisma.entryExecution.count({ where: { entry_id: id } });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getEntryVersions(id: number): Promise<EntryVersion[]> {
 | 
			
		||||
  const versions = await prisma.entryVersion.findMany({
 | 
			
		||||
    where: { entry_id: id },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,28 +12,19 @@ import {
 | 
			
		|||
  getEntries,
 | 
			
		||||
  getEntry,
 | 
			
		||||
  getEntryExecutions,
 | 
			
		||||
  getEntryNExecutions,
 | 
			
		||||
  getEntryNVersions,
 | 
			
		||||
  getEntryVersions,
 | 
			
		||||
  newEntry,
 | 
			
		||||
  newEntryExecution,
 | 
			
		||||
  newEntryVersion,
 | 
			
		||||
} from "$lib/server/query";
 | 
			
		||||
import { executionsDiff, versionsDiff } from "$lib/shared/util/diff";
 | 
			
		||||
import { versionsDiff } from "$lib/shared/util/diff";
 | 
			
		||||
 | 
			
		||||
const ZEntityId = fields.EntityId();
 | 
			
		||||
 | 
			
		||||
export const entryRouter = t.router({
 | 
			
		||||
  get: t.procedure.input(ZEntityId).query(async (opts) =>
 | 
			
		||||
    trpcWrap(async () => {
 | 
			
		||||
      const [entry, n_versions, n_executions] = await Promise.all([
 | 
			
		||||
        getEntry(opts.input),
 | 
			
		||||
        getEntryNVersions(opts.input),
 | 
			
		||||
        getEntryNExecutions(opts.input),
 | 
			
		||||
      ]);
 | 
			
		||||
      return { ...entry, n_versions, n_executions };
 | 
			
		||||
    })
 | 
			
		||||
  ),
 | 
			
		||||
  get: t.procedure
 | 
			
		||||
    .input(ZEntityId)
 | 
			
		||||
    .query(async (opts) => trpcWrap(async () => getEntry(opts.input))),
 | 
			
		||||
  list: t.procedure
 | 
			
		||||
    .input(ZEntriesQuery)
 | 
			
		||||
    .query(async (opts) =>
 | 
			
		||||
| 
						 | 
				
			
			@ -57,12 +48,6 @@ export const entryRouter = t.router({
 | 
			
		|||
  executions: t.procedure
 | 
			
		||||
    .input(ZEntityId)
 | 
			
		||||
    .query(async (opts) => trpcWrap(async () => getEntryExecutions(opts.input))),
 | 
			
		||||
  executionsDiff: t.procedure.input(ZEntityId).query(async (opts) =>
 | 
			
		||||
    trpcWrap(async () => {
 | 
			
		||||
      const executions = await getEntryExecutions(opts.input);
 | 
			
		||||
      return executionsDiff(executions);
 | 
			
		||||
    })
 | 
			
		||||
  ),
 | 
			
		||||
  create: t.procedure
 | 
			
		||||
    .input(ZEntryNew)
 | 
			
		||||
    .mutation(async (opts) =>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,11 +1,5 @@
 | 
			
		|||
import { diffWords } from "diff";
 | 
			
		||||
import type {
 | 
			
		||||
  EntryVersion,
 | 
			
		||||
  EntryExecution,
 | 
			
		||||
  Category,
 | 
			
		||||
  Option,
 | 
			
		||||
  UserTag,
 | 
			
		||||
} from "$lib/shared/model";
 | 
			
		||||
import type { EntryVersion, Category, Option, UserTag } from "$lib/shared/model";
 | 
			
		||||
 | 
			
		||||
export type EntryVersionChange = {
 | 
			
		||||
  id: number;
 | 
			
		||||
| 
						 | 
				
			
			@ -18,14 +12,6 @@ export type EntryVersionChange = {
 | 
			
		|||
  priority?: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type EntryExecutionChange = {
 | 
			
		||||
  id: number;
 | 
			
		||||
  author: UserTag;
 | 
			
		||||
  created_at: Date;
 | 
			
		||||
 | 
			
		||||
  text: Diff.Change[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function newOrUndef<T>(o: T, n: T): T | undefined {
 | 
			
		||||
  return o === n ? undefined : n;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -58,29 +44,3 @@ export function versionsDiff(versions: EntryVersion[]): EntryVersionChange[] {
 | 
			
		|||
 | 
			
		||||
  return changes;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function executionsDiff(executions: EntryExecution[]): EntryExecutionChange[] {
 | 
			
		||||
  let prev = executions[executions.length - 1];
 | 
			
		||||
  const changes: EntryExecutionChange[] = [
 | 
			
		||||
    {
 | 
			
		||||
      ...prev,
 | 
			
		||||
      text: diffWords("", prev.text),
 | 
			
		||||
    },
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  for (let i = executions.length - 2; i >= 0; i--) {
 | 
			
		||||
    const v = executions[i];
 | 
			
		||||
    const text = diffWords(prev.text, v.text);
 | 
			
		||||
 | 
			
		||||
    changes.push({
 | 
			
		||||
      id: v.id,
 | 
			
		||||
      author: v.author,
 | 
			
		||||
      created_at: v.created_at,
 | 
			
		||||
      text,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    prev = v;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return changes;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,32 +0,0 @@
 | 
			
		|||
import { expect, test, vi } from "vitest";
 | 
			
		||||
import { humanDate } from ".";
 | 
			
		||||
 | 
			
		||||
const MINUTE = 60000;
 | 
			
		||||
const HOUR = 3_600_000;
 | 
			
		||||
const DAY = 24 * HOUR;
 | 
			
		||||
 | 
			
		||||
test.each([
 | 
			
		||||
  { s: 0, txt: "jetzt gerade" },
 | 
			
		||||
  { s: -30 * MINUTE, txt: "vor 30 Minuten" },
 | 
			
		||||
  { s: -11 * HOUR, txt: "vor 11 Stunden" },
 | 
			
		||||
  { s: -DAY, txt: "gestern" },
 | 
			
		||||
  { s: -2.5 * DAY, txt: "vor 2 Tagen" },
 | 
			
		||||
  { s: -2.6 * DAY, txt: "vor 3 Tagen" },
 | 
			
		||||
  { s: -3.5 * DAY, txt: "vor 3 Tagen" },
 | 
			
		||||
  { s: -4 * DAY, txt: "am 28.12.2023, 12:00" },
 | 
			
		||||
 | 
			
		||||
  { s: 28 * MINUTE, txt: "in 28 Minuten" },
 | 
			
		||||
  { s: 8 * HOUR, txt: "in 8 Stunden" },
 | 
			
		||||
  { s: DAY, txt: "morgen" },
 | 
			
		||||
  { s: 2.6 * DAY, txt: "in 3 Tagen" },
 | 
			
		||||
  { s: 4 * DAY, txt: "am 05.01.2024, 12:00" },
 | 
			
		||||
])("humanDate", ({ s, txt }) => {
 | 
			
		||||
  const mockDate = new Date(2024, 0, 1, 12, 0);
 | 
			
		||||
  vi.setSystemTime(mockDate);
 | 
			
		||||
 | 
			
		||||
  const dt = new Date(Number(mockDate) + s);
 | 
			
		||||
  const res = humanDate(dt, true);
 | 
			
		||||
  expect(res).toBe(txt);
 | 
			
		||||
 | 
			
		||||
  vi.useRealTimers();
 | 
			
		||||
});
 | 
			
		||||
| 
						 | 
				
			
			@ -4,19 +4,13 @@ import { TRPCClientError } from "@trpc/client";
 | 
			
		|||
import { error } from "@sveltejs/kit";
 | 
			
		||||
import { ZodError } from "zod";
 | 
			
		||||
 | 
			
		||||
const LOCALE = "de-DE";
 | 
			
		||||
 | 
			
		||||
function coerceDate(date: Date | string): Date {
 | 
			
		||||
  if (!(date instanceof Date)) {
 | 
			
		||||
    return new Date(date);
 | 
			
		||||
  }
 | 
			
		||||
  return date;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function formatDate(date: Date | string, time = false): string {
 | 
			
		||||
  const dt = coerceDate(date);
 | 
			
		||||
  let dt = date;
 | 
			
		||||
  if (!(dt instanceof Date)) {
 | 
			
		||||
    dt = new Date(dt);
 | 
			
		||||
  }
 | 
			
		||||
  if (time) {
 | 
			
		||||
    return dt.toLocaleString(LOCALE, {
 | 
			
		||||
    return dt.toLocaleString("de-DE", {
 | 
			
		||||
      day: "2-digit",
 | 
			
		||||
      month: "2-digit",
 | 
			
		||||
      year: "numeric",
 | 
			
		||||
| 
						 | 
				
			
			@ -24,7 +18,7 @@ export function formatDate(date: Date | string, time = false): string {
 | 
			
		|||
      minute: "2-digit",
 | 
			
		||||
    });
 | 
			
		||||
  } else {
 | 
			
		||||
    return dt.toLocaleDateString(LOCALE, {
 | 
			
		||||
    return dt.toLocaleDateString("de-DE", {
 | 
			
		||||
      day: "2-digit",
 | 
			
		||||
      month: "2-digit",
 | 
			
		||||
      year: "numeric",
 | 
			
		||||
| 
						 | 
				
			
			@ -32,42 +26,6 @@ export function formatDate(date: Date | string, time = false): string {
 | 
			
		|||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const MS_PER_DAY = 86400000;
 | 
			
		||||
 | 
			
		||||
function dateDiffInDays(a: Date, b: Date): number {
 | 
			
		||||
  const utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
 | 
			
		||||
  const utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
 | 
			
		||||
 | 
			
		||||
  return Math.round((utc2 - utc1) / MS_PER_DAY);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function humanDate(date: Date | string, time = false): string {
 | 
			
		||||
  const now = new Date();
 | 
			
		||||
  const dt = coerceDate(date);
 | 
			
		||||
  const threshold = 302400000; // 3.5 * 24 * 3_600_000
 | 
			
		||||
  const diff = Number(dt) - Number(now); // pos: Future, neg: Past
 | 
			
		||||
  if (Math.abs(diff) > threshold) return "am " + formatDate(date, time);
 | 
			
		||||
 | 
			
		||||
  const intl = new Intl.RelativeTimeFormat(LOCALE);
 | 
			
		||||
 | 
			
		||||
  const diffDays = dateDiffInDays(now, dt);
 | 
			
		||||
  if (diffDays !== 0) {
 | 
			
		||||
    if (diffDays === 1) return "morgen";
 | 
			
		||||
    if (diffDays === -1) return "gestern";
 | 
			
		||||
    return intl.format(diffDays, "day");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (time) {
 | 
			
		||||
    const diffHours = Math.round(diff / 3_600_000);
 | 
			
		||||
    if (diffHours !== 0) return intl.format(diffHours, "hour");
 | 
			
		||||
 | 
			
		||||
    const diffMinutes = Math.round(diff / 60_000);
 | 
			
		||||
    if (diffMinutes !== 0) return intl.format(diffMinutes, "minute");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return time ? "jetzt gerade" : "heute";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function formatBool(val: boolean): string {
 | 
			
		||||
  return val ? "Ja" : "Nein";
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -48,6 +48,6 @@
 | 
			
		|||
    </div>
 | 
			
		||||
  </nav>
 | 
			
		||||
</div>
 | 
			
		||||
<div class="max-w-[100vw] p-4 pb-8 flex flex-col gap-4">
 | 
			
		||||
<div class="max-w-[100vw] p-4 pb-8">
 | 
			
		||||
  <slot />
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,18 +1,15 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
  import type { PageData } from "./$types";
 | 
			
		||||
  import { formatDate, humanDate } from "$lib/shared/util";
 | 
			
		||||
  import { formatDate } from "$lib/shared/util";
 | 
			
		||||
  import UserField from "$lib/components/table/UserField.svelte";
 | 
			
		||||
  import RoomField from "$lib/components/table/RoomField.svelte";
 | 
			
		||||
  import CategoryField from "$lib/components/table/CategoryField.svelte";
 | 
			
		||||
  import Markdown from "$lib/components/ui/Markdown.svelte";
 | 
			
		||||
  import Header from "$lib/components/ui/Header.svelte";
 | 
			
		||||
  import Icon from "$lib/components/ui/Icon.svelte";
 | 
			
		||||
  import { mdiPencil } from "@mdi/js";
 | 
			
		||||
  import VersionsButton from "$lib/components/ui/VersionsButton.svelte";
 | 
			
		||||
  import { mdiHistory } from "@mdi/js";
 | 
			
		||||
 | 
			
		||||
  export let data: PageData;
 | 
			
		||||
 | 
			
		||||
  $: basePath = `/entry/${data.entry.id}`;
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
| 
						 | 
				
			
			@ -26,13 +23,16 @@
 | 
			
		|||
  {#if data.entry.current_version.priority}
 | 
			
		||||
    <div class="badge ellipsis badge-warning">Priorität</div>
 | 
			
		||||
  {/if}
 | 
			
		||||
</Header>
 | 
			
		||||
 | 
			
		||||
<p class="text-sm flex flex-row gap-2">
 | 
			
		||||
  <span>Erstellt {humanDate(data.entry.created_at, true)}</span>
 | 
			
		||||
  <span>·</span>
 | 
			
		||||
  <span>Zu erledigen {humanDate(data.entry.current_version.date)}</span>
 | 
			
		||||
</p>
 | 
			
		||||
  <a
 | 
			
		||||
    href="/entry/{data.entry.id}/versions"
 | 
			
		||||
    class="btn btn-sm btn-primary ml-auto"
 | 
			
		||||
    slot="rightBtn"
 | 
			
		||||
  >
 | 
			
		||||
    <Icon path={mdiHistory} />
 | 
			
		||||
    <span class="hidden sm:inline">Versionen</span>
 | 
			
		||||
  </a>
 | 
			
		||||
</Header>
 | 
			
		||||
 | 
			
		||||
<div class="card2">
 | 
			
		||||
  <div class="row c-light text-sm">Patient</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -40,48 +40,28 @@
 | 
			
		|||
    {#if data.entry.patient.room}
 | 
			
		||||
      <RoomField room={data.entry.patient.room} />
 | 
			
		||||
    {/if}
 | 
			
		||||
    <a href="/patient/{data.entry.patient.id}">
 | 
			
		||||
      {data.entry.patient.first_name}
 | 
			
		||||
      {data.entry.patient.last_name}
 | 
			
		||||
      ({data.entry.patient.age})
 | 
			
		||||
    </a>
 | 
			
		||||
    {data.entry.patient.first_name}
 | 
			
		||||
    {data.entry.patient.last_name}
 | 
			
		||||
    ({data.entry.patient.age})
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<div class="card2">
 | 
			
		||||
  <div class="row c-light text-sm items-center justify-between">
 | 
			
		||||
    Beschreibung
 | 
			
		||||
    <div>
 | 
			
		||||
      <VersionsButton href="{basePath}/versions" n={data.entry.n_versions} />
 | 
			
		||||
      <button class="btn btn-circle btn-sm btn-ghost">
 | 
			
		||||
        <Icon path={mdiPencil} size={1.2} />
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="row c-light text-sm">Beschreibung</div>
 | 
			
		||||
  <div class="row">
 | 
			
		||||
    <p class="prose">
 | 
			
		||||
      <Markdown src={data.entry.current_version.text} />
 | 
			
		||||
    </p>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="row c-vlight text-sm">
 | 
			
		||||
    Zuletzt bearbeitet am {formatDate(data.entry.current_version.created_at, true)} von 
 | 
			
		||||
    <UserField user={data.entry.current_version.author} filterName="author" />
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
{#if data.entry.execution}
 | 
			
		||||
  <div class="card2">
 | 
			
		||||
    <div class="row c-light text-sm items-center justify-between">
 | 
			
		||||
    <div class="row c-light text-sm">
 | 
			
		||||
      <p>
 | 
			
		||||
        Erledigt am {formatDate(data.entry.execution.created_at, true)} von
 | 
			
		||||
        <UserField user={data.entry.execution.author} filterName="executor" />:
 | 
			
		||||
      </p>
 | 
			
		||||
      <div>
 | 
			
		||||
        <VersionsButton href="{basePath}/executions" n={data.entry.n_executions} />
 | 
			
		||||
        <button class="btn btn-circle btn-xs btn-ghost">
 | 
			
		||||
          <Icon path={mdiPencil} size={1.2} />
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="row">
 | 
			
		||||
      <p class="prose">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,45 +0,0 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
  import CategoryField from "$lib/components/table/CategoryField.svelte";
 | 
			
		||||
  import UserField from "$lib/components/table/UserField.svelte";
 | 
			
		||||
  import { formatBool, formatDate } from "$lib/shared/util";
 | 
			
		||||
  import type { PageData } from "./$types";
 | 
			
		||||
  import Header from "$lib/components/ui/Header.svelte";
 | 
			
		||||
  import { page } from "$app/stores";
 | 
			
		||||
 | 
			
		||||
  export let data: PageData;
 | 
			
		||||
  $: entryId = $page.params.id;
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<svelte:head>
 | 
			
		||||
  <title>Eintrag #{entryId} - Erledigt</title>
 | 
			
		||||
</svelte:head>
 | 
			
		||||
 | 
			
		||||
<Header title="Eintrag #{entryId} - Erledigt" backHref="/entry/{entryId}" />
 | 
			
		||||
 | 
			
		||||
{#each data.versions as version, i}
 | 
			
		||||
  <div class="card2">
 | 
			
		||||
    <div class="row c-light text-sm">
 | 
			
		||||
      #{i + 1} 
 | 
			
		||||
      <UserField user={version.author} />, {formatDate(version.created_at, true)}
 | 
			
		||||
    </div>
 | 
			
		||||
    {#if version.text.length > 0}
 | 
			
		||||
      <div class="row whitespace-pre-wrap">
 | 
			
		||||
        {#each version.text as change}
 | 
			
		||||
          <span class:added={change.added} class:removed={change.removed}>
 | 
			
		||||
            {change.value}
 | 
			
		||||
          </span>
 | 
			
		||||
        {/each}
 | 
			
		||||
      </div>
 | 
			
		||||
    {/if}
 | 
			
		||||
  </div>
 | 
			
		||||
{/each}
 | 
			
		||||
 | 
			
		||||
<style lang="postcss">
 | 
			
		||||
  .added {
 | 
			
		||||
    @apply bg-success/20;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .removed {
 | 
			
		||||
    @apply bg-error/20;
 | 
			
		||||
  }
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,13 +0,0 @@
 | 
			
		|||
import { ZUrlEntityId } from "$lib/shared/model/validation";
 | 
			
		||||
import { trpc } from "$lib/shared/trpc";
 | 
			
		||||
import { loadWrap } from "$lib/shared/util";
 | 
			
		||||
import type { PageLoad } from "./$types";
 | 
			
		||||
 | 
			
		||||
export const load: PageLoad = async (event) => {
 | 
			
		||||
  const id = ZUrlEntityId.parse(event.params.id);
 | 
			
		||||
  const versions = await loadWrap(async () =>
 | 
			
		||||
    trpc(event).entry.executionsDiff.query(id)
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return { versions };
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -16,44 +16,45 @@
 | 
			
		|||
 | 
			
		||||
<Header title="Eintrag #{entryId} - Versionen" backHref="/entry/{entryId}" />
 | 
			
		||||
 | 
			
		||||
{#each data.versions as version, i}
 | 
			
		||||
  <div class="card2">
 | 
			
		||||
    <div class="row c-light text-sm">
 | 
			
		||||
      #{i + 1} 
 | 
			
		||||
      <UserField user={version.author} />, {formatDate(version.created_at, true)}
 | 
			
		||||
    </div>
 | 
			
		||||
    {#if version.category}
 | 
			
		||||
      <div class="row">
 | 
			
		||||
        <div>Kategeorie</div>
 | 
			
		||||
        <div><CategoryField category={version.category} /></div>
 | 
			
		||||
<div class="overflow-x-auto">
 | 
			
		||||
  {#each data.versions as version}
 | 
			
		||||
    <div class="card2">
 | 
			
		||||
      <div class="row c-light text-sm">
 | 
			
		||||
        <UserField user={version.author} />, {formatDate(version.created_at, true)}
 | 
			
		||||
      </div>
 | 
			
		||||
    {/if}
 | 
			
		||||
    {#if version.text.length > 0}
 | 
			
		||||
      <div class="row">
 | 
			
		||||
        <div>Text</div>
 | 
			
		||||
        <div class="whitespace-pre-wrap">
 | 
			
		||||
          {#each version.text as change}
 | 
			
		||||
            <span class:added={change.added} class:removed={change.removed}>
 | 
			
		||||
              {change.value}
 | 
			
		||||
            </span>
 | 
			
		||||
          {/each}
 | 
			
		||||
      {#if version.category}
 | 
			
		||||
        <div class="row">
 | 
			
		||||
          <div>Kategeorie</div>
 | 
			
		||||
          <div><CategoryField category={version.category} /></div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    {/if}
 | 
			
		||||
    {#if version.date !== undefined}
 | 
			
		||||
      <div class="row">
 | 
			
		||||
        <div>Datum</div>
 | 
			
		||||
        <div>{formatDate(version.date)}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
    {/if}
 | 
			
		||||
    {#if version.priority !== undefined}
 | 
			
		||||
      <div class="row">
 | 
			
		||||
        <div>Priorität</div>
 | 
			
		||||
        <div>{formatBool(version.priority)}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
    {/if}
 | 
			
		||||
  </div>
 | 
			
		||||
{/each}
 | 
			
		||||
      {/if}
 | 
			
		||||
      {#if version.text.length > 0}
 | 
			
		||||
        <div class="row">
 | 
			
		||||
          <div>Text</div>
 | 
			
		||||
          <div class="whitespace-pre-wrap">
 | 
			
		||||
            {#each version.text as change}
 | 
			
		||||
              <span class:added={change.added} class:removed={change.removed}>
 | 
			
		||||
                {change.value}
 | 
			
		||||
              </span>
 | 
			
		||||
            {/each}
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      {/if}
 | 
			
		||||
      {#if version.date !== undefined}
 | 
			
		||||
        <div class="row">
 | 
			
		||||
          <div>Datum</div>
 | 
			
		||||
          <div>{formatDate(version.date)}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      {/if}
 | 
			
		||||
      {#if version.priority !== undefined}
 | 
			
		||||
        <div class="row">
 | 
			
		||||
          <div>Priorität</div>
 | 
			
		||||
          <div>{formatBool(version.priority)}</div>
 | 
			
		||||
        </div>
 | 
			
		||||
      {/if}
 | 
			
		||||
    </div>
 | 
			
		||||
  {/each}
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<style lang="postcss">
 | 
			
		||||
  .row > div:first-child {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,6 @@
 | 
			
		|||
import {
 | 
			
		||||
  getEntries,
 | 
			
		||||
  getEntry,
 | 
			
		||||
  getEntryNExecutions,
 | 
			
		||||
  getEntryNVersions,
 | 
			
		||||
  getEntryVersions,
 | 
			
		||||
  newEntry,
 | 
			
		||||
  newEntryExecution,
 | 
			
		||||
| 
						 | 
				
			
			@ -34,11 +32,6 @@ test("create entry", async () => {
 | 
			
		|||
  expect(entry.current_version.text).toBe(TEST_VERSION.text);
 | 
			
		||||
  expect(entry.current_version.date).toStrictEqual(TEST_VERSION.date);
 | 
			
		||||
  expect(entry.current_version.priority).toBe(TEST_VERSION.priority);
 | 
			
		||||
 | 
			
		||||
  const nVersions = await getEntryNVersions(eId);
 | 
			
		||||
  expect(nVersions).toBe(1);
 | 
			
		||||
  const nExecutions = await getEntryNExecutions(eId);
 | 
			
		||||
  expect(nExecutions).toBe(0);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test("create entry version", async () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -72,9 +65,6 @@ test("create entry version", async () => {
 | 
			
		|||
  expect(history[0]).toMatchObject(expectedVersion);
 | 
			
		||||
  expect(history[1].text).toBe(TEST_VERSION.text);
 | 
			
		||||
  expect(history[0].created_at).greaterThan(history[1].created_at);
 | 
			
		||||
 | 
			
		||||
  const nVersions = await getEntryNVersions(eId);
 | 
			
		||||
  expect(nVersions).toBe(2);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test("create entry version (partial)", async () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -127,9 +117,6 @@ test("create entry execution", async () => {
 | 
			
		|||
  expect(entry.execution?.id).toBe(xId);
 | 
			
		||||
  expect(entry.execution?.author.id).toBe(1);
 | 
			
		||||
  expect(entry.execution?.text).toBe(text);
 | 
			
		||||
 | 
			
		||||
  const nExecutions = await getEntryNExecutions(eId);
 | 
			
		||||
  expect(nExecutions).toBe(1);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test("create entry execution (update)", async () => {
 | 
			
		||||
| 
						 | 
				
			
			@ -145,9 +132,6 @@ test("create entry execution (update)", async () => {
 | 
			
		|||
  expect(entry.execution?.id).toBe(x2);
 | 
			
		||||
  expect(entry.execution?.text).toBe("x2");
 | 
			
		||||
  expect(entry.execution?.author.id).toBe(2);
 | 
			
		||||
 | 
			
		||||
  const nExecutions = await getEntryNExecutions(eId);
 | 
			
		||||
  expect(nExecutions).toBe(2);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test("create entry execution (wrong old xid)", async () => {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue