User list view
This commit is contained in:
183
packages/frontend/src/routes/Admin.tsx
Normal file
183
packages/frontend/src/routes/Admin.tsx
Normal file
@@ -0,0 +1,183 @@
|
||||
import { userCache } from "@/cache";
|
||||
import { client } from "@/client";
|
||||
import { Avatar, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { useBreakpoint } from "@/hooks/useBreakpoint";
|
||||
import { useCache } from "@/hooks/useCache";
|
||||
import { useLoading } from "@/hooks/useLoading";
|
||||
import { DEBOUNCE } from "@/snippets";
|
||||
import { UserId } from "common";
|
||||
import { Role } from "common/the_api";
|
||||
import { Array, Effect, HashSet, Match, Option, pipe } from "effect";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
export function Admin() {
|
||||
|
||||
const [displayName, setDisplayName] = useState("");
|
||||
const [role, setRole] = useState<Role | "">("");
|
||||
|
||||
const debounce = useRef(Effect.void);
|
||||
const breakpoint = useBreakpoint();
|
||||
const columns = breakpoint ? 4 : 1;
|
||||
|
||||
const { isLoading, error, data: userIds } = useLoading(Effect.gen(function* () {
|
||||
yield* debounce.current;
|
||||
const data = yield* client.queryUsers({
|
||||
displayName: displayName !== "" ? Option.some(displayName) : Option.none(),
|
||||
role: role !== "" ? Option.some(role) : Option.none(),
|
||||
offset: 0,
|
||||
limit: 100,
|
||||
});
|
||||
return data;
|
||||
}), [displayName, role]);
|
||||
|
||||
return (
|
||||
<div className="p-4 overflow-y-auto flex flex-col gap-4">
|
||||
<div className="flex flex-row flex-wrap items-baseline gap-4">
|
||||
<Input
|
||||
className="w-full md:w-[32ch]"
|
||||
type="text"
|
||||
placeholder="Nazwa wyświetlana"
|
||||
value={displayName}
|
||||
onChange={(e) => {
|
||||
setDisplayName(e.target.value);
|
||||
debounce.current = DEBOUNCE;
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
value={role}
|
||||
onValueChange={(role) => setRole(role === "None" ? "" : role as Role)}
|
||||
>
|
||||
<SelectTrigger className="w-full md:w-[24ch]">
|
||||
<SelectValue placeholder="Rola" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectLabel>Role</SelectLabel>
|
||||
<SelectItem value="None"><em>Dowolna</em></SelectItem>
|
||||
<SelectItem value={Role.Viewer}>Przeglądanie</SelectItem>
|
||||
<SelectItem value={Role.Editor}>Edytor</SelectItem>
|
||||
<SelectItem value={Role.Admin}>Administrator</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<Table>
|
||||
{breakpoint && (
|
||||
<TableHeader className="bg-white sticky top-0">
|
||||
<TableRow>
|
||||
<TableHead className="w-10" />
|
||||
<TableHead>Nazwa wyświetlana</TableHead>
|
||||
<TableHead>ID</TableHead>
|
||||
<TableHead>Role</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
)}
|
||||
<TableBody>
|
||||
{isLoading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns} >
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Loader2 className="animate-spin" />
|
||||
Ładowanie…
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : error !== null ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns} >
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
Wystąpił błąd: {Match.value(error).pipe(
|
||||
Match.tag("FetchError", () => "Nie można połączyć się z serwerem"),
|
||||
Match.tag("Unauthenticated", () => "Zaloguj się, aby kontynuować"),
|
||||
Match.exhaustive,
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
userIds.map((userId) => <UserRow key={userId} userId={userId} />)
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
namespace UserRow {
|
||||
export interface Props {
|
||||
readonly userId: UserId;
|
||||
}
|
||||
}
|
||||
|
||||
function UserRow(props: UserRow.Props) {
|
||||
|
||||
const { isLoading, error, data: user } = useCache(userCache, props.userId);
|
||||
|
||||
const breakpoint = useBreakpoint();
|
||||
const columns = breakpoint ? 4 : 1;
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns}>Ładowanie…</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
if (error !== null) {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell colSpan={columns}>
|
||||
Wystąpił błąd: {Match.value(error).pipe(
|
||||
Match.tag("FetchError", () => "Nie można połączyć się z serwerem"),
|
||||
Match.tag("NotFound", () => "Użytkownik nie istnieje"),
|
||||
Match.tag("Unauthenticated", () => "Zaloguj się, aby kontynuować"),
|
||||
Match.exhaustive,
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
const rolesText = pipe(
|
||||
user.roles,
|
||||
HashSet.values,
|
||||
Array.join(", "),
|
||||
);
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
{breakpoint ? (<>
|
||||
<TableCell>
|
||||
<Avatar className="shadow">
|
||||
<AvatarImage src={Option.getOrUndefined(user.avatarUrl)} />
|
||||
</Avatar>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{Option.getOrUndefined(user.displayName)}
|
||||
</TableCell>
|
||||
<TableCell className="font-mono">
|
||||
{user.userId}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{rolesText}
|
||||
</TableCell>
|
||||
</>) : (
|
||||
<TableCell className="flex flex-col">
|
||||
<div className="flex flex-row items-center gap-2 mb-2">
|
||||
<Avatar className="shadow">
|
||||
<AvatarImage src={Option.getOrUndefined(user.avatarUrl)} />
|
||||
</Avatar>
|
||||
<div>{Option.getOrUndefined(user.displayName)}</div>
|
||||
</div>
|
||||
<div className="text-xs font-mono">{user.userId}</div>
|
||||
<div className="text-xs">{rolesText}</div>
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user