feat: варианты полей заявки 1С из колонок SP + вид работ «Маршруты в ДО»
«Виды работ», «Организация» и «База 1С» теперь читаются из колонок списка (общий хук shared/fieldChoices), а не из констант: новый пункт добавляется в параметрах столбца без правки кода и деплоя. Пилот — на форме 1С, при успехе переводим остальные формы. В стартовые варианты provisioning.json добавлен «Маршруты в ДО». Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import '@pnp/sp/fields';
|
||||
import { getSP } from '../pnp/pnpjs';
|
||||
|
||||
/** Ответ SP по колонке Choice: массив в nometadata, { results } — в verbose-формате OData. */
|
||||
interface IChoiceFieldResponse {
|
||||
Choices?: string[] | { results?: string[] };
|
||||
}
|
||||
|
||||
// Кеш на время жизни страницы: варианты администратор меняет редко, а форму за
|
||||
// сессию открывают по нескольку раз — повторно ходить в SP незачем. Хранится
|
||||
// промис, чтобы параллельные вызовы (три поля одной формы) не дублировали запрос.
|
||||
const cache = new Map<string, Promise<string[]>>();
|
||||
|
||||
/**
|
||||
* Варианты колонки Choice/MultiChoice прямо из списка SharePoint.
|
||||
*
|
||||
* Колонка списка — единственный источник вариантов: чтобы добавить пункт,
|
||||
* достаточно дописать его в параметрах столбца, без правки кода и деплоя.
|
||||
*/
|
||||
export function getFieldChoices(listTitle: string, internalName: string): Promise<string[]> {
|
||||
const key = `${listTitle}|${internalName}`.toLowerCase();
|
||||
const cached = cache.get(key);
|
||||
if (cached) return cached;
|
||||
|
||||
const request = loadFieldChoices(listTitle, internalName);
|
||||
// Сбой не кешируем — при следующем открытии формы запрос уйдёт заново.
|
||||
request.catch(() => cache.delete(key));
|
||||
cache.set(key, request);
|
||||
return request;
|
||||
}
|
||||
|
||||
async function loadFieldChoices(listTitle: string, internalName: string): Promise<string[]> {
|
||||
const field = (await getSP()
|
||||
.web.lists.getByTitle(listTitle)
|
||||
.fields.getByInternalNameOrTitle(internalName)
|
||||
.select('Choices')()) as IChoiceFieldResponse;
|
||||
|
||||
const choices = Array.isArray(field.Choices) ? field.Choices : field.Choices?.results || [];
|
||||
return choices.filter((choice) => choice.trim() !== '');
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export { getFieldChoices } from './fieldChoices.service';
|
||||
export { useFieldChoices } from './useFieldChoices';
|
||||
export type { IFieldChoicesState } from './useFieldChoices';
|
||||
@@ -0,0 +1,36 @@
|
||||
import * as React from 'react';
|
||||
import { ISelectOption } from '@tnsu/ui-kit';
|
||||
import { friendlyErrorMessage } from '../diagnostics';
|
||||
import { getFieldChoices } from './fieldChoices.service';
|
||||
|
||||
export interface IFieldChoicesState {
|
||||
/** Варианты колонки в формате Select/MultiChoiceField; пусто, пока грузится или при ошибке. */
|
||||
options: ISelectOption[];
|
||||
isLoading: boolean;
|
||||
/** Текст ошибки загрузки; undefined — ошибки нет. */
|
||||
error?: string;
|
||||
}
|
||||
|
||||
const LOADING_STATE: IFieldChoicesState = { options: [], isLoading: true };
|
||||
|
||||
/** Варианты колонки Choice списка SharePoint — см. getFieldChoices. */
|
||||
export function useFieldChoices(listTitle: string, internalName: string): IFieldChoicesState {
|
||||
const [state, setState] = React.useState<IFieldChoicesState>(LOADING_STATE);
|
||||
|
||||
React.useEffect(() => {
|
||||
let alive = true;
|
||||
setState(LOADING_STATE);
|
||||
getFieldChoices(listTitle, internalName)
|
||||
.then((choices) => {
|
||||
if (alive) setState({ options: choices.map((c) => ({ value: c, label: c })), isLoading: false });
|
||||
})
|
||||
.catch((e) => {
|
||||
if (alive) setState({ options: [], isLoading: false, error: friendlyErrorMessage(e) });
|
||||
});
|
||||
return () => {
|
||||
alive = false;
|
||||
};
|
||||
}, [listTitle, internalName]);
|
||||
|
||||
return state;
|
||||
}
|
||||
Reference in New Issue
Block a user