Initial commit
This commit is contained in:
184
resources/js/pages/AdminUsersPage.tsx
Normal file
184
resources/js/pages/AdminUsersPage.tsx
Normal file
@@ -0,0 +1,184 @@
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import axios from "axios";
|
||||
import { Button } from "@heroui/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import AdminUsersTable from "@/components/admin/users/AdminUsersTable";
|
||||
import AdminUserForm from "@/components/admin/users/AdminUserForm";
|
||||
|
||||
type UserItem = {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
is_admin: boolean;
|
||||
is_active: boolean;
|
||||
};
|
||||
|
||||
type FormMode = "none" | "create" | "edit";
|
||||
|
||||
type PaginatedResponse<T> = {
|
||||
data: T[];
|
||||
};
|
||||
|
||||
export default function AdminUsersPage() {
|
||||
const { t } = useTranslation("common");
|
||||
const [items, setItems] = useState<UserItem[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [refreshKey, setRefreshKey] = useState(0);
|
||||
const [query, setQuery] = useState("");
|
||||
|
||||
const [formMode, setFormMode] = useState<FormMode>("none");
|
||||
const [editing, setEditing] = useState<UserItem | 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<UserItem>>("/api/users", {
|
||||
headers: { Accept: "application/json" },
|
||||
params: { per_page: 200, query },
|
||||
withCredentials: true,
|
||||
});
|
||||
if (!active) return;
|
||||
setItems(res.data.data ?? []);
|
||||
} catch (e: any) {
|
||||
if (!active) return;
|
||||
setError(
|
||||
e?.response?.data?.message ??
|
||||
(t("admin_users_load_failed") ?? "Nepodařilo se načíst uživatele.")
|
||||
);
|
||||
} finally {
|
||||
if (active) setLoading(false);
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [refreshKey, query, t]);
|
||||
|
||||
const visibleItems = useMemo(() => items, [items]);
|
||||
|
||||
const resetForm = () => {
|
||||
setEditing(null);
|
||||
setFormError(null);
|
||||
};
|
||||
|
||||
const openCreate = () => {
|
||||
resetForm();
|
||||
setFormMode("create");
|
||||
};
|
||||
|
||||
const openEdit = (item: UserItem) => {
|
||||
resetForm();
|
||||
setEditing(item);
|
||||
setFormMode("edit");
|
||||
};
|
||||
|
||||
const deactivateUser = async (item: UserItem) => {
|
||||
try {
|
||||
await axios.delete(`/api/users/${item.id}`, {
|
||||
headers: { Accept: "application/json" },
|
||||
withCredentials: true,
|
||||
withXSRFToken: true,
|
||||
});
|
||||
setRefreshKey((k) => k + 1);
|
||||
} catch (e: any) {
|
||||
setError(
|
||||
e?.response?.data?.message ??
|
||||
(t("admin_users_deactivate_failed") ?? "Nepodařilo se deaktivovat uživatele.")
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (payload: {
|
||||
name: string;
|
||||
email: string;
|
||||
password?: string;
|
||||
is_admin: boolean;
|
||||
is_active: boolean;
|
||||
}) => {
|
||||
setFormError(null);
|
||||
|
||||
try {
|
||||
setSubmitting(true);
|
||||
await axios.get("/sanctum/csrf-cookie", { withCredentials: true });
|
||||
|
||||
if (formMode === "edit" && editing) {
|
||||
await axios.put(`/api/users/${editing.id}`, payload, {
|
||||
headers: { Accept: "application/json" },
|
||||
withCredentials: true,
|
||||
withXSRFToken: true,
|
||||
});
|
||||
} else {
|
||||
await axios.post("/api/users", 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_users_save_failed") ?? "Chyba při ukládání uživatele.")
|
||||
);
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex flex-wrap gap-3 items-center">
|
||||
<h1 className="text-xl font-semibold">{t("admin_users_title") ?? "Uživatelé"}</h1>
|
||||
<input
|
||||
type="text"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
placeholder={t("admin_users_search") ?? "Hledat jméno nebo email"}
|
||||
className="border rounded px-2 py-1 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{error && <div className="text-red-600 text-sm">{error}</div>}
|
||||
|
||||
<AdminUsersTable
|
||||
items={visibleItems}
|
||||
loading={loading}
|
||||
onEdit={openEdit}
|
||||
onDeactivate={deactivateUser}
|
||||
/>
|
||||
<Button onPress={openCreate}>{t("admin_users_create") ?? "Nový uživatel"}</Button>
|
||||
{formMode !== "none" && (
|
||||
<Button
|
||||
variant="light"
|
||||
onPress={() => {
|
||||
setFormMode("none");
|
||||
resetForm();
|
||||
}}
|
||||
>
|
||||
{t("admin_form_close") ?? "Zavřít formulář"}
|
||||
</Button>
|
||||
)}
|
||||
{formMode !== "none" && (
|
||||
<AdminUserForm
|
||||
mode={formMode}
|
||||
editing={editing}
|
||||
submitting={submitting}
|
||||
serverError={formError}
|
||||
onSubmit={handleSubmit}
|
||||
onCancel={() => {
|
||||
setFormMode("none");
|
||||
resetForm();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user