Files
tnsu-approval-route/frontend/src/webparts/approvalAdmin/texts.ts
T
gorbenko.pavel f4542c7b39
Tests / frontend-test (pull_request) Successful in 43s
Tests / backend-test (pull_request) Successful in 4m0s
добавлен новый тип менеджера и условие новое
2026-08-13 12:16:22 +05:00

381 lines
23 KiB
TypeScript

// Все пользовательские тексты интерфейса — в одном месте.
// Так их видно целиком (легко вычитать формулировки) и это задел под локализацию:
// компонентам не нужно знать, откуда пришла строка.
//
// Строки с подстановкой оформлены функциями, чтобы шаблон и данные не разъезжались.
import { ComparisonOperator, HeadType, ProjectRole, RouteStepStatus } from './models';
import { pluralRu } from './utils/plural';
// ---- Подписи enum'ов ----
// Record<enum, string> гарантирует, что при добавлении значения в enum
// компилятор потребует и подпись к нему.
export const routeStepStatusLabels: Record<RouteStepStatus, string> = {
[RouteStepStatus.None]: 'Не задан',
[RouteStepStatus.Approval]: 'Согласование',
[RouteStepStatus.Execution]: 'Исполнение'
};
export const projectRoleLabels: Record<ProjectRole, string> = {
[ProjectRole.None]: '—',
[ProjectRole.ProjectManager]: 'Руководитель проекта',
[ProjectRole.Planner]: 'Планировщик',
[ProjectRole.ManagerHSE]: 'Менеджер HSE',
[ProjectRole.Security]: 'Безопасность',
[ProjectRole.Keeper]: 'Заведующий склада'
};
export const headTypeLabels: Record<HeadType, string> = {
[HeadType.None]: '—',
[HeadType.Chief]: 'Непосредственный руководитель',
[HeadType.DirectionHead]: 'Руководитель направления',
[HeadType.DirectorTansu]: 'Директор Tansu',
[HeadType.ManagingDirectorTansu]: 'Управляющий директор Tansu',
[HeadType.DepartmentHead]: 'Руководитель указанного отдела'
};
export const comparisonOperatorLabels: Record<ComparisonOperator, string> = {
[ComparisonOperator.None]: '—',
[ComparisonOperator.GreaterThan]: 'Больше (>)',
[ComparisonOperator.LessThan]: 'Меньше (<)',
[ComparisonOperator.Equal]: 'Равно (=)'
};
export const comparisonOperatorSymbols: Record<ComparisonOperator, string> = {
[ComparisonOperator.None]: '',
[ComparisonOperator.GreaterThan]: '>',
[ComparisonOperator.LessThan]: '<',
[ComparisonOperator.Equal]: '='
};
export const texts = {
common: {
home: 'Главная',
cancel: 'Отмена',
save: 'Сохранить',
saving: 'Сохранение...',
remove: 'Удалить',
edit: 'Редактировать',
close: 'Закрыть',
loading: 'Загрузка...',
yes: 'Да',
no: 'Нет',
/**
* Третье состояние для настроек с наследованием (SLA, опрос причины
* просрочки): пусто значит «взять значение уровнем выше», и это НЕ то же
* самое, что «Нет» или «0» — их выбирают осознанно.
*/
inherited: 'Не задано',
/** Прочерк вместо пустого значения — чтобы ячейка таблицы не выглядела сломанной. */
empty: '—'
},
nav: {
title: 'Панель настройки маршрутов',
dashboard: 'Дашборд',
requestTypes: 'Типы заявок',
routes: 'Маршруты',
resolve: 'Проверка маршрута',
manageRequests: 'Редактирование заявок'
},
app: {
connecting: 'Подключение к API...',
connectFailed: (message: string): string => `Не удалось подключиться к API: ${message}`,
connectHint: 'Проверьте свойства веб-части (Api Base Url, режим авторизации) в панели настроек.'
},
dashboard: {
requestTypesStat: 'Типов заявок',
routesStat: 'Маршрутов согласования',
sections: 'Разделы',
requestTypesCard: {
title: 'Типы заявок',
description: 'Справочник типов заявок и их страниц'
},
routesCard: {
title: 'Маршруты согласования',
description: 'Маршруты, шаги и исполнители'
},
resolveCard: {
title: 'Проверка маршрута',
description: 'Как разрешится маршрут для конкретного пользователя'
}
},
requestTypes: {
breadcrumb: 'Типы заявок',
subtitle: 'Справочник',
title: 'Типы заявок',
add: 'Добавить тип',
emptyList: 'Типов заявок пока нет.',
columnName: 'Название',
columnPageUrl: 'Url страницы',
columnSection: 'Раздел',
columnListId: 'Список SharePoint',
columnSiteUrl: 'Сайт SharePoint',
columnSla: 'SLA',
createTitle: 'Новый тип заявки',
editTitle: 'Редактировать тип заявки',
nameLabel: 'Название',
namePlaceholder: 'Например, Заявка на командировку',
pageUrlLabel: 'Url страницы',
pageUrlHint: 'Страница формы заявки на портале.',
pageUrlPlaceholder: '/pages/business-trip',
sectionLabel: 'Раздел',
sectionPlaceholder: 'Например, Кадры',
siteUrlLabel: 'Сайт SharePoint',
siteUrlHint: 'Сайт, где живёт список — например, https://tnsukz.sharepoint.com/sites/hrm.',
siteUrlPlaceholder: 'https://tnsukz.sharepoint.com/sites/hrm',
siteUrlSearchPlaceholder: 'Поиск сайта по названию...',
listIdLabel: 'Список SharePoint',
listIdHint: 'Источник полей (field_name) для условий в шагах маршрута.',
listIdPlaceholder: 'Сначала укажите сайт SharePoint',
listIdLoading: 'Загрузка списков сайта...',
listIdEmpty: 'На этом сайте нет доступных списков',
listIdError: (message: string): string => `Не удалось получить списки сайта: ${message}`,
slaHoursLabel: 'SLA, часов',
slaHoursHint:
'Через сколько часов простоя шага напоминать. Пусто — напоминания по этому типу заявок не работают. ' +
'Отсчёт идёт от момента, когда шаг стал активным.',
slaHoursPlaceholder: 'Например, 24',
askOverdueReasonLabel: 'Спрашивать причину просрочки',
askOverdueReasonHint: 'Значение по умолчанию для всех шагов этого типа заявок.',
validationSlaHours: 'SLA укажите целым числом часов (0 и больше) или оставьте поле пустым.',
validationRequired: 'Заполните имя, Url страницы и раздел.',
validationListReference: 'Сайт SharePoint и список должны быть заполнены оба вместе или не заполнены вовсе.',
deleteTitle: 'Удалить тип заявки?',
deleteText: (name: string): string =>
`Тип «${name}» будет удалён безвозвратно. Связанные маршруты могут перестать разрешаться.`
},
routes: {
breadcrumb: 'Маршруты согласования',
subtitle: 'Настройка согласований',
title: 'Маршруты согласования',
add: 'Добавить маршрут',
filterLabel: 'Фильтр по типу заявки',
filterAll: 'Все типы заявок',
dictionaryFailed: (message: string): string => `Справочник компаний и отделов недоступен: ${message}`,
emptyList: 'Маршрутов пока нет.',
columnRequestType: 'Тип заявки',
columnCompany: 'Компания',
columnDepartment: 'Отдел',
columnByProject: 'По проекту',
columnControl: 'Контроль',
controlRequired: 'Требуется',
openSteps: 'Шаги',
createTitle: 'Новый маршрут',
editTitle: (routeId: number): string => `Маршрут #${routeId}`,
requestTypeLabel: 'Тип заявки',
requestTypePlaceholder: 'Выберите тип заявки',
companyLabel: 'Компания',
companyHint: 'Необязательно — пусто означает запасной маршрут, если для компании инициатора своего маршрута нет.',
companyPlaceholder: 'Без компании (запасной маршрут)',
companySearchPlaceholder: 'Поиск по названию компании...',
companyClear: 'Без компании (запасной маршрут)',
departmentLabel: 'Отдел',
departmentHint: 'Необязательно — пусто означает маршрут «по умолчанию» для всей компании.',
departmentHintNoCompany: 'Сначала выберите компанию.',
departmentPlaceholder: 'Без отдела (маршрут по умолчанию)',
departmentSearchPlaceholder: 'Поиск по названию отдела...',
departmentEmpty: 'У выбранной компании нет отделов в справочнике',
byProjectLabel: 'По проекту',
byProjectCheckbox: 'Маршрут учитывает конкретный проект',
controlLabel: 'Контроль',
controlCheckbox: 'Требуется отдельный контроль исполнения',
validationRequestType: 'Выберите тип заявки.',
deleteTitle: 'Удалить маршрут?',
deleteText: (routeId: number): string =>
`Маршрут #${routeId} и все его шаги будут удалены безвозвратно.`
},
routeSteps: {
breadcrumb: (routeId: number): string => `Шаги маршрута #${routeId}`,
title: (routeId: number): string => `Маршрут #${routeId}`,
subtitle: (requestTypeName: string): string => requestTypeName,
back: 'К маршрутам',
add: 'Добавить шаг',
emptyList: 'Шагов пока нет — добавьте первый.',
columnOrderNo: '№',
columnExecutor: 'Исполнитель',
columnStatus: 'Статус',
columnReassign: 'Переназначение',
columnCondition: 'Условие',
reassignAllowed: 'Разрешено',
executorRole: (roleLabel: string): string => `Роль: ${roleLabel}`,
executorHead: (headLabel: string): string => `Руководитель: ${headLabel}`,
createTitle: 'Новый шаг маршрута',
editTitle: (orderNo: number): string => `Шаг #${orderNo}`,
orderNoLabel: 'Порядковый номер',
statusLabel: 'Статус шага',
kindLabel: 'Кто исполнитель',
kindHint: 'Должно быть заполнено ровно одно из полей.',
kindEmail: 'Конкретный сотрудник',
kindPosition: 'По должности в компании инициатора',
kindProjectRole: 'По роли в проекте',
kindProjectRoleDisabled: 'Недоступно: маршрут не учитывает проект (по проекту = нет).',
kindHeadType: 'По типу руководителя',
executorLabel: 'Сотрудник',
executorPlaceholder: 'Выберите сотрудника',
positionLabel: 'Должность',
positionHint: 'Ищется в справочнике api.tnsu.kz по компании инициатора.',
positionPlaceholder: 'Главный инженер',
projectRoleLabel: 'Роль в проекте',
headTypeLabel: 'Тип руководителя',
reassignLabel: 'Переназначение',
reassignCheckbox: 'Разрешить переназначать другим согласующим',
slaHoursLabel: 'SLA шага, часов',
slaHoursHint:
'Пусто — берётся SLA типа заявки. Заданное здесь значение его перекрывает; 0 означает «напоминать сразу».',
slaHoursPlaceholder: 'Как у типа заявки',
askOverdueReasonLabel: 'Спрашивать причину просрочки',
askOverdueReasonHint: '«Не задано» — берётся значение типа заявки.',
conditionLabel: 'Условие видимости шага',
conditionCheckbox: 'Показывать шаг только при выполнении условия',
conditionDisabledHint:
'У типа заявки не указан список SharePoint (list_name) — доступно только служебное поле «Инициатор — руководитель».',
conditionFieldIsManagerLabel: 'Инициатор — руководитель (IsManager)',
conditionFieldNameLabel: 'Поле списка',
conditionFieldNamePlaceholder: 'Выберите столбец списка',
conditionFieldNameLoading: 'Загрузка столбцов списка...',
conditionFieldNameEmpty: 'В списке нет доступных столбцов',
conditionFieldNameError: (message: string): string => `Не удалось получить столбцы списка: ${message}`,
conditionOperationLabel: 'Оператор сравнения',
conditionValueLabel: 'Значение',
conditionValuePlaceholder: 'Например, 1000',
validationOrderNo: 'Порядковый номер шага обязателен.',
validationExecutor: 'Выберите сотрудника-исполнителя.',
validationPosition: 'Укажите должность.',
validationCondition: 'Заполните поле списка, оператор и значение условия.',
validationConditionNumber: 'Выбранное поле числовое — значение условия должно быть числом.',
validationSlaHours: 'SLA укажите целым числом часов (0 и больше) или оставьте поле пустым.',
deleteTitle: 'Удалить шаг?',
deleteText: (orderNo: number): string => `Шаг #${orderNo} будет удалён безвозвратно.`
},
resolve: {
breadcrumb: 'Проверка маршрута',
subtitle: 'Диагностика',
title: 'Проверка разрешения маршрута',
requestTypeLabel: 'Тип заявки',
requestTypePlaceholder: 'Выберите тип заявки',
userEmailLabel: 'Инициатор',
userEmailHint: (currentUserEmail: string): string => `Пусто = текущий пользователь (${currentUserEmail}).`,
userEmailHintUnknown: 'Пусто = текущий пользователь из токена.',
userEmailPlaceholder: 'Выберите сотрудника',
userEmailClear: 'Текущий пользователь',
projectLabel: 'Проект',
projectHint: 'Заполните, если маршрут зависит от проекта (ByProject = true).',
projectPlaceholder: 'Не выбран',
projectLoadingPlaceholder: 'Справочник загружается...',
projectSearchPlaceholder: 'Поиск проекта по названию...',
projectClear: 'Без проекта',
projectDictionaryLoading: 'Справочник проектов ещё загружается.',
departmentLabel: 'Отдел (для шага «Руководитель указанного отдела»)',
departmentHint: 'Заполните, если в маршруте есть шаг с типом руководителя «Руководитель указанного отдела».',
departmentPlaceholder: 'Не выбран',
departmentSearchPlaceholder: 'Поиск по названию отдела...',
departmentClear: 'Без отдела',
departmentEmpty: 'Отделы не найдены',
statusLabel: 'Статус',
conditionsLabel: 'Условия (значения полей заявки)',
conditionsDisabledHint: 'Недоступно: у выбранного типа заявки не указан список SharePoint (list_name).',
conditionFieldPlaceholder: 'Выберите столбец списка',
conditionFieldLoading: 'Загрузка столбцов списка...',
conditionValuePlaceholder: 'Значение для проверки',
conditionAdd: 'Добавить условие',
run: 'Проверить',
running: 'Проверка...',
validationRequestType: 'Выберите тип заявки.',
validationConditionField: 'В каждом условии заполните и поле, и значение.',
summary: (routeId: number, requiresControl: boolean): string =>
`Маршрут #${routeId}${requiresControl ? ' · требуется контроль исполнения' : ''}`,
unresolved: (count: number): string =>
`Исполнитель не определён: ${count} ${pluralRu(count, ['шаг', 'шага', 'шагов'])} — причина указана в строке.`,
positionMissing: 'Должность не указана в карточке сотрудника.',
columnOrderNo: '№',
columnExecutor: 'Исполнитель',
columnPosition: 'Должность',
columnReassign: 'Переназначение',
reassignAllowed: 'Разрешено',
emptySteps: 'Маршрут разрешился без шагов для этого статуса.'
},
manageRequests: {
breadcrumb: 'Редактирование заявок',
subtitle: 'Правка маршрута согласования',
title: 'Редактирование заявок',
requestTypeLabel: 'Тип заявки',
requestTypePlaceholder: 'Выберите тип заявки',
requestTypeEmpty: 'Нет типов заявок с привязанным списком SharePoint.',
requestLabel: 'Заявка',
requestPlaceholder: 'Выберите заявку',
requestSearchPlaceholder: 'Поиск по названию...',
requestDisabledHint: 'Сначала выберите тип заявки.',
noApprovalList: 'У этого типа заявки список согласования не найден (Title списка данных не оканчивается на «— данные»).',
requestStatusLabel: 'Статус заявки',
fullyClosedNotice: 'Заявка полностью закрыта — маршрут доступен только для просмотра.',
approvalPhaseTitle: 'Согласование',
executionPhaseTitle: 'Исполнение',
columnOrder: 'Шаг',
columnApprover: 'Согласующий',
columnPosition: 'Должность',
columnStatus: 'Статус',
columnDate: 'Дата',
emptyPhase: 'Шагов нет.',
emptyMatrix: 'У этой заявки маршрут ещё не записан.',
reassign: 'Переназначить',
remove: 'Удалить шаг',
notify: 'Отправить уведомление',
notifyNotActive: 'Уведомить можно только текущий активный шаг (не «Ждёт очереди»)',
notifySuccess: 'Уведомление отправлено',
notifyError: 'Не удалось отправить уведомление',
addApproverPlaceholder: 'Добавить согласующего: начните вводить ФИО…',
addExecutorPlaceholder: 'Добавить исполнителя: начните вводить ФИО…',
lockedHint: 'Решение уже принято — недоступно для правки.',
ghostHint: 'Сохраняется…',
phaseClosedHint: 'Эта фаза уже завершена — добавить в неё новый шаг нельзя.',
requestRejectedHint: 'Заявка отклонена — добавить исполнителя нельзя.',
moveUp: 'Шаг выше',
moveDown: 'Шаг ниже',
mergeWithPrev: 'Объединить с предыдущим шагом (выполнять параллельно)',
splitFromPrev: 'Отделить от предыдущего шага (выполнять после него)',
moveBlockedByDecision: 'Нельзя переставить — соседний шаг уже решён.',
reassignTitle: 'Переназначить шаг',
columnSla: 'SLA',
slaInherited: 'от типа заявки',
slaHoursLabel: 'SLA, часов',
slaHoursHint:
'Через сколько часов простоя напоминать по этому шагу. Пусто — берётся SLA типа заявки; 0 — напоминать сразу.',
slaHoursPlaceholder: 'Как у типа заявки',
askOverdueReasonLabel: 'Спрашивать причину просрочки',
askOverdueReasonHint: '«Не задано» — берётся значение типа заявки.',
validationSlaHours: 'SLA укажите целым числом часов (0 и больше) или оставьте поле пустым.',
approverLabel: 'Сотрудник',
approverPlaceholder: 'Выберите сотрудника',
validationApprover: 'Выберите сотрудника.',
validationDuplicateApprover: 'Этот сотрудник уже стоит в этой фазе на другом шаге.',
deleteTitle: 'Удалить шаг?',
deleteText: (position: string): string => `Шаг «${position || 'без должности'}» будет удалён безвозвратно.`,
orderFloorHint: (order: number): string =>
`Шаг встанет на позицию ${order} — раньше уже принятых решений в этой фазе поставить нельзя.`,
loadError: 'Не удалось загрузить заявку',
saveError: 'Не удалось сохранить'
},
// Значения по умолчанию для переиспользуемых контролов выбора.
select: {
placeholder: 'Выберите значение',
searchPlaceholder: 'Поиск...',
nothingFound: 'Ничего не найдено',
notSelected: 'Не выбрано',
missingInDictionary: 'Нет в справочнике',
loadingDictionary: 'Загрузка справочника...',
searching: 'Поиск...',
employeeSearchPlaceholder: 'Поиск по ФИО или email...',
employeesEmpty: 'Сотрудники не найдены в справочнике'
}
};