// «Движения по сотруднику» — карточка: нагрузка, эффективность, отсутствия, штрафы,
// зарплата + история (таймлайн). Задел под рекомендации ИИ-чата.
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 (
);
}
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 (
setReason(e.target.value)} />
{err &&
{err}
}
);
}
window.PersonProfileModal = PersonProfileModal;