diff --git a/packages/frontend/src/routes/Home.tsx b/packages/frontend/src/routes/Home.tsx
index 3fc2d32..02df01d 100644
--- a/packages/frontend/src/routes/Home.tsx
+++ b/packages/frontend/src/routes/Home.tsx
@@ -1,21 +1,35 @@
import { ListMusic, Music3 } from "lucide-react";
-import { Link } from "react-router-dom";
+import { ReactNode } from "react";
+import { Link, To } from "react-router-dom";
export function Home() {
return (
-
-
-
-
-
-
+
+
+ Utwory
+
+
+
+ Repertuary
+
);
}
+
+namespace LinkCard {
+ export interface Props {
+ readonly to: To;
+ readonly children: ReactNode;
+ }
+}
+
+function LinkCard(props: LinkCard.Props) {
+ return (
+
+
+ {props.children}
+
+
+ );
+}
diff --git a/packages/frontend/src/routes/Piece.tsx b/packages/frontend/src/routes/Piece.tsx
index 8765ec6..73a2ae4 100644
--- a/packages/frontend/src/routes/Piece.tsx
+++ b/packages/frontend/src/routes/Piece.tsx
@@ -1,11 +1,11 @@
-import { Attachment, denormalizeSystemInformation, type Piece, pieceCache, User } from "@/cache";
+import { Attachment, denormalizeSystemInformation, type Piece, pieceCache } from "@/cache";
import { API_URL_PREFIX, client } from "@/client";
import { Button, buttonVariants } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { useLoadingEffect } from "@/hooks/useLoading";
import { mapProp, Update, Updater } from "@/hooks/useStore";
-import { timeout } from "@/lib/utils";
+import { created, modified, saveDelay } from "@/snippets";
import { Label } from "@radix-ui/react-label";
import clsx from "clsx";
import { PieceId } from "common";
@@ -78,7 +78,7 @@ function PieceForm(props: PieceForm.Props) {
const onSubmit: FormEventHandler = async (e) => {
e.preventDefault();
- const delay = timeout(250);
+ const delay = saveDelay();
try {
setIsSaving(true);
@@ -271,14 +271,10 @@ function AttachmentRow(props: AttachmentRow.Props) {
{props.attachment.mediaType}
- {props.attachment.createdAt}
- {Option.isSome(props.attachment.createdBy) && <>
przez {props.attachment.createdBy.value.username}>}
+ {created(props.attachment)}
- {Option.isNone(props.attachment.modifiedAt) && Option.isNone(props.attachment.modifiedBy) ? "\u2014"
- : Option.isSome(props.attachment.modifiedAt) && Option.isNone(props.attachment.modifiedBy) ? props.attachment.modifiedAt.value
- : Option.isNone(props.attachment.modifiedAt) ? `przez ${(props.attachment.modifiedBy as Option.Some).value.username}`
- : <>{props.attachment.modifiedAt.value}
przez {(props.attachment.modifiedBy as Option.Some).value.username}>}
+ {modified(props.attachment)}
@@ -315,7 +311,7 @@ function AttachmentForm(props: AttachmentForm.Props) {
return;
}
- const delay = timeout(250);
+ const delay = saveDelay();
try {
setIsLoading(true);
diff --git a/packages/frontend/src/routes/Pieces.tsx b/packages/frontend/src/routes/Pieces.tsx
index 96d0977..5f9bd1a 100644
--- a/packages/frontend/src/routes/Pieces.tsx
+++ b/packages/frontend/src/routes/Pieces.tsx
@@ -1,4 +1,4 @@
-import { pieceCache, User } from "@/cache";
+import { pieceCache } from "@/cache";
import { client } from "@/client";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
@@ -6,10 +6,11 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { useLoadingEffect } from "@/hooks/useLoading";
+import { authors, created, DEBOUNCE, modified } from "@/snippets";
import { PieceId } from "common";
-import { Cause, Clock, Duration, Effect, Option } from "effect";
+import { Cause, Effect } from "effect";
import { Loader2, Plus } from "lucide-react";
-import { FormEventHandler, ReactNode, useId, useState } from "react";
+import { FormEventHandler, useId, useRef, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
export function Pieces() {
@@ -17,8 +18,10 @@ export function Pieces() {
const [name, setName] = useState("");
const [author, setAuthor] = useState("");
+ const debounce = useRef(Effect.void);
+
const { isLoading, error, data: pieceIds } = useLoadingEffect(Effect.gen(function* () {
- yield* Clock.sleep(Duration.millis(500));
+ yield* debounce.current;
const { error, data } = yield* Effect.promise((signal) => client.piece.get({
query: {
...(name !== "" ? { name } : undefined),
@@ -50,14 +53,20 @@ export function Pieces() {
type="text"
placeholder="Tytuł"
value={name}
- onChange={(e) => setName(e.target.value)}
+ onChange={(e) => {
+ setName(e.target.value);
+ debounce.current = DEBOUNCE;
+ }}
/>
setAuthor(e.target.value)}
+ onChange={(e) => {
+ setAuthor(e.target.value);
+ debounce.current = DEBOUNCE;
+ }}
/>
@@ -120,30 +129,19 @@ function PieceRow(props: PieceRow.Props) {
);
}
- const composerParts: ReactNode[] = [];
-
- if (Option.isSome(piece.composer)) composerParts.push(piece.composer.value);
- if (Option.isSome(piece.arranger)) composerParts.push(`opracowanie: ${piece.arranger.value}`);
- if (Option.isSome(piece.lyricist)) composerParts.push(`słowa: ${piece.lyricist.value}`);
- if (composerParts.length === 0) composerParts.push(Nieznani);
-
return (
{piece.name}
- {...composerParts.flatMap((x, i, a) => i < a.length - 1 ? [x,
] : [x])}
+ {authors(piece)}
- {piece.createdAt}
- {Option.isSome(piece.createdBy) && <>
przez {piece.createdBy.value.username}>}
+ {created(piece)}
- {Option.isNone(piece.modifiedAt) && Option.isNone(piece.modifiedBy) ? "\u2014"
- : Option.isSome(piece.modifiedAt) && Option.isNone(piece.modifiedBy) ? piece.modifiedAt.value
- : Option.isNone(piece.modifiedAt) ? `przez ${(piece.modifiedBy as Option.Some).value.username}`
- : <>{piece.modifiedAt.value}
przez {(piece.modifiedBy as Option.Some).value.username}>}
+ {modified(piece)}
);
diff --git a/packages/frontend/src/routes/Repertoire.tsx b/packages/frontend/src/routes/Repertoire.tsx
index b5da472..2a07c06 100644
--- a/packages/frontend/src/routes/Repertoire.tsx
+++ b/packages/frontend/src/routes/Repertoire.tsx
@@ -6,11 +6,11 @@ import { Label } from "@/components/ui/label";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { useLoadingEffect } from "@/hooks/useLoading";
import { mapProp, Update, Updater } from "@/hooks/useStore";
-import { timeout } from "@/lib/utils";
+import { authors, saveDelay } from "@/snippets";
import { PieceId, RepertoireId } from "common";
-import { Array, Cause, Effect, Option, pipe } from "effect";
+import { Array, Cause, Effect, pipe } from "effect";
import { CircleMinus, Loader2 } from "lucide-react";
-import { FormEventHandler, ReactNode, useCallback, useId, useState } from "react";
+import { FormEventHandler, useCallback, useId, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
export function Repertoire() {
@@ -67,7 +67,7 @@ function RepertoireForm(props: RepertoireForm.Props) {
const onSubmit: FormEventHandler = async (e) => {
e.preventDefault();
- const delay = timeout(250);
+ const delay = saveDelay();
try {
setIsSaving(true);
@@ -200,13 +200,6 @@ function EntryRow(props: EntryRow.Props) {
}, [props]);
- const composerParts: ReactNode[] = [];
-
- if (Option.isSome(props.piece.composer)) composerParts.push(props.piece.composer.value);
- if (Option.isSome(props.piece.arranger)) composerParts.push(`opracowanie: ${props.piece.arranger.value}`);
- if (Option.isSome(props.piece.lyricist)) composerParts.push(`słowa: ${props.piece.lyricist.value}`);
- if (composerParts.length === 0) composerParts.push(Nieznani);
-
return (
@@ -216,7 +209,7 @@ function EntryRow(props: EntryRow.Props) {
{props.piece.name}
- {...composerParts.flatMap((x, i, a) => i < a.length - 1 ? [x,
] : [x])}
+ {authors(props.piece)}