// «Движения по сотруднику» — карточка: нагрузка, эффективность, отсутствия, штрафы, // зарплата + история (таймлайн). Задел под рекомендации ИИ-чата. const TL_META = { payroll: { label: "Зарплата", color: "var(--ok)", icon: I.Coins }, fine: { label: "Взыскание", color: "var(--err)", icon: I.Shield }, efficiency: { label: "Эффективность", color: "var(--accent-strong)", icon: I.Gauge }, absence: { label: "Отсутствие", color: "var(--warn)", icon: I.Calendar }, task: { label: "Задача", color: "var(--ink-400)", icon: I.Check }, }; function profMoney(n) { return Math.round(n || 0).toLocaleString("ru-RU") + " ₽"; } function PersonProfileModal({ person, onClose }) { const [data, setData] = React.useState(null); const [tab, setTab] = React.useState("all"); const [editPay, setEditPay] = React.useState(false); const canManage = window.ME && (window.ME.role === "admin" || window.ME.role === "controller"); const load = React.useCallback(() => { if (!window.API || !window.API.personProfile) return; window.API.personProfile(person).then(setData).catch(() => setData({ timeline: [] })); }, [person]); React.useEffect(() => { load(); }, [load]); const tl = (data && data.timeline) || []; const shown = tab === "all" ? tl : tl.filter(e => e.type === tab); const load_ = (data && data.load) || {}; const avail = (data && data.availability) || {}; const pay = (data && data.payroll) || {}; const cur = pay.current || {}; return (
e.stopPropagation()}>

{person}

{(data && data.person && data.person.dept) || "—"}{avail.absentNow ? ` · сейчас: ${ABSENCE_LABEL[avail.kind] || "отсутствует"} до ${avail.until}` : ""}

{!data ?
Загрузка…
: ( <> {/* load + efficiency summary */}
{load_.overLimit &&
Перегружен в пиковый день ({load_.peakDay}: {load_.peakHours} ч при норме {load_.workdayHours} ч) — стоит перенести или передать задачу.
} {/* salary + history */} setEditPay(v => !v)}> {editPay ? "Отмена" : "Изменить"} : null} style={{ boxShadow: "none", border: "1px solid var(--ink-100)" }}> {editPay ? { setEditPay(false); load(); }} /> : (
Оклад: {profMoney(cur.base || 0)}
Надбавки: {profMoney(cur.accruals || 0)}
Изменений: {(pay.history || []).length}
)}
{/* timeline */}
{["all", "payroll", "fine", "efficiency", "absence", "task"].map(k => ( ))}
{shown.length === 0 &&
Нет записей.
} {shown.map((e, i) => { const m = TL_META[e.type] || TL_META.task; const Ic = m.icon; return (
{e.label}
{e.detail &&
{e.detail}
}
{e.ts}
); })}
)}
); } function MiniStat({ label, value, color }) { return (
{label}
{value}
); } function PayrollEdit({ cur, person, onSaved }) { const [base, setBase] = React.useState(cur.base || 0); const [accruals, setAccruals] = React.useState(cur.accruals || 0); const [reason, setReason] = React.useState(""); const [busy, setBusy] = React.useState(false); const [err, setErr] = React.useState(""); const save = async () => { setBusy(true); setErr(""); try { await window.API.setPayroll(person, { base: Number(base), accruals: Number(accruals), reason: reason.trim() }); onSaved(); } catch (e) { setErr(String((e && e.message) || e)); setBusy(false); } }; return (
setBase(e.target.value)} />
setAccruals(e.target.value)} />
setReason(e.target.value)} />
{err &&
{err}
}
); } window.PersonProfileModal = PersonProfileModal;