153 lines
5.3 KiB
TypeScript
153 lines
5.3 KiB
TypeScript
import { Button } from "@repo/shadcn-ui/components/button";
|
|
import { Card, CardContent } from "@repo/shadcn-ui/components/card";
|
|
import { FormDialog } from "@repo/shadcn-ui/components/form-dialog";
|
|
import { Input } from "@repo/shadcn-ui/components/input";
|
|
import { Label } from "@repo/shadcn-ui/components/label";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import type { ReactNode } from "react";
|
|
import { useId, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
import type { ResourceDTO } from "../common";
|
|
|
|
import { catalogApi, catalogKeys } from "./catalog-api";
|
|
import { inventoryApi, inventoryKeys } from "./inventory-api";
|
|
import { ResourceDetail } from "./resource-detail";
|
|
import { InventoryFeedback, ResourceForm } from "./resource-form";
|
|
|
|
export function ResourcesPage({
|
|
companyId,
|
|
csrfToken,
|
|
relatedContent,
|
|
}: {
|
|
companyId: string;
|
|
csrfToken: string;
|
|
relatedContent?: (resource: ResourceDTO) => ReactNode;
|
|
}) {
|
|
const { t } = useTranslation("resources");
|
|
const searchId = useId();
|
|
const [search, setSearch] = useState("");
|
|
const [editor, setEditor] = useState<string>();
|
|
const [viewId, setViewId] = useState<string>();
|
|
const query = useQuery({
|
|
queryKey: inventoryKeys.company(companyId),
|
|
queryFn: () => inventoryApi.get(companyId),
|
|
});
|
|
const catalog = useQuery({
|
|
queryKey: catalogKeys.scope(companyId),
|
|
queryFn: () => catalogApi.get(companyId),
|
|
});
|
|
const edited = query.data?.resources.find((item) => item.id === editor);
|
|
const viewed = query.data?.resources.find((item) => item.id === viewId);
|
|
const visible =
|
|
query.data?.resources.filter((item) =>
|
|
`${item.values.name} ${item.specification.templateName}`
|
|
.toLocaleLowerCase()
|
|
.includes(search.toLocaleLowerCase())
|
|
) ?? [];
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="flex flex-wrap items-start justify-between gap-4">
|
|
<h2 className="text-2xl font-semibold">{t("inventory")}</h2>
|
|
<Button disabled={!(query.data && catalog.data)} onClick={() => setEditor("new")}>
|
|
{t("newResource")}
|
|
</Button>
|
|
</div>
|
|
<p className="text-sm text-muted-foreground">{t("resourceScope")}</p>
|
|
{(query.isPending || catalog.isPending) && <p role="status">{t("loading")}</p>}
|
|
<InventoryFeedback error={query.error ?? catalog.error} />
|
|
{(query.isError || catalog.isError) && (
|
|
<Button
|
|
onClick={() => {
|
|
Promise.all([query.refetch(), catalog.refetch()]).catch(() => undefined);
|
|
}}
|
|
variant="outline"
|
|
>
|
|
{t("retry")}
|
|
</Button>
|
|
)}
|
|
<Card>
|
|
<CardContent className="space-y-4 pt-6">
|
|
<Label htmlFor={searchId}>{t("search")}</Label>
|
|
<Input id={searchId} onChange={(event) => setSearch(event.target.value)} value={search} />
|
|
{query.data &&
|
|
(visible.length ? (
|
|
<div className="divide-y">
|
|
{visible.map((item) => (
|
|
<div
|
|
className="flex flex-wrap items-center justify-between gap-4 py-4"
|
|
key={item.id}
|
|
>
|
|
<div>
|
|
<h3 className="font-medium">{item.values.name}</h3>
|
|
<p className="text-sm text-muted-foreground">
|
|
{item.specification.templateName} ·{" "}
|
|
{t(item.values.active ? "active" : "inactive")}
|
|
</p>
|
|
</div>
|
|
<div className="flex gap-2">
|
|
<Button onClick={() => setViewId(item.id)} size="sm" variant="outline">
|
|
{t("viewResource")}
|
|
</Button>
|
|
<Button
|
|
disabled={!catalog.data}
|
|
onClick={() => setEditor(item.id)}
|
|
size="sm"
|
|
variant="outline"
|
|
>
|
|
{t("edit")}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<p className="text-muted-foreground">{t("empty")}</p>
|
|
))}
|
|
</CardContent>
|
|
</Card>
|
|
{editor && catalog.data && (
|
|
<FormDialog
|
|
cancelLabel={t("cancel")}
|
|
formActions
|
|
onOpenChange={(open) => {
|
|
if (!open) {
|
|
setEditor(undefined);
|
|
}
|
|
}}
|
|
open
|
|
title={t(edited ? "editResource" : "newResource")}
|
|
wide
|
|
>
|
|
<ResourceForm
|
|
companyId={companyId}
|
|
csrfToken={csrfToken}
|
|
items={catalog.data.items}
|
|
onSaved={(id) => {
|
|
setEditor(undefined);
|
|
setViewId(id);
|
|
}}
|
|
resource={edited}
|
|
/>
|
|
</FormDialog>
|
|
)}
|
|
{viewed && (
|
|
<FormDialog
|
|
cancelLabel={t("close")}
|
|
onOpenChange={(open) => {
|
|
if (!open) {
|
|
setViewId(undefined);
|
|
}
|
|
}}
|
|
open
|
|
title={viewed.values.name}
|
|
wide
|
|
>
|
|
<ResourceDetail references={query.data?.references ?? []} resource={viewed} />
|
|
{relatedContent?.(viewed)}
|
|
</FormDialog>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|