105 lines
4.5 KiB
JavaScript
105 lines
4.5 KiB
JavaScript
import { FilePlus2, MoreHorizontal } from "lucide-react";
|
||
import { useQuery } from "@tanstack/react-query";
|
||
import { Link } from "react-router-dom";
|
||
import { useSession } from "../../app/store/session";
|
||
import { contentApi } from "../../shared/api/endpoints";
|
||
import { normalizeContentList } from "../../shared/api/normalize";
|
||
import { queryKeys } from "../../shared/api/queryKeys";
|
||
import { Button } from "../../shared/ui/Button";
|
||
import { EmptyState, ErrorState, Skeleton } from "../../shared/ui/States";
|
||
import { StatusBadge } from "../../shared/ui/StatusBadge";
|
||
|
||
const statusFilters = [
|
||
["all", "Все"],
|
||
["draft", "Черновики"],
|
||
["moderation", "На модерации"],
|
||
["published", "Опубликованные"],
|
||
["returned", "Возвращенные"],
|
||
];
|
||
|
||
function formatDate(value) {
|
||
if (!value) return "Нет даты";
|
||
const date = new Date(value);
|
||
if (Number.isNaN(date.getTime())) return value;
|
||
return date.toLocaleString("ru-RU", { day: "numeric", month: "long", hour: "2-digit", minute: "2-digit" });
|
||
}
|
||
|
||
export function EditorPage() {
|
||
const user = useSession((state) => state.user);
|
||
const queryParams = { mine: true, authorId: user?.id };
|
||
const materialsQuery = useQuery({
|
||
queryKey: queryKeys.content(queryParams),
|
||
queryFn: () => contentApi.list(queryParams),
|
||
enabled: Boolean(user?.id),
|
||
});
|
||
const materials = normalizeContentList(materialsQuery.data);
|
||
|
||
return (
|
||
<div>
|
||
<div className="flex flex-wrap items-end justify-between gap-5">
|
||
<div>
|
||
<p className="text-xs font-bold uppercase tracking-widest text-accent">Редакция</p>
|
||
<h1 className="mt-2 font-serif text-4xl">Мои материалы</h1>
|
||
<p className="mt-3 text-muted">Черновики, публикации и замечания модераторов загружаются из backend.</p>
|
||
</div>
|
||
<Link to="/cabinet/editor/new">
|
||
<Button icon={<FilePlus2 className="size-4" />}>Новый материал</Button>
|
||
</Link>
|
||
</div>
|
||
|
||
<div className="mt-7 flex gap-2 overflow-x-auto pb-2">
|
||
{statusFilters.map(([status, label], index) => (
|
||
<Button key={status} size="sm" variant={index === 0 ? "primary" : "secondary"}>
|
||
{label}
|
||
</Button>
|
||
))}
|
||
</div>
|
||
|
||
<div className="mt-5 overflow-hidden rounded-lg border border-line bg-surface">
|
||
<div className="hidden grid-cols-[minmax(0,4fr)_minmax(8rem,1fr)_minmax(8rem,1fr)_auto] border-b border-line bg-paper px-5 py-3 text-xs font-bold uppercase tracking-wider text-muted md:grid">
|
||
<span>Материал</span>
|
||
<span>Статус</span>
|
||
<span>Обновлен</span>
|
||
<span />
|
||
</div>
|
||
{materialsQuery.isLoading && (
|
||
<div className="space-y-3 p-5">
|
||
<Skeleton className="h-24" />
|
||
<Skeleton className="h-24" />
|
||
<Skeleton className="h-24" />
|
||
</div>
|
||
)}
|
||
{materialsQuery.isError && (
|
||
<div className="p-5">
|
||
<ErrorState retry={() => materialsQuery.refetch()} />
|
||
</div>
|
||
)}
|
||
{materialsQuery.isSuccess && materials.length === 0 && (
|
||
<div className="p-5">
|
||
<EmptyState title="Материалов пока нет" text="Созданные через backend записи появятся в этом списке." />
|
||
</div>
|
||
)}
|
||
{materialsQuery.isSuccess && materials.map((material) => (
|
||
<article
|
||
key={material.id}
|
||
className="grid gap-4 border-b border-line p-5 last:border-0 md:grid-cols-[minmax(0,4fr)_minmax(8rem,1fr)_minmax(8rem,1fr)_auto] md:items-center"
|
||
>
|
||
<div>
|
||
<p className="text-xs text-muted">{material.type ?? "Материал"} · {material.category}</p>
|
||
<h2 className="mt-1 font-serif text-xl">{material.title}</h2>
|
||
{(material.moderatorComment || material.reviewComment) && (
|
||
<p className="mt-2 text-sm text-danger">{material.moderatorComment ?? material.reviewComment}</p>
|
||
)}
|
||
</div>
|
||
<StatusBadge status={material.status ?? "draft"} />
|
||
<span className="text-sm text-muted">{formatDate(material.updatedAt ?? material.publishedAt)}</span>
|
||
<button className="grid size-9 place-items-center rounded-md hover:bg-paper" aria-label="Действия">
|
||
<MoreHorizontal className="size-5" />
|
||
</button>
|
||
</article>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|