(() => { const { Card, Button, Field, Input, Badge, Avatar, StatusPill, Icon, Modal } = window.TaskFlowDesignSystem_dd53c2; // Password diperiksa terhadap akun yang login. Di produksi: password_hash() di server. const accountOf = (id) => (id === 0 ? DB.direktur : DB.users.find((u) => u.id === id) || null); function ProfileScreen({ user, tasks = [], onPhoto, signature, onSignature }) { const bp = useBreakpoint(); const md = monthRange(); const [form, setForm] = React.useState({ old: "", next: "", repeat: "" }); const [msg, setMsg] = React.useState(null); const [from, setFrom] = React.useState(md.from); const [to, setTo] = React.useState(md.to); const photo = avatarOf(user.id); const [photoOpen, setPhotoOpen] = React.useState(false); const [draftPhoto, setDraftPhoto] = React.useState(null); const [photoErr, setPhotoErr] = React.useState(""); const fileRef = React.useRef(null); /* JPG/PNG only; anything bigger than 500 KB is downscaled through a canvas until it fits, then cropped square so it fills the circle. */ const pickPhoto = (e) => { const f = e.target.files && e.target.files[0]; if (!f) return; if (!/image\/(jpeg|png)/.test(f.type)) { setPhotoErr("Hanya menerima file JPG atau PNG."); setDraftPhoto(null); return; } const reader = new FileReader(); reader.onload = () => { const img = new Image(); img.onload = () => { const side = Math.min(img.width, img.height); const size = Math.min(512, side); const c = document.createElement("canvas"); c.width = c.height = size; c.getContext("2d").drawImage(img, (img.width - side) / 2, (img.height - side) / 2, side, side, 0, 0, size, size); let q = 0.9, url = c.toDataURL("image/jpeg", q); while (url.length * 0.75 > 500 * 1024 && q > 0.3) { q -= 0.1; url = c.toDataURL("image/jpeg", q); } setPhotoErr(""); setDraftPhoto({ url, kb: Math.round((url.length * 0.75) / 1024), name: f.name, originalKb: Math.round(f.size / 1024) }); }; img.src = reader.result; }; reader.readAsDataURL(f); }; const all = tasks.filter((t) => t.user === user.id); const mine = all.filter((t) => inRange(t.assigned, from, to)); const submit = () => { if (!form.old.trim()) return setMsg({ tone: "err", text: "Password lama wajib diisi terlebih dahulu." }); const acc = accountOf(user.id); if (form.next.length < 8) return setMsg({ tone: "err", text: "Password baru minimal 8 karakter." }); if (form.next === form.old) return setMsg({ tone: "err", text: "Password baru harus berbeda dari password lama." }); if (form.next !== form.repeat) return setMsg({ tone: "err", text: "Ulangi password tidak sama." }); // Server aktif: password disimpan ter-hash di server, bukan di perangkat. if (window.TMSNet && TMSNet.online) { TMSNet.setPassword(form.old, form.next).then(() => { if (acc) acc.mustChange = false; setForm({ old: "", next: "", repeat: "" }); setMsg({ tone: "ok", text: "Password berhasil diperbarui \u2014 dipakai untuk login berikutnya." }); }).catch((e) => setMsg({ tone: "err", text: e.message || "Gagal menyimpan password." })); return; } if (!acc || form.old !== acc.pass) return setMsg({ tone: "err", text: "Password lama tidak sesuai." }); acc.pass = form.next; acc.mustChange = false; DB.users = DB.users; saveDb(); setForm({ old: "", next: "", repeat: "" }); setMsg({ tone: "ok", text: "Password berhasil diperbarui — dipakai untuk login berikutnya." }); }; return (
Masukkan password lama terlebih dahulu. Jika lupa, minta Direktur melakukan reset.