feat(profile): read-first редизайн страницы пользователя, per-field как в задачах

- Раскладка: шапка-профиль → Основное/Контакты → Роль и доступ/Поля+Замещение → вторичные разделы чипами
- ProfileFieldRow: двойной клик/карандаш, редактор в слоте, Esc, optimistic-кэш, все типы полей
- Замещение: надпись + ⊕ popover, список под строкой
- Удалены RHF-форма, zod-схема и кнопки «Сохранить»
- server: PATCH /api/users/:id (частичное обновление, admin-градация, аудит)
- server: history-number в REST PATCH пишет историю + пересчёт средней
This commit is contained in:
2026-09-06 18:47:05 +03:00
parent ddc2bd9491
commit a60a75eda0
6 changed files with 2041 additions and 914 deletions

View File

@@ -345,12 +345,38 @@ export function registerUserProfileRoutes(router: Router): void {
if (readOnlyError) {
return res.status(400).json({ success: false, error: readOnlyError });
}
const field = await storage.getUserProfileFieldById(fieldId, req.organizationId!);
const existing = await storage.getUserProfileFieldValue(userId, fieldId);
if (value === null || value === undefined) {
// Удаление (null): очищается только значение поля, история history-number не трогается
if (existing) {
await storage.deleteUserProfileFieldValueWithAudit(userId, fieldId, req.organizationId!, changedBy);
}
res.json({ success: true, fieldValue: null });
} else if (field!.type === 'history-number') {
// history-number: число пишется в user_field_history (актор — текущий пользователь),
// становится значением поля, месячная средняя пересчитывается
let num: number;
try {
num = await storage.recordUserFieldHistoryValue(userId, field!, value, req.organizationId!, {
auditActor: {
changedBy,
changedByName: `${req.user!.firstName || ''} ${req.user!.lastName || ''}`.trim(),
},
});
} catch (err) {
return res.status(400).json({
success: false,
error: err instanceof Error ? err.message : 'Значение должно быть числом',
});
}
if (existing) {
const updated = await storage.updateUserProfileFieldValueWithAudit(userId, fieldId, { value: num }, req.organizationId!, changedBy);
res.json({ success: true, fieldValue: updated });
} else {
const created = await storage.createUserProfileFieldValueWithAudit({ userId, fieldId, value: num }, req.organizationId!, changedBy);
res.json({ success: true, fieldValue: created });
}
} else {
if (existing) {
const updated = await storage.updateUserProfileFieldValueWithAudit(userId, fieldId, { value }, req.organizationId!, changedBy);