feat: добавил поле организация во все заявки, а также расширил компонет выбора даты для ui-kit

This commit is contained in:
Тимур Городецкий
2026-08-25 11:43:57 +05:00
parent a146877327
commit 3b4ee93b48
55 changed files with 641 additions and 143 deletions
@@ -0,0 +1,21 @@
@import '../../../../../styles/design-tokens';
.list {
display: flex;
flex-direction: column;
gap: $sp-1;
}
// Read-only «карточка» на организацию — тот же радиус/паддинг, что у TextInput
// ($r-sm, 11px/13px из @tnsu/ui-kit), но приглушённый фон/рамка (как в
// readonly-field) — сигнал «подставлено автоматически, не редактируется».
.line {
box-sizing: border-box;
padding: 11px 13px;
border: 1px solid $gray-200;
border-radius: $r-sm;
background: $gray-50;
font-family: $font-sans;
font-size: 13.5px;
color: $gray-700;
}
@@ -0,0 +1,38 @@
import * as React from 'react';
import styles from './OrganizationField.module.scss';
import { FormField } from '@tnsu/ui-kit';
interface IOrganizationFieldProps {
/** `\n`-joined «Компания — должность» на организацию (см. formatEmployeeOrganizations). */
value: string;
/**
* Подпись поля. По умолчанию «Организация» — переопределяют формы, где
* секция описывает ДРУГОГО человека (напр. нового сотрудника), чтобы не
* читалось как «организация того, о ком заявка».
*/
label?: string;
}
/**
* Организации сотрудника — read-only, подтягивается автоматически по e-mail
* (см. shared/dictionary/employeeOrganizations.service.ts), руками не
* редактируется. Сотрудник может состоять сразу в НЕСКОЛЬКИХ организациях —
* тогда строк несколько. Пока не резолвилось (сотрудник не выбран/не найден) —
* ничего не рендерим, а не пустой блок.
*/
export const OrganizationField: React.FC<IOrganizationFieldProps> = ({ value, label = 'Организация' }) => {
const lines = value ? value.split('\n') : [];
if (lines.length === 0) return null;
return (
<FormField label={label} full>
<div className={styles.list}>
{lines.map((line) => (
<div key={line} className={styles.line}>
{line}
</div>
))}
</div>
</FormField>
);
};
@@ -2,6 +2,7 @@ import * as React from 'react';
import { PeoplePicker, PrincipalType } from '@pnp/spfx-controls-react/lib/PeoplePicker';
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { buildPickerContext, pickedLogin, IPickedUser } from './pickerContext';
import { peoplePickerStyles } from './peoplePickerStyles';
import { useFormReadOnly } from '@tnsu/ui-kit';
interface IPeopleFieldProps {
@@ -34,6 +35,7 @@ export const PeopleField: React.FC<IPeopleFieldProps> = ({ context, value, onCha
disabled={effectiveReadOnly}
placeholder={placeholder}
onChange={handleChange}
styles={peoplePickerStyles}
/>
);
};
@@ -2,6 +2,7 @@ import * as React from 'react';
import { PeoplePicker, PrincipalType } from '@pnp/spfx-controls-react/lib/PeoplePicker';
import { WebPartContext } from '@microsoft/sp-webpart-base';
import { buildPickerContext, pickedLogin, IPickedUser } from './pickerContext';
import { peoplePickerStyles } from './peoplePickerStyles';
import { useFormReadOnly } from '@tnsu/ui-kit';
interface IPeopleFieldMultiProps {
@@ -43,6 +44,7 @@ export const PeopleFieldMulti: React.FC<IPeopleFieldMultiProps> = ({
disabled={effectiveReadOnly}
placeholder={placeholder}
onChange={handleChange}
styles={peoplePickerStyles}
/>
);
};
@@ -0,0 +1,40 @@
import type { IBasePickerStyles } from '@fluentui/react/lib/Pickers';
/**
* PeoplePicker — сторонний Fluent-контрол (@pnp/spfx-controls-react), а не наш
* @tnsu/ui-kit: у него нет своего CSS-модуля и без этих стилей он выглядит
* дефолтным Fluent-полем — другой border-radius, другие паддинги, другой цвет
* рамки, не в тон соседним полям формы (TextInput и т.п.). Стилизуется через
* fluent-проп `styles` (IBasePickerStyles), а не через className/CSS-модуль,
* поэтому значения ниже — те же токены, что в styles/_design-tokens.scss
* ($gray-300, $gold-500, $r-sm, TextInput.module.scss), но продублированы как
* значения: Fluent styles-проп не умеет импортировать SCSS-переменные.
*/
export const peoplePickerStyles: Partial<IBasePickerStyles> = {
text: {
boxSizing: 'border-box',
padding: '9px 12px',
border: '1px solid #D1D5DB', // $gray-300
borderRadius: 6, // $r-sm
background: '#fff',
fontFamily: "'Inter', -apple-system, 'Segoe UI', sans-serif",
fontSize: 13.5,
transition: '.18s',
selectors: {
// Фокус — на самом input внутри .text, подсветку показываем на всей рамке.
':focus-within': {
borderColor: '#D4A01E', // $gold-500
boxShadow: '0 0 0 3px rgba(212,160,30,.12)'
}
}
},
input: {
padding: 0,
fontFamily: "'Inter', -apple-system, 'Segoe UI', sans-serif",
fontSize: 13.5,
color: '#111827', // $gray-900
selectors: {
'::placeholder': { color: '#9CA3AF' } // $gray-400
}
}
};
@@ -38,6 +38,7 @@ export { EMPTY_LINE_ITEM } from './ItemsTable/ILineItem';
export { parseExcelRows } from './ItemsTable/parseExcelRows';
export { PeopleField } from './PeopleField/PeopleField';
export { PeopleFieldMulti } from './PeopleField/PeopleFieldMulti';
export { OrganizationField } from './OrganizationField/OrganizationField';
export { ErrorOverlay } from './ErrorOverlay/ErrorOverlay';
export { SubmitActions } from './SubmitActions/SubmitActions';
export { InitiatorLine } from './InitiatorLine/InitiatorLine';