Поля профиля с options.readOnly: запрет редактирования «Оценки» и «Средней оценки»
- UI: read-only поля отображаются текстом (у history-number остаётся кнопка графика), исключены из сохранения; кнопка «Сохранить» скрыта, если все поля вкладки read-only - Сервер: POST/PATCH field-values профиля отклоняют запись в поле с options.readOnly (400); автоматизации пишут через storage напрямую и не затрагиваются - Данные: options.readOnly=true у полей «Оценка» и «Средняя оценка»
This commit is contained in:
@@ -334,26 +334,33 @@ function HistoryTab({ auditLog }: { auditLog: AuditLogEntry[] }) {
|
||||
|
||||
// Поле history-number: обычный ввод значения + кнопка с графиком истории оценок.
|
||||
// График доступен всегда (просмотр), редактирование значения — как у остальных полей.
|
||||
// readOnly (options.readOnly): ввод недоступен — только значение текстом и график.
|
||||
function HistoryNumberFieldInput({
|
||||
field,
|
||||
value,
|
||||
onChange,
|
||||
userId,
|
||||
readOnly,
|
||||
}: {
|
||||
field: UserProfileField;
|
||||
value: any;
|
||||
onChange: (val: any) => void;
|
||||
userId: number;
|
||||
readOnly?: boolean;
|
||||
}) {
|
||||
const [historyOpen, setHistoryOpen] = useState(false);
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
value={value ?? ''}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={field.placeholder}
|
||||
required={field.isRequired}
|
||||
/>
|
||||
{readOnly ? (
|
||||
<span className="text-sm leading-6">{value ?? '—'}</span>
|
||||
) : (
|
||||
<Input
|
||||
value={value ?? ''}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={field.placeholder}
|
||||
required={field.isRequired}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
@@ -388,6 +395,22 @@ function UserProfileFieldInput({
|
||||
users: Array<{ id: number; firstName?: string; lastName?: string; middleName?: string; email: string }>;
|
||||
userId: number;
|
||||
}) {
|
||||
// Поля с options.readOnly — только отображение (редактирование запрещено на сервере)
|
||||
const readOnly = !!field.options?.readOnly;
|
||||
if (field.type === 'history-number') {
|
||||
return (
|
||||
<HistoryNumberFieldInput
|
||||
field={field}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
userId={userId}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (readOnly) {
|
||||
return <span className="text-sm leading-6">{value === null || value === undefined || value === '' ? '—' : String(value)}</span>;
|
||||
}
|
||||
switch (field.type) {
|
||||
case 'text':
|
||||
case 'contract-number':
|
||||
@@ -399,15 +422,6 @@ function UserProfileFieldInput({
|
||||
required={field.isRequired}
|
||||
/>
|
||||
);
|
||||
case 'history-number':
|
||||
return (
|
||||
<HistoryNumberFieldInput
|
||||
field={field}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
userId={userId}
|
||||
/>
|
||||
);
|
||||
case 'number':
|
||||
return (
|
||||
<Input
|
||||
@@ -894,7 +908,10 @@ export default function UserProfilePage({ userId: userIdProp }: { userId?: numbe
|
||||
};
|
||||
|
||||
const onSaveCustomValues = (tabFields: UserProfileField[]) => {
|
||||
const values = tabFields.map((f) => ({
|
||||
// Read-only поля (например «Оценка», «Средняя оценка») не сохраняем — сервер их отклонит
|
||||
const editable = tabFields.filter((f) => !f.options?.readOnly);
|
||||
if (editable.length === 0) return;
|
||||
const values = editable.map((f) => ({
|
||||
fieldId: f.id,
|
||||
value: customValues[f.id] ?? null,
|
||||
}));
|
||||
@@ -1311,7 +1328,7 @@ export default function UserProfilePage({ userId: userIdProp }: { userId?: numbe
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{canEdit && (
|
||||
{canEdit && tabFields.some((f) => !f.options?.readOnly) && (
|
||||
<Button
|
||||
onClick={() => onSaveCustomValues(tabFields)}
|
||||
disabled={saveCustomValuesMutation.isPending}
|
||||
|
||||
Reference in New Issue
Block a user