// web/screens/archive.jsx — экран «Архив» серий.
// Реальные серии с archivedAt != null, фильтры по типу/году/поиску,
// группировка по месяцу, модал подтверждения удаления.
const MONTHS_RU = ["январь", "февраль", "март", "апрель", "май", "июнь",
"июль", "август", "сентябрь", "октябрь", "ноябрь", "декабрь"];
function ArchiveScreen({ setRoute }) {
const [archiveState, reload] = useAsync(() => api.feed.shoots("true"), []);
const [confirmDelete, setConfirmDelete] = React.useState(null);
const [query, setQuery] = React.useState("");
const [kindFilter, setKindFilter] = React.useState("all");
const [sortBy, setSortBy] = React.useState("date-desc");
const [_, toast] = useToasts();
const all = archiveState.data || [];
const totalPhotos = all.reduce((n, s) => n + (s.Photos || 0), 0);
const byKind = (s) => {
if (kindFilter === "all") return true;
if (kindFilter === "editorial") return s.IsEditorial;
return s.ContentType === kindFilter;
};
const byQuery = (s) => {
if (!query.trim()) return true;
const q = query.trim().toLowerCase();
return ((s.Title || "") + " " + (s.ID || "")).toLowerCase().includes(q);
};
const sortFn = (a, b) => {
if (sortBy === "date-desc") return a.ArchivedAt < b.ArchivedAt ? 1 : -1;
if (sortBy === "date-asc") return a.ArchivedAt > b.ArchivedAt ? 1 : -1;
if (sortBy === "count") return (b.Photos || 0) - (a.Photos || 0);
return 0;
};
const filtered = all.filter(byKind).filter(byQuery).slice().sort(sortFn);
// Группировка по месяцу архивации.
const byMonth = {};
for (const s of filtered) {
const d = new Date(s.ArchivedAt || s.CreatedAt);
const key = d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0");
if (!byMonth[key]) byMonth[key] = { label: MONTHS_RU[d.getMonth()] + " " + d.getFullYear(), items: [] };
byMonth[key].items.push(s);
}
const monthsInOrder = Object.keys(byMonth).sort().reverse();
const handleDelete = async (s) => {
try {
await api.feed.deleteShoot(s.ID);
toast(`«${s.Title || s.ID}» удалено`, "info");
setConfirmDelete(null);
reload();
} catch (e) {
toast("Не удалось удалить: " + e.message, "error");
}
};
return (
Архив
{all.length} серий · {totalPhotos} файлов всего
setRoute("shoots")}>← Мои съёмки
{/* Filter strip */}
setQuery(e.target.value)}
placeholder="Поиск по серии…"
style={{ flex: 1, maxWidth: 320, padding: "5px 10px", fontSize: 13 }}/>
{[
{ id: "all", label: "Все" },
{ id: "photo", label: "Фото", icon: "image" },
{ id: "video", label: "Видео", icon: "film" },
{ id: "vector", label: "Vector", icon: "layers" },
{ id: "editorial", label: "Editorial", icon: "edit" },
].map((c) => (
setKindFilter(c.id)}
className={"chip" + (kindFilter === c.id ? " active" : "")}>
{c.icon && }
{c.label}
))}
setSortBy(e.target.value)}
className="input"
style={{ padding: "5px 24px 5px 10px", fontSize: 12.5, cursor: "pointer" }}>
Новые сверху
Старые сверху
По количеству фото
{archiveState.loading && (
загружаем архив…
)}
{!archiveState.loading && all.length === 0 && (
Архив пуст
Готовые серии можно отправить в архив со страницы «Мои съёмки».
)}
{!archiveState.loading && all.length > 0 && filtered.length === 0 && (
Ничего не нашли
Сбросьте фильтры или поищите по другому слову.
)}
{monthsInOrder.map((mk) => (
{byMonth[mk].label}
{byMonth[mk].items.length} серий · {byMonth[mk].items.reduce((n, s) => n + (s.Photos || 0), 0)} файлов
{byMonth[mk].items.map((s) => (
setRoute("series", s.ID)}
style={{ cursor: "pointer" }}>
{s.Cover
?
:
}
{s.ContentType !== "photo" && (
{s.ContentType === "video" ? "видео" : "vector"}
)}
{s.IsEditorial && (
editorial
)}
{s.Title || `Серия ${s.ID}`}
{new Date(s.ArchivedAt || s.CreatedAt).toLocaleDateString("ru-RU", {
day: "2-digit", month: "short", year: "numeric",
})}
·
{s.Photos || 0} файлов
{
e.stopPropagation();
try {
await api.shoot.Unarchive(s.ID);
toast("Серия «" + (s.Title || s.ID) + "» возвращена из архива → Готово", "info");
reload();
} catch (err) {
toast("Не удалось вернуть из архива: " + err.message, "error");
}
}}>
Вернуть
{ e.stopPropagation(); setConfirmDelete(s); }}>
))}
))}
{all.length > 0 && (
Что хранится в архиве: заголовки, описания,
ключевые слова, анализ, CSV — навсегда.{" "}
Что чистится: копии 512px удаляются через 30 дней
после ухода серии в архив. Для переобработки сервис берёт исходник с Я.Диска.
)}
{confirmDelete && (
setConfirmDelete(null)}>
e.stopPropagation()}>
Удалить серию насовсем?
«{confirmDelete.Title || `Серия ${confirmDelete.ID}`}»
Удалится навсегда : заголовки, описания, ключи, анализ, CSV — всё, что
Pika сохранила про эту серию. Восстановить будет нельзя — только переобработать
заново из _Pikasrv/ , если исходники ещё там.
setConfirmDelete(null)}>
Оставить как есть
handleDelete(confirmDelete)}>
Удалить насовсем
)}
);
}
window.ArchiveScreen = ArchiveScreen;