fix: таблица организаций — «Структурное подразделение», должности все редактируемы, без лишней подписи

Три правки в OrganizationsTable по месту (без изменений схемы/формата хранения
— поле по-прежнему называется department, источник тот же Section):

1. Заголовок колонки «Департамент» → «Структурное подразделение».
2. Должность редактируема В КАЖДОЙ строке, не только у первой (основной)
   компании — справочник не всегда точен, поправить нужно любую. На маршрут и
   валидацию по-прежнему влияет только должность ПЕРВОЙ записи
   (primaryPosition) — остальные редактируются, но ни на что не влияют.
   onChangePosition получил индекс строки вместо неявного «всегда нулевая».
3. Убрана обёртка FormField с подписью «Организация · подразделение ·
   должность *» над таблицей — избыточна, у таблицы уже есть заголовки
   колонок. full-ширина (grid-column: 1/-1) взята напрямую в CSS, без FormField
   (у него label обязателен).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
gorbenko.pavel
2026-08-26 10:43:40 +05:00
parent 435aceb83f
commit e90cad7e88
4 changed files with 46 additions and 28 deletions
@@ -28,20 +28,22 @@ RequestTypeId» в `docs/forms.md`). Тип заведён в админке app
People-picker (`Employee`), маршрут резолвится по e-mail **выбранного** сотрудника, People-picker (`Employee`), маршрут резолвится по e-mail **выбранного** сотрудника,
reloadKey матрицы включает `data.Employee` и должность основной компании (см. ниже). reloadKey матрицы включает `data.Employee` и должность основной компании (см. ниже).
- **Автоподстановка при выборе сотрудника:** вся таблица организаций — из ОДНОГО - **Автоподстановка при выборе сотрудника:** вся таблица организаций — из ОДНОГО
справочника `Employee/ByEmail` (`shared/dictionary`) — см. «Организация · справочника `Employee/ByEmail` (`shared/dictionary`) — см. ниже.
подразделение · должность». - **Таблица организаций** (`OrganizationsTable`, `forms/Dismissal/sections/`, без
- **«Организация · подразделение · должность»** (`OrganizationsTable`, подписи над собой — у неё уже есть заголовки колонок «Компания» / «Структурное
`forms/Dismissal/sections/`) — ЕДИНСТВЕННОЕ место для компании, департамента и подразделение» / «Должность») — ЕДИНСТВЕННОЕ место для компании, подразделения и
должности, и на форме подачи (редактируемая), и в режиме просмотра (read-only) — должности, и на форме подачи (редактируемая), и в режиме просмотра (read-only) —
отдельных полей для них нет (были бы дублированием). `data.Organization: отдельных полей для них нет (были бы дублированием). `data.Organization:
IOrganizationRecord[]` — массив `{company, department, position}`, компания и IOrganizationRecord[]` — массив `{company, department, position}`; поле называется
департамент по каждой строке read-only; редактируется только должность ПЕРВОЙ `department` (тот же справочник `Section`, что и раньше — см. следующий абзац),
(основной) записи — `primaryPosition(data.Organization)`, используется маршрутом колонка называется «Структурное подразделение», и то и другое read-only. Должность
(условие «IsManager») и обязательна валидацией; должности остальных компаний редактируема В ЛЮБОЙ строке (справочник не всегда точен), но маршрут (условие
(совместитель) — информационные. Справочник ничего не вернул (сотрудник не найден «IsManager») и обязательность по валидации завязаны ТОЛЬКО на должность ПЕРВОЙ
в api.tnsu.kz) — таблица всё равно рендерится одной строкой с прочерками вместо (основной) записи — `primaryPosition(data.Organization)`; должности остальных
компании/департамента и редактируемой должностью: иначе такому человеку нечем было компаний правятся, но ни на что не влияют. Справочник ничего не вернул (сотрудник
бы заполнить обязательное поле. не найден в api.tnsu.kz) — таблица всё равно рендерится одной строкой с прочерками
вместо компании/подразделения и редактируемой должностью: иначе такому человеку
нечем было бы заполнить обязательное поле.
**Хранение в SharePoint — три ПАРАЛЛЕЛЬНЫХ JSON-столбца**, не один общий блок: **Хранение в SharePoint — три ПАРАЛЛЕЛЬНЫХ JSON-столбца**, не один общий блок:
`Organization`/`Department`/`Position` — каждый содержит JSON-массив строк `Organization`/`Department`/`Position` — каждый содержит JSON-массив строк
@@ -1,5 +1,13 @@
@import '../../../../../styles/design-tokens'; @import '../../../../../styles/design-tokens';
// Занять обе колонки сетки формы — то же, что даёт проп full у FormField
// (._full_1wk7n_8{grid-column:1/-1} в @tnsu/ui-kit), без самого FormField:
// подпись над таблицей не нужна, у неё уже есть заголовки колонок.
.wrapper {
grid-column: 1 / -1;
min-width: 0;
}
.table { .table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
@@ -1,18 +1,18 @@
import * as React from 'react'; import * as React from 'react';
import { FormField, Icon, TextInput } from '../../../shared/ui'; import { Icon, TextInput } from '../../../shared/ui';
import { IOrganizationRecord } from '../model'; import { IOrganizationRecord } from '../model';
import styles from './OrganizationsTable.module.scss'; import styles from './OrganizationsTable.module.scss';
interface IOrganizationsTableProps { interface IOrganizationsTableProps {
records: IOrganizationRecord[]; records: IOrganizationRecord[];
/** /**
* Должность ПЕРВОЙ (основной) компании редактируема — единственная ячейка * Должность любой строки можно поправить — справочник не всегда точен, а
* таблицы, у которой это так: маршрут согласования резолвится по НЕЙ (см. * условие маршрута «IsManager» резолвится по должности ПЕРВОЙ (основной)
* DismissalForm — fieldValues.Position), должности остальных компаний * компании (см. DismissalForm — primaryPosition/fieldValues.Position), так
* (совместитель) — только для просмотра, ставить им что-то в соответствие * что расхождение со справочником там, где оно есть, важно уметь исправить.
* незачем. Не задано — вся таблица read-only (режим просмотра, DismissalView). * Не задано — вся таблица read-only (режим просмотра, DismissalView).
*/ */
onChangePosition?: (value: string) => void; onChangePosition?: (index: number, value: string) => void;
/** /**
* Убрать компанию из заявления — есть только на форме подачи (см. * Убрать компанию из заявления — есть только на форме подачи (см.
* DismissalForm.handleRemoveOrganization), не в режиме просмотра: там состав * DismissalForm.handleRemoveOrganization), не в режиме просмотра: там состав
@@ -22,7 +22,7 @@ interface IOrganizationsTableProps {
} }
/** /**
* Организации сотрудника — ЕДИНСТВЕННОЕ место для компании/департамента/ * Организации сотрудника — ЕДИНСТВЕННОЕ место для компании/подразделения/
* должности, и на форме подачи (редактируемая — см. onChangePosition/onRemove), * должности, и на форме подачи (редактируемая — см. onChangePosition/onRemove),
* и в режиме просмотра заявки (read-only, те же данные из сохранённой заявки, * и в режиме просмотра заявки (read-only, те же данные из сохранённой заявки,
* а не live-запрос к справочнику — см. DismissalView). Раньше это были * а не live-запрос к справочнику — см. DismissalView). Раньше это были
@@ -42,17 +42,21 @@ interface IOrganizationsTableProps {
* редактируемой должностью: без этого заявление на человека без карточки в * редактируемой должностью: без этого заявление на человека без карточки в
* справочнике нельзя было бы подать вовсе (некуда вписать должность вручную — * справочнике нельзя было бы подать вовсе (некуда вписать должность вручную —
* а маршрут согласования без неё не резолвит условие «IsManager»). * а маршрут согласования без неё не резолвит условие «IsManager»).
*
* Без обёртки FormField/подписи над таблицей намеренно — у нёе уже есть
* заголовки колонок, дублировать их общей подписью незачем; full-ширина взята
* напрямую (см. .wrapper), а не через FormField (там label обязателен).
*/ */
export const OrganizationsTable: React.FC<IOrganizationsTableProps> = ({ records, onChangePosition, onRemove }) => { export const OrganizationsTable: React.FC<IOrganizationsTableProps> = ({ records, onChangePosition, onRemove }) => {
const rows = records.length > 0 ? records : [undefined]; const rows = records.length > 0 ? records : [undefined];
return ( return (
<FormField label="Организация · подразделение · должность" required={!!onChangePosition} full> <div className={styles.wrapper}>
<table className={styles.table}> <table className={styles.table}>
<thead> <thead>
<tr> <tr>
<th className={styles.company}>Компания</th> <th className={styles.company}>Компания</th>
<th className={styles.department}>Департамент</th> <th className={styles.department}>Структурное подразделение</th>
<th className={styles.position}>Должность</th> <th className={styles.position}>Должность</th>
{onRemove && <th className={styles.action} />} {onRemove && <th className={styles.action} />}
</tr> </tr>
@@ -62,9 +66,13 @@ export const OrganizationsTable: React.FC<IOrganizationsTableProps> = ({ records
<tr key={r?.company || 'manual'}> <tr key={r?.company || 'manual'}>
<td className={styles.company}>{r?.company || '—'}</td> <td className={styles.company}>{r?.company || '—'}</td>
<td className={styles.department}>{r?.department || '—'}</td> <td className={styles.department}>{r?.department || '—'}</td>
{i === 0 && onChangePosition ? ( {onChangePosition ? (
<td className={styles.positionInput}> <td className={styles.positionInput}>
<TextInput value={r?.position || ''} onChange={onChangePosition} placeholder="Введите должность" /> <TextInput
value={r?.position || ''}
onChange={(v) => onChangePosition(i, v)}
placeholder="Введите должность"
/>
</td> </td>
) : ( ) : (
<td className={styles.position}>{r?.position || '—'}</td> <td className={styles.position}>{r?.position || '—'}</td>
@@ -88,6 +96,6 @@ export const OrganizationsTable: React.FC<IOrganizationsTableProps> = ({ records
))} ))}
</tbody> </tbody>
</table> </table>
</FormField> </div>
); );
}; };
@@ -37,7 +37,7 @@ export const RequestSection: React.FC<IRequestSectionProps> = ({
placeholder={EMPLOYEE_PLACEHOLDER} placeholder={EMPLOYEE_PLACEHOLDER}
/> />
</FormField> </FormField>
{/* ЕДИНСТВЕННОЕ место для компании/департамента/должности — и на форме {/* ЕДИНСТВЕННОЕ место для компании/подразделения/должности — и на форме
подачи (редактируемая, onRemoveOrganization задан), и в просмотре подачи (редактируемая, onRemoveOrganization задан), и в просмотре
(read-only, ничего не передано). Отдельных полей для них нет — были бы (read-only, ничего не передано). Отдельных полей для них нет — были бы
дублированием того, что уже показывает эта таблица. */} дублированием того, что уже показывает эта таблица. */}
@@ -45,11 +45,11 @@ export const RequestSection: React.FC<IRequestSectionProps> = ({
records={data.Organization} records={data.Organization}
onChangePosition={ onChangePosition={
onRemoveOrganization && onRemoveOrganization &&
((v) => ((i, v) =>
set( set(
'Organization', 'Organization',
data.Organization.length > 0 data.Organization.length > 0
? data.Organization.map((r, i) => (i === 0 ? { ...r, position: v } : r)) ? data.Organization.map((r, ri) => (ri === i ? { ...r, position: v } : r))
: [{ company: '', department: '', position: v }] : [{ company: '', department: '', position: v }]
)) ))
} }