feat: version 4.12.0 -- Conexiones externas: pacientes de SAGA, empleados de nomina y medicos
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import { useAuth } from '../../context/auth-context.jsx';
|
||||
import { useAsync, useAction } from '../../hooks/useAsync.js';
|
||||
import { listUsers, listRoles, createUser, updateUser, setUserActive, approveUser, rejectUser, resendUserEmail, listUserSessions, revokeUserSession, revokeAllUserSessions } from '../../services/users.js';
|
||||
import { listDepartments } from '../../services/departments.js';
|
||||
import { listEmployees } from '../../services/integrations.js';
|
||||
import { ListView } from '../../components/ListView.jsx';
|
||||
import { SearchInput } from '../../components/SearchInput.jsx';
|
||||
import { Button } from '../../components/Button.jsx';
|
||||
@@ -27,7 +28,10 @@ function flatten(nodes, acc = []) {
|
||||
}
|
||||
|
||||
const STATUS_COLOR = { pending_verification: 'muted', pending_approval: 'warning', active: 'success', invited: 'muted' };
|
||||
const EMPTY_FORM = { name: '', email: '', roleId: '', departmentId: '', position: '', password: '' };
|
||||
const EMPTY_FORM = { name: '', email: '', roleId: '', departmentId: '', position: '', password: '', employeeId: '' };
|
||||
// Nómina viene en MAYÚSCULAS: "RIERA CAJAMARCA" → "Riera Cajamarca".
|
||||
const titleCase = (s) => String(s || '').toLowerCase().replace(/(^|\s)\p{L}/gu, (m) => m.toUpperCase()).trim();
|
||||
const fold = (s) => String(s || '').normalize('NFD').replace(/[\u0300-\u036f]/g, '').trim().toUpperCase();
|
||||
|
||||
export default function UsersPanel() {
|
||||
const { t, pushToast, openDialog } = useUi();
|
||||
@@ -47,6 +51,7 @@ export default function UsersPanel() {
|
||||
const revokeAllSessions = useCallback(() => revokeAllUserSessions(sessionsUser.id), [sessionsUser]);
|
||||
|
||||
const [search, setSearch] = useState('');
|
||||
const [employees, setEmployees] = useState([]);
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [selected, setSelected] = useState(null);
|
||||
const [approving, setApproving] = useState(null);
|
||||
@@ -79,6 +84,14 @@ export default function UsersPanel() {
|
||||
function startNew() {
|
||||
setSelected(null);
|
||||
setEditing(true);
|
||||
// Empleados de nómina que aún no tienen usuario (si hay fuente de nómina configurada).
|
||||
listEmployees(true).then(setEmployees).catch(() => setEmployees([]));
|
||||
}
|
||||
function pickEmployee(id) {
|
||||
const e = employees.find((x) => x.id === Number(id));
|
||||
if (!e) return setForm((f) => ({ ...f, employeeId: '' }));
|
||||
const dept = deptOptions.find((d) => fold(d.label) === fold(e.department_name));
|
||||
setForm((f) => ({ ...f, employeeId: e.id, name: titleCase(`${e.first_names} ${e.last_names}`), email: e.email || f.email, position: e.position ? titleCase(e.position) : f.position, departmentId: dept ? dept.value : f.departmentId }));
|
||||
}
|
||||
|
||||
function startEdit(user) {
|
||||
@@ -94,7 +107,7 @@ export default function UsersPanel() {
|
||||
if (selected) {
|
||||
await updateUser(selected.id, { name: form.name.trim(), roleId: form.roleId, departmentId: form.departmentId || null, position: form.position || null, editVersion: form.editVersion });
|
||||
} else {
|
||||
await createUser({ name: form.name.trim(), email: form.email.trim(), roleId: form.roleId, departmentId: form.departmentId || null, position: form.position || null, password: form.password });
|
||||
await createUser({ name: form.name.trim(), email: form.email.trim(), roleId: form.roleId, departmentId: form.departmentId || null, position: form.position || null, password: form.password, employeeId: form.employeeId || undefined });
|
||||
}
|
||||
draft.clear();
|
||||
await reload();
|
||||
@@ -208,6 +221,11 @@ export default function UsersPanel() {
|
||||
|
||||
<DetailPopup open={editing} title={selected ? t('common_edit') : t('user_new')} onClose={() => guardedClose(draft, () => setEditing(false))} width={560} height={500}>
|
||||
<DraftNotice draft={draft} />
|
||||
{!selected && employees.length > 0 && (
|
||||
<Field label={t('user_from_payroll')} dirty={draft.isFieldDirty('employeeId')}>
|
||||
<ComboBox options={[{ value: '', label: '—' }, ...employees.map((e) => ({ value: e.id, label: `${e.last_names} ${e.first_names} · ${e.identification}${e.position ? ` · ${e.position}` : ''}` }))]} value={form.employeeId} onChange={pickEmployee} />
|
||||
</Field>
|
||||
)}
|
||||
<FieldRow>
|
||||
<Field label={t('common_name')} dirty={draft.isFieldDirty('name')}>
|
||||
<input className="input" value={form.name} onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))} autoFocus />
|
||||
|
||||
Reference in New Issue
Block a user