144 lines
4.2 KiB
TypeScript
144 lines
4.2 KiB
TypeScript
import React, { useEffect, useMemo, useState } from "react";
|
|
import axios from "axios";
|
|
import { Button } from "@heroui/react";
|
|
import { useLanguageStore } from "@/stores/languageStore";
|
|
import { useTranslation } from "react-i18next";
|
|
import AdminNewsTable from "@/components/admin/news/AdminNewsTable";
|
|
import AdminNewsForm from "@/components/admin/news/AdminNewsForm";
|
|
import { type NewsItem, type NewsPayload, type FormMode } from "@/components/admin/news/adminNewsTypes";
|
|
|
|
type PaginatedResponse<T> = { data: T[] };
|
|
|
|
export default function AdminNewsPage() {
|
|
const locale = useLanguageStore((s) => s.locale);
|
|
const { t } = useTranslation("common");
|
|
const [items, setItems] = useState<NewsItem[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [refreshKey, setRefreshKey] = useState(0);
|
|
|
|
const [formMode, setFormMode] = useState<FormMode>("none");
|
|
const [editing, setEditing] = useState<NewsItem | null>(null);
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const [formError, setFormError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
let active = true;
|
|
(async () => {
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
const res = await axios.get<PaginatedResponse<NewsItem> | NewsItem[]>("/api/news", {
|
|
headers: { Accept: "application/json" },
|
|
params: { per_page: 200, include_unpublished: 1 },
|
|
withCredentials: true,
|
|
});
|
|
if (!active) return;
|
|
const data = Array.isArray(res.data) ? res.data : res.data.data;
|
|
setItems(data);
|
|
} catch {
|
|
if (!active) return;
|
|
setError(t("admin_news_load_failed") ?? "Nepodařilo se načíst novinky.");
|
|
} finally {
|
|
if (active) setLoading(false);
|
|
}
|
|
})();
|
|
return () => {
|
|
active = false;
|
|
};
|
|
}, [locale, refreshKey, t]);
|
|
|
|
const visibleItems = useMemo(() => items, [items]);
|
|
|
|
const resetForm = () => {
|
|
setEditing(null);
|
|
setFormError(null);
|
|
};
|
|
|
|
const openCreate = () => {
|
|
resetForm();
|
|
setFormMode("create");
|
|
};
|
|
|
|
const openEdit = (item: NewsItem) => {
|
|
resetForm();
|
|
setEditing(item);
|
|
setFormMode("edit");
|
|
};
|
|
|
|
const handleSubmit = async (payload: NewsPayload) => {
|
|
setFormError(null);
|
|
|
|
try {
|
|
setSubmitting(true);
|
|
await axios.get("/sanctum/csrf-cookie", { withCredentials: true });
|
|
|
|
if (formMode === "edit" && editing) {
|
|
const key = editing.slug ?? editing.id;
|
|
await axios.put(`/api/news/${key}`, payload, {
|
|
headers: { Accept: "application/json" },
|
|
withCredentials: true,
|
|
withXSRFToken: true,
|
|
});
|
|
} else {
|
|
await axios.post("/api/news", payload, {
|
|
headers: { Accept: "application/json" },
|
|
withCredentials: true,
|
|
withXSRFToken: true,
|
|
});
|
|
}
|
|
setFormMode("none");
|
|
resetForm();
|
|
setRefreshKey((k) => k + 1);
|
|
} catch (e: any) {
|
|
setFormError(
|
|
e?.response?.data?.message ??
|
|
(t("admin_news_save_failed") ?? "Chyba při ukládání novinky.")
|
|
);
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div className="flex gap-3 items-center">
|
|
<h1 className="text-xl font-semibold">{t("admin_news_title") ?? "Novinky"}</h1>
|
|
</div>
|
|
|
|
<AdminNewsTable
|
|
items={visibleItems}
|
|
locale={locale}
|
|
loading={loading}
|
|
error={error}
|
|
onEdit={openEdit}
|
|
/>
|
|
<Button onPress={openCreate}>{t("admin_news_create") ?? "Nová novinka"}</Button>
|
|
{formMode !== "none" && (
|
|
<Button
|
|
variant="light"
|
|
onPress={() => {
|
|
setFormMode("none");
|
|
resetForm();
|
|
}}
|
|
>
|
|
{t("admin_form_close") ?? "Zavřít formulář"}
|
|
</Button>
|
|
)}
|
|
{formMode !== "none" && (
|
|
<AdminNewsForm
|
|
mode={formMode}
|
|
editing={editing}
|
|
submitting={submitting}
|
|
serverError={formError}
|
|
onSubmit={handleSubmit}
|
|
onCancel={() => {
|
|
setFormMode("none");
|
|
resetForm();
|
|
}}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|