(() => { const { Card, Button, IconButton, Badge, Avatar, Modal, Field, Input, Select, DataTable, EmptyState, Icon } = window.TaskFlowDesignSystem_dd53c2; /* Satu-satunya tempat kelola akun (halaman Setting lama dihapus, isinya pindah ke sini). Password TIDAK pernah disimpan di klien: form hanya mengirim perintah ke server (password_hash + flag must_change). */ const DEFAULT_POSITIONS = ["Manager", "Staff"]; const GRANTABLE_PAGES = [ { id: "dashboard", label: "Dashboard", icon: "layout-dashboard" }, { id: "tasks", label: "Task List", icon: "clipboard-list" }, { id: "calendar", label: "Calendar", icon: "calendar-days" }, { id: "document", label: "Document", icon: "folder-open" }, { id: "report", label: "Report (lihat saja)", icon: "chart-pie" }, { id: "profile", label: "Profile", icon: "user" }, { id: "absen", label: "Absen Kunjungan", icon: "map-pin-check" }, ]; const ADMIN_ONLY_PAGES = ["Task Verification", "Add New Task", "Log Activity", "Manajemen User"]; const DEFAULT_ROLES = [ { id: "user", name: "User", pages: ["dashboard", "tasks", "calendar", "document", "profile"], locked: true }, { id: "marketing", name: "Marketing", pages: ["dashboard", "tasks", "calendar", "document", "profile", "absen"], locked: true }, ]; const DIREKTUR = DB.direktur; function UserManagement({ users, tasks = [], onSave, onToggleActive, onResetPassword, onDelete }) { const narrow = useBreakpoint() !== "desktop"; const [q, setQ] = React.useState(""); const [positions, setPositions] = React.useState(DEFAULT_POSITIONS); const [posEdit, setPosEdit] = React.useState(null); const [posErr, setPosErr] = React.useState(""); const [roles, setRoles] = React.useState(DEFAULT_ROLES); const [roleEdit, setRoleEdit] = React.useState(null); const [roleErr, setRoleErr] = React.useState(""); const [form, setForm] = React.useState(null); // {mode,id,values} const [reset, setReset] = React.useState(null); const [wipe, setWipe] = React.useState(false); const [confirm, setConfirm] = React.useState(null); const [err, setErr] = React.useState(""); const load = React.useMemo(() => { const m = {}; tasks.forEach((t) => { m[t.user] = m[t.user] || { total: 0, open: 0, late: 0 }; m[t.user].total++; if (t.status !== "done") m[t.user].open++; if (t.status !== "done" && isOverdue(t.target)) m[t.user].late++; }); return m; }, [tasks]); const rows = React.useMemo(() => { const s = q.trim().toLowerCase(); return [DIREKTUR, ...users].filter((u) => !s || u.name.toLowerCase().includes(s) || u.email.toLowerCase().includes(s) || String(u.role).toLowerCase().includes(s) || String(u.position || "").toLowerCase().includes(s)); }, [users, q]); const openAdd = () => { setErr(""); setForm({ mode: "add", id: null, values: { name: "", email: "", role: positions[0] || "Staff", position: positions[0] || "Staff", access: "user", password: "" } }); }; const openEdit = (u) => { setErr(""); setForm({ mode: "edit", id: u.id, values: { name: u.name, email: u.email, role: u.role, position: u.position || positions[0] || "Staff", access: u.access || "user", password: "" } }); }; const submit = () => { const v = form.values; if (!v.name.trim()) return setErr("Nama wajib diisi."); if (!/^[^\s@]+@[^\s@]+\.[a-z]{2,}$/i.test(v.email.trim())) return setErr("Format email tidak valid."); if ([DIREKTUR, ...users].some((u) => u.id !== form.id && u.email.toLowerCase() === v.email.trim().toLowerCase())) return setErr("Email sudah dipakai akun lain."); if (form.mode === "add" && !v.password) return setErr("Password awal wajib diisi (diberikan ke user)."); if (form.mode === "add" && !(v.password.length >= 8 && /[A-Za-z]/.test(v.password) && /[0-9]/.test(v.password))) return setErr("Password awal minimal 8 karakter dan memuat huruf serta angka."); onSave(form.mode, form.id, { name: v.name.trim(), email: v.email.trim().toLowerCase(), role: v.role, position: v.position, access: v.access, pass: v.password || undefined }); setErr(""); setForm(null); }; const savePosition = () => { const v = (posEdit.value || "").trim(); if (!v) return setPosErr("Nama jabatan wajib diisi."); if (positions.some((p, i) => p.toLowerCase() === v.toLowerCase() && i !== posEdit.index)) return setPosErr("Jabatan sudah ada."); setPositions((p) => (posEdit.mode === "add" ? [...p, v] : p.map((x, i) => (i === posEdit.index ? v : x)))); setPosErr(""); setPosEdit(null); }; const removePosition = (i) => { const name = positions[i]; if (users.some((u) => u.position === name)) return setPosErr(`Jabatan "${name}" masih dipakai user.`); setPositions((p) => p.filter((_, x) => x !== i)); setPosErr(""); }; const togglePage = (id) => setRoleEdit((r) => ({ ...r, pages: r.pages.includes(id) ? r.pages.filter((p) => p !== id) : [...r.pages, id] })); const saveRole = () => { const v = (roleEdit.name || "").trim(); if (!v) return setRoleErr("Nama role wajib diisi."); if (v.toLowerCase() === "direktur" || v.toLowerCase() === "admin") return setRoleErr("Nama itu dipakai sistem — pakai nama lain."); if (roles.some((r) => r.name.toLowerCase() === v.toLowerCase() && r.id !== roleEdit.id)) return setRoleErr("Role sudah ada."); if (roleEdit.pages.length === 0) return setRoleErr("Pilih minimal satu halaman."); setRoles((r) => (roleEdit.mode === "add" ? [...r, { id: v.toLowerCase().replace(/\s+/g, "-"), name: v, pages: roleEdit.pages }] : r.map((x) => (x.id === roleEdit.id ? { ...x, name: v, pages: roleEdit.pages } : x)))); setRoleErr(""); setRoleEdit(null); }; return (
User tidak bisa mendaftar sendiri — semua akun dibuat di halaman ini.
Tentukan halaman yang boleh dibuka tiap role. Maksimal setara role User.
Jabatan ini yang muncul sebagai pilihan pada form user.