Initial commit

This commit is contained in:
Zdeněk Burda
2026-01-09 21:26:40 +01:00
parent e83aec6dca
commit 41e3ce6f25
404 changed files with 61250 additions and 28 deletions

View File

@@ -0,0 +1,137 @@
import React, { useEffect, useState } from "react";
import { Button, Checkbox, Input } from "@heroui/react";
import { useTranslation } from "react-i18next";
type UserItem = {
id: number;
name: string;
email: string;
is_admin: boolean;
is_active: boolean;
};
type FormMode = "create" | "edit";
type Props = {
mode: FormMode;
editing: UserItem | null;
submitting: boolean;
serverError: string | null;
onSubmit: (payload: {
name: string;
email: string;
password?: string;
is_admin: boolean;
is_active: boolean;
}) => void;
onCancel: () => void;
};
export default function AdminUserForm({
mode,
editing,
submitting,
serverError,
onSubmit,
onCancel,
}: Props) {
const { t } = useTranslation("common");
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [isAdmin, setIsAdmin] = useState(false);
const [isActive, setIsActive] = useState(true);
useEffect(() => {
if (mode === "edit" && editing) {
setName(editing.name);
setEmail(editing.email);
setPassword("");
setIsAdmin(editing.is_admin);
setIsActive(editing.is_active);
} else {
setName("");
setEmail("");
setPassword("");
setIsAdmin(false);
setIsActive(true);
}
}, [mode, editing]);
const handleSubmit = () => {
const payload = {
name: name.trim(),
email: email.trim(),
is_admin: isAdmin,
is_active: isActive,
} as {
name: string;
email: string;
password?: string;
is_admin: boolean;
is_active: boolean;
};
if (password.trim()) {
payload.password = password.trim();
}
onSubmit(payload);
};
return (
<div className="rounded border border-divider p-4 space-y-3">
<div className="text-sm font-semibold">
{mode === "edit"
? t("admin_users_edit_title") ?? "Upravit uživatele"
: t("admin_users_create_title") ?? "Nový uživatel"}
</div>
<div className="grid gap-3">
<Input
label={t("admin_users_name") ?? "Jméno"}
value={name}
onChange={(e) => setName(e.target.value)}
size="sm"
/>
<Input
label={t("admin_users_email") ?? "Email"}
value={email}
onChange={(e) => setEmail(e.target.value)}
size="sm"
/>
<Input
label={t("admin_users_password") ?? "Heslo"}
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
size="sm"
placeholder={
mode === "edit"
? t("admin_users_password_hint") ?? "Nech prázdné pro beze změny"
: undefined
}
/>
<div className="flex gap-6">
<Checkbox isSelected={isAdmin} onValueChange={setIsAdmin}>
{t("admin_users_is_admin") ?? "Admin"}
</Checkbox>
<Checkbox isSelected={isActive} onValueChange={setIsActive}>
{t("admin_users_is_active") ?? "Aktivní"}
</Checkbox>
</div>
</div>
{serverError && <div className="text-red-600 text-sm">{serverError}</div>}
<div className="flex gap-2">
<Button color="primary" isDisabled={submitting} onPress={handleSubmit}>
{submitting ? t("admin_users_saving") ?? "Ukládám…" : t("admin_users_save") ?? "Uložit"}
</Button>
<Button variant="light" onPress={onCancel}>
{t("admin_form_close") ?? "Zavřít formulář"}
</Button>
</div>
</div>
);
}

View File

@@ -0,0 +1,67 @@
import React from "react";
import { Button, Table, TableBody, TableCell, TableColumn, TableHeader, TableRow } from "@heroui/react";
import { useTranslation } from "react-i18next";
type UserItem = {
id: number;
name: string;
email: string;
is_admin: boolean;
is_active: boolean;
};
type Props = {
items: UserItem[];
loading: boolean;
onEdit: (item: UserItem) => void;
onDeactivate: (item: UserItem) => void;
};
export default function AdminUsersTable({ items, loading, onEdit, onDeactivate }: Props) {
const { t } = useTranslation("common");
return (
<Table aria-label="Users table">
<TableHeader>
<TableColumn>{t("admin_users_name") ?? "Jméno"}</TableColumn>
<TableColumn>{t("admin_users_email") ?? "Email"}</TableColumn>
<TableColumn>{t("admin_users_is_admin") ?? "Admin"}</TableColumn>
<TableColumn>{t("admin_users_is_active") ?? "Aktivní"}</TableColumn>
<TableColumn>{t("admin_users_actions") ?? "Akce"}</TableColumn>
</TableHeader>
<TableBody
items={items}
emptyContent={
loading
? t("admin_users_loading") ?? "Načítám..."
: t("admin_users_empty") ?? "Žádní uživatelé."
}
>
{(item) => (
<TableRow key={item.id}>
<TableCell>{item.name}</TableCell>
<TableCell>{item.email}</TableCell>
<TableCell>{item.is_admin ? "ANO" : "NE"}</TableCell>
<TableCell>{item.is_active ? "ANO" : "NE"}</TableCell>
<TableCell>
<div className="flex gap-2">
<Button size="sm" variant="bordered" onPress={() => onEdit(item)}>
{t("admin_users_edit") ?? "Upravit"}
</Button>
<Button
size="sm"
color="danger"
variant="light"
onPress={() => onDeactivate(item)}
isDisabled={!item.is_active}
>
{t("admin_users_deactivate") ?? "Deaktivovat"}
</Button>
</div>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
);
}