Rename Home to Pieces
This commit is contained in:
243
packages/frontend/src/routes/Pieces.tsx
Normal file
243
packages/frontend/src/routes/Pieces.tsx
Normal file
@@ -0,0 +1,243 @@
|
||||
import { pieceCache, User } from "@/cache";
|
||||
import { client } from "@/client";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||
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 { PieceId } from "common";
|
||||
import { Cause, Clock, Duration, Effect, Option } from "effect";
|
||||
import { Loader2, Plus } from "lucide-react";
|
||||
import { FormEventHandler, ReactNode, useId, useState } from "react";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
|
||||
export function Pieces() {
|
||||
|
||||
const [name, setName] = useState("");
|
||||
const [author, setAuthor] = useState("");
|
||||
|
||||
const { isLoading, error, data: pieceIds } = useLoadingEffect(Effect.gen(function* () {
|
||||
yield* Clock.sleep(Duration.millis(500));
|
||||
const { error, data } = yield* Effect.promise((signal) => client.piece.get({
|
||||
query: {
|
||||
...(name !== "" ? { name } : undefined),
|
||||
...(author !== "" ? { author } : undefined),
|
||||
},
|
||||
fetch: { signal },
|
||||
}));
|
||||
|
||||
if (error !== null) {
|
||||
return yield* Effect.fail(error);
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
}), [name, author]);
|
||||
|
||||
return (
|
||||
<div className="p-4 overflow-y-auto flex flex-col items-start gap-4">
|
||||
<div className="flex flex-row gap-4">
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline">
|
||||
<Plus />Dodaj utwór
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<AddPieceDialogContent />
|
||||
</Dialog>
|
||||
<Input
|
||||
className="w-[32ch]"
|
||||
type="text"
|
||||
placeholder="Tytuł"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
<Input
|
||||
className="w-[32ch]"
|
||||
type="text"
|
||||
placeholder="Twórcy"
|
||||
value={author}
|
||||
onChange={(e) => setAuthor(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<Table>
|
||||
<TableHeader className="bg-white sticky top-0">
|
||||
<TableRow>
|
||||
<TableHead>Tytuł</TableHead>
|
||||
<TableHead>Twórcy</TableHead>
|
||||
<TableHead className="text-center">Dodano</TableHead>
|
||||
<TableHead className="text-center">Zmodyfikowano</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{isLoading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} >
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Loader2 className="animate-spin" />
|
||||
Ładowanie…
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : error !== null ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} className="text-center">
|
||||
{Cause.isUnknownException(error) ? "Wystąpił nieznany błąd" : `Wystąpił błąd: ${error.value}`}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
pieceIds.map((pieceId) => <PieceRow key={pieceId} pieceId={pieceId} />)
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
namespace PieceRow {
|
||||
export interface Props {
|
||||
readonly pieceId: PieceId;
|
||||
}
|
||||
}
|
||||
|
||||
function PieceRow(props: PieceRow.Props) {
|
||||
|
||||
const { isLoading, error, data: piece } = useLoadingEffect(Effect.uninterruptible(pieceCache.get(props.pieceId)), [props.pieceId]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4}>Ładowanie…</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
if (error !== null) {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4}>Wystąpił błąd: {error.value}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
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(<em>Nieznani</em>);
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<Link className="underline" to={`piece/${piece.pieceId}`}>{piece.name}</Link>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{...composerParts.flatMap((x, i, a) => i < a.length - 1 ? [x, <br />] : [x])}
|
||||
</TableCell>
|
||||
<TableCell className="text-center text-xs">
|
||||
{piece.createdAt}
|
||||
{Option.isSome(piece.createdBy) && <><br />przez {piece.createdBy.value.username}</>}
|
||||
</TableCell>
|
||||
<TableCell className="text-center text-xs">
|
||||
{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<User>).value.username}`
|
||||
: <>{piece.modifiedAt.value}<br />przez {(piece.modifiedBy as Option.Some<User>).value.username}</>}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
function AddPieceDialogContent() {
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [name, setName] = useState("");
|
||||
const [composer, setComposer] = useState("");
|
||||
const [lyricist, setLyricist] = useState("");
|
||||
const [arranger, setArranger] = useState("");
|
||||
|
||||
const nameId = useId();
|
||||
const composerId = useId();
|
||||
const lyricistId = useId();
|
||||
const arrangerId = useId();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const onSubmit: FormEventHandler<HTMLFormElement> = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const { data, error } = await client.piece.post({
|
||||
name,
|
||||
composer: composer.length > 0 ? composer : null,
|
||||
lyricist: lyricist.length > 0 ? lyricist : null,
|
||||
arranger: arranger.length > 0 ? arranger : null,
|
||||
});
|
||||
|
||||
if (error !== null) {
|
||||
console.error(error.value);
|
||||
return;
|
||||
}
|
||||
|
||||
navigate(`piece/${data.pieceId}`);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<form onSubmit={onSubmit}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Dodaj utwór</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="grid items-baseline grid-cols-4 gap-4 py-4">
|
||||
<Label htmlFor={nameId} className="text-right">Tytuł</Label>
|
||||
<Input
|
||||
id={nameId}
|
||||
className="col-span-3"
|
||||
type="text"
|
||||
value={name}
|
||||
autoFocus
|
||||
required
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
/>
|
||||
<Label htmlFor={composerId} className="text-right">Kompozytor</Label>
|
||||
<Input
|
||||
id={composerId}
|
||||
className="col-span-3"
|
||||
type="text"
|
||||
value={composer}
|
||||
onChange={(e) => setComposer(e.target.value)}
|
||||
/>
|
||||
<Label htmlFor={lyricistId} className="text-right">Słowa</Label>
|
||||
<Input
|
||||
id={lyricistId}
|
||||
className="col-span-3"
|
||||
type="text"
|
||||
value={lyricist}
|
||||
onChange={(e) => setLyricist(e.target.value)}
|
||||
/>
|
||||
<Label htmlFor={arrangerId} className="text-right">Opracowanie</Label>
|
||||
<Input
|
||||
id={arrangerId}
|
||||
className="col-span-3"
|
||||
type="text"
|
||||
value={arranger}
|
||||
onChange={(e) => setArranger(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit" disabled={isLoading}>
|
||||
{isLoading && <Loader2 className="animate-spin" />}
|
||||
Dodaj
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user