~timharek/timharek.no

610fcc490e93383c708e5eccaf122135948608c9 — Tim Hårek Andreassen a month ago 60649b2
feat: Add collapsable log entry

Signed-off-by: Tim Hårek Andreassen <tim@harek.no>
1 files changed, 35 insertions(+), 33 deletions(-)

M routes/logs/[slug].tsx
M routes/logs/[slug].tsx => routes/logs/[slug].tsx +35 -33
@@ 13,13 13,11 @@ import {
  Log,
  MovieEntry,
  RATING_MAX,
  Review,
} from "../../src/schemas.ts";
import { z } from "zod";

interface AvailableLogs {
  [key: string]: string[];
}
type AvailableLogs = Record<string, string[]>;

const availableLogs: AvailableLogs = {
  watched: ["movies.json", "tv_shows.json"],
  reading: ["books.json"],


@@ 27,10 25,10 @@ const availableLogs: AvailableLogs = {
  travel: ["travel.json"],
};

interface LogProps {
type LogProps = {
  entries: Entry[];
  page: Page;
}
};

export const handler: Handlers<LogProps, ServerState> = {
  async GET(req, ctx) {


@@ 153,7 151,7 @@ export default function Page({ data }: PageProps<LogProps>) {
              </div>
              <ul class="divide-gray-700 divide-y-2">
                {groupedEntries[year].map((item, index) => (
                  <Item key={index} item={item} />
                  <LogEntry key={index} item={item} />
                ))}
              </ul>
            </div>


@@ 217,10 215,10 @@ interface ItemProps {
  item: Entry;
}

function Item({ item }: ItemProps) {
function LogEntry({ item }: ItemProps) {
  if (item.type === "travel") {
    return (
      <ItemWrapper>
      <LogEntryListItem>
        <h3 class="md:col-span-2">{item.title}</h3>
        <div>
          <span aria-label="Country">


@@ 242,36 240,34 @@ function Item({ item }: ItemProps) {
              </>
            )}
        </div>
      </ItemWrapper>
      </LogEntryListItem>
    );
  }
  if (item.type === "game") {
    return (
      <ItemWrapper>
      <LogEntryListItem comment={item.review.comment}>
        <h3 class="md:col-span-2">{item.title} ({item.platform})</h3>
        <Stars rating={item.review.rating} />
        <div class="md:justify-self-end">
          <DateT date={item.date} />
        </div>
        <ReviewComment comment={item.review.comment} />
      </ItemWrapper>
      </LogEntryListItem>
    );
  }
  if (item.type === "book") {
    return (
      <ItemWrapper>
      <LogEntryListItem comment={item.review.comment}>
        <h3 class="md:col-span-2">{item.title}</h3>
        <Stars rating={item.review.rating} />
        <div class="md:justify-self-end">
          <DateT date={item.date} />
        </div>
        <ReviewComment comment={item.review.comment} />
      </ItemWrapper>
      </LogEntryListItem>
    );
  }
  if (item.type === "movie" || item.type === "tv") {
    return (
      <ItemWrapper>
      <LogEntryListItem comment={item.review.comment}>
        <h3 class="md:col-span-2">
          {item.title}
          {item.type === "tv" && ` S${item.season}`}


@@ 281,20 277,37 @@ function Item({ item }: ItemProps) {
        <div class="md:justify-self-end">
          <DateT date={item.date} />
        </div>
        <ReviewComment comment={item.review.comment} />
      </ItemWrapper>
      </LogEntryListItem>
    );
  }
  return <></>;
}

function ItemWrapper(
  { children }: {
const ITEM_WRAPPER_CLASS =
  "py-4 grid md:grid-cols-4 grid-cols-1 gap-4 hover:bg-zinc-800";

function LogEntryListItem(
  { children, comment }: {
    children?: ComponentChildren;
    comment?: string | null;
  },
) {
  if (comment) {
    return (
      <li>
        <details>
          <summary class={ITEM_WRAPPER_CLASS + " cursor-pointer"}>
            {children}
          </summary>
          <p className="py-2 pl-4">
            {comment}
          </p>
        </details>
      </li>
    );
  }
  return (
    <li class="py-4 grid md:grid-cols-4 grid-cols-1 gap-4 hover:bg-zinc-800">
    <li class={ITEM_WRAPPER_CLASS}>
      {children}
    </li>
  );


@@ 312,17 325,6 @@ function DateT({ date }: { date: Date }) {
  );
}

function ReviewComment({ comment }: { comment: Review["comment"] }) {
  if (!comment) {
    return <></>;
  }
  return (
    <p className="row-start-3 md:row-start-2 md:col-start-3 md:col-span-2">
      {comment}
    </p>
  );
}

function Stars({ rating }: { rating: number }) {
  const string = `${rating} out of 5 stars`;
  return <div aria-label={string} title={string}>{getStars(rating)}</div>;