Редактирование заявок у конкретного пользователя (#14)
Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz> Reviewed-on: http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/pulls/14
This commit was merged in pull request #14.
This commit is contained in:
@@ -15,6 +15,7 @@ import {
|
||||
} from '../../models';
|
||||
import { texts } from '../../texts';
|
||||
import { useRequestTypes } from '../../hooks/useRequestTypes';
|
||||
import { useRequestTypeListPartitions } from '../../hooks/useRequestTypeListPartitions';
|
||||
import { usePortalEmployees } from '../../hooks/usePortalEmployees';
|
||||
import { useLocalStorageState } from '../../hooks/useLocalStorageState';
|
||||
import { findEmployeeByEmail } from '../../utils/employee';
|
||||
@@ -47,6 +48,14 @@ export interface IManageRequestsScreenProps {
|
||||
route?: IManageRequestRouteParams;
|
||||
}
|
||||
|
||||
// Подпись заявки в комбобоксе — уникальный код (RequestUniqueCode, тот же, что
|
||||
// видит заявитель на портале), а не служебный числовой Id списка SharePoint.
|
||||
// Запасной вариант на #id — для заявок без заполненного кода (старые заявки,
|
||||
// либо список данных этой колонки не заводит).
|
||||
function requestLabel(r: Pick<ManagedRequestSummary, 'id' | 'title' | 'uniqueCode'>): string {
|
||||
return `${r.uniqueCode || `#${r.id}`} ${r.title}`;
|
||||
}
|
||||
|
||||
// Цвет плашки статуса — те же группы смысла, что и в request-portal
|
||||
// (StatusBadge.tsx): решено положительно/отрицательно/ожидает/черновик.
|
||||
function statusVariant(status: string): BadgeVariant {
|
||||
@@ -158,7 +167,19 @@ export const ManageRequestsScreen: React.FC<IManageRequestsScreenProps> = ({ api
|
||||
if (!route) return;
|
||||
setSelection({
|
||||
requestTypeId: route.requestTypeId,
|
||||
selectedRequest: { id: Number(route.requestId), title: '', status: '', created: '', authorName: '' }
|
||||
// listId из ссылки — какая партиция, если ссылку скопировали уже после
|
||||
// этого фикса; пусто у старых ссылок — тогда reloadMatrix ниже
|
||||
// подставит текущую партицию (тот же эффект, что был раньше, но теперь
|
||||
// явный, а не единственно возможный).
|
||||
selectedRequest: {
|
||||
id: Number(route.requestId),
|
||||
title: '',
|
||||
status: '',
|
||||
created: '',
|
||||
authorName: '',
|
||||
uniqueCode: null,
|
||||
listId: route.listId
|
||||
}
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
@@ -167,11 +188,15 @@ export const ManageRequestsScreen: React.FC<IManageRequestsScreenProps> = ({ api
|
||||
// прислать коллеге. replaceRoute, а не navigate: смена заявки — это правка
|
||||
// состояния текущего экрана, а не переход на новую страницу с отдельным
|
||||
// шагом в истории браузера. title/status заявки в URL не пишем — их выводит
|
||||
// сам requestId, хранить производные данные в ссылке незачем.
|
||||
// сам requestId, хранить производные данные в ссылке незачем. listId —
|
||||
// пишем: без него по ссылке нельзя однозначно понять, в какой партиции
|
||||
// искать requestId (см. IManageRequestRouteParams).
|
||||
React.useEffect(() => {
|
||||
replaceRoute({
|
||||
tab: 'manageRequests',
|
||||
manage: selectedRequest ? { requestTypeId, requestId: String(selectedRequest.id) } : undefined
|
||||
manage: selectedRequest
|
||||
? { requestTypeId, requestId: String(selectedRequest.id), listId: selectedRequest.listId }
|
||||
: undefined
|
||||
});
|
||||
}, [requestTypeId, selectedRequest]);
|
||||
const [matrix, setMatrix] = React.useState<ManagedRequestMatrix | undefined>();
|
||||
@@ -192,6 +217,11 @@ export const ManageRequestsScreen: React.FC<IManageRequestsScreenProps> = ({ api
|
||||
// и читать заявки этого типа отсюда просто негде.
|
||||
const manageableRequestTypes = requestTypes.items.filter(rt => rt.listId && rt.siteUrl);
|
||||
|
||||
// Вся серия партиций выбранного типа (архив + текущая) — заявки этого типа
|
||||
// физически лежат в любой из них (см. ТЗ по архивации), искать и открывать
|
||||
// нужно по всем сразу, не только по текущей.
|
||||
const partitions = useRequestTypeListPartitions(api, requestType?.id);
|
||||
|
||||
const selectRequestType = (id: string): void => {
|
||||
// Заявка выбрана для ДРУГОГО типа — сбрасываем, иначе комбобокс продолжит
|
||||
// показывать заявку не того типа, что выбран сейчас.
|
||||
@@ -205,10 +235,12 @@ export const ManageRequestsScreen: React.FC<IManageRequestsScreenProps> = ({ api
|
||||
|
||||
const searchRequests = React.useCallback(
|
||||
(query: string, page: number) => {
|
||||
if (!requestType) return Promise.resolve({ items: [], hasMore: false });
|
||||
return requestManage.searchRequests(requestType, query, page);
|
||||
if (!requestType || !requestType.siteUrl || partitions.items.length === 0) {
|
||||
return Promise.resolve({ items: [], hasMore: false });
|
||||
}
|
||||
return requestManage.searchRequests(partitions.items, requestType.siteUrl, query, page);
|
||||
},
|
||||
[requestManage, requestType]
|
||||
[requestManage, requestType, partitions.items]
|
||||
);
|
||||
|
||||
// silent=true — фоновое обновление (после своего же действия, автообновление
|
||||
@@ -219,18 +251,38 @@ export const ManageRequestsScreen: React.FC<IManageRequestsScreenProps> = ({ api
|
||||
const reloadMatrix = React.useCallback(
|
||||
(silent = false) => {
|
||||
if (!requestType || !selectedRequest) return;
|
||||
// Партиции ещё не подтянулись (первый рендер после смены типа/захода по
|
||||
// ссылке) — без них нельзя даже подставить запасной вариант (текущая
|
||||
// партиция) для ссылок без listId, ждём следующего рендера.
|
||||
if (partitions.isLoading) return;
|
||||
if (!requestType.siteUrl) {
|
||||
setMatrixError('У типа заявки не указан список SharePoint.');
|
||||
return;
|
||||
}
|
||||
// listId заявки уже известен (выбрана из комбобокса, либо ссылка после
|
||||
// этого фикса) — используем его. Иначе (старая ссылка без listId) —
|
||||
// единственный разумный запасной вариант: текущая партиция, тот же
|
||||
// список, что искался бы и раньше.
|
||||
const listId = selectedRequest.listId || partitions.items.filter(p => p.isCurrent)[0]?.listId;
|
||||
if (!listId) {
|
||||
setMatrixError('Для типа заявки не найдено ни одной партиции списка SharePoint.');
|
||||
return;
|
||||
}
|
||||
if (!silent) setMatrixLoading(true);
|
||||
setMatrixError(undefined);
|
||||
requestManage
|
||||
.getMatrix(requestType, selectedRequest.id)
|
||||
.getMatrix(requestType.siteUrl, listId, selectedRequest.id)
|
||||
.then(m => {
|
||||
setMatrix(m);
|
||||
// title/status в комбобоксе (а значит, и в localStorage) держим
|
||||
// title/status/listId в комбобоксе (а значит, и в localStorage) держим
|
||||
// синхронными с сервером — особенно важно после захода по глубокой
|
||||
// ссылке, где изначально они пустые (см. эффект-затравку выше).
|
||||
// ссылке, где изначально они пустые (см. эффект-затравку выше), и
|
||||
// после подстановки запасной (текущей) партиции выше: как только
|
||||
// реальная партиция заявки известна, оседает в выборе — следующий
|
||||
// reloadMatrix пойдёт уже по ней напрямую.
|
||||
//
|
||||
// ВАЖНО: возвращаем ТОТ ЖЕ prev (без изменений), если title/status не
|
||||
// отличаются — m.request это каждый раз НОВЫЙ объект (свежий парсинг
|
||||
// ВАЖНО: возвращаем ТОТ ЖЕ prev (без изменений), если ничего не
|
||||
// отличается — m.request это каждый раз НОВЫЙ объект (свежий парсинг
|
||||
// ответа), и если писать его в состояние безусловно, selectedRequest
|
||||
// получает новую ссылку на КАЖДОЙ загрузке, даже когда содержимое не
|
||||
// поменялось. Это меняет зависимости reloadMatrix → пересоздаёт саму
|
||||
@@ -240,7 +292,7 @@ export const ManageRequestsScreen: React.FC<IManageRequestsScreenProps> = ({ api
|
||||
setSelection(prev => {
|
||||
const sel = prev.selectedRequest;
|
||||
if (!sel || sel.id !== m.request.id) return prev;
|
||||
const same = sel.title === m.request.title && sel.status === m.request.status;
|
||||
const same = sel.title === m.request.title && sel.status === m.request.status && sel.listId === m.request.listId;
|
||||
return same ? prev : { ...prev, selectedRequest: m.request };
|
||||
});
|
||||
})
|
||||
@@ -249,7 +301,7 @@ export const ManageRequestsScreen: React.FC<IManageRequestsScreenProps> = ({ api
|
||||
if (!silent) setMatrixLoading(false);
|
||||
});
|
||||
},
|
||||
[requestManage, requestType, selectedRequest]
|
||||
[requestManage, requestType, selectedRequest, partitions.items, partitions.isLoading]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -533,14 +585,14 @@ export const ManageRequestsScreen: React.FC<IManageRequestsScreenProps> = ({ api
|
||||
<AsyncSearchableSelect<ManagedRequestSummary>
|
||||
key={requestTypeId || 'none'}
|
||||
value={selectedRequest ? String(selectedRequest.id) : ''}
|
||||
selectedLabel={selectedRequest ? `#${selectedRequest.id} ${selectedRequest.title}` : undefined}
|
||||
selectedLabel={selectedRequest ? requestLabel(selectedRequest) : undefined}
|
||||
selectedSublabel={selectedRequest?.status}
|
||||
disabled={!requestType}
|
||||
placeholder={texts.manageRequests.requestPlaceholder}
|
||||
searchPlaceholder={texts.manageRequests.requestSearchPlaceholder}
|
||||
search={searchRequests}
|
||||
getValue={r => String(r.id)}
|
||||
getLabel={r => `#${r.id} ${r.title}`}
|
||||
getLabel={requestLabel}
|
||||
getSublabel={r => r.status}
|
||||
onChange={(_, item) => selectRequest(item)}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user