diff --git a/src/webparts/requestPortal/features/myRequests/RequestsTableView.tsx b/src/webparts/requestPortal/features/myRequests/RequestsTableView.tsx index 776fd37..16566f7 100644 --- a/src/webparts/requestPortal/features/myRequests/RequestsTableView.tsx +++ b/src/webparts/requestPortal/features/myRequests/RequestsTableView.tsx @@ -17,12 +17,14 @@ import { hydratePendingAuthors, searchOwnRequests, } from './myRequests.service'; +import { hydrateStartDates } from './startDates.service'; import { IMyRequest } from './models/IMyRequest'; import { DataTable, IDataTableColumn, IconButton } from '@tnsu/ui-kit'; import { REQUEST_STATUS, IRequestType, StatusBadge } from '../../shared/approval'; import { confirmAndDeleteDraft, isDeletableStatus } from '../../shared/draftDeletion'; import { friendlyErrorMessage } from '../../shared/diagnostics'; import { getFormComponent } from '../../forms/registry'; +import { FORM_ID as WORKPLACE_SETUP_FORM_ID } from '../../forms/WorkplaceSetup/model'; import { onRequestsChanged } from '../../shared/requestEvents'; import { perfScope } from '../../shared/perfTrace'; import { useRoutedQuery } from '../../shared/usePersistedQuery'; @@ -135,6 +137,8 @@ interface ITableColumns { status: boolean; /** Колонка «Исполнитель» — у кого заявка сейчас (см. showsAssignee). */ assignee: boolean; + /** Колонка «Дата выхода» — только «Все заявки» по организации рабочего места. */ + startDate?: boolean; /** Заголовок колонки с комментарием; undefined — колонки нет. */ commentLabel?: string; } @@ -157,7 +161,12 @@ function showsAssignee(view: ListView, statusFilter: string): boolean { return true; } -function columnsFor(view: ListView, statusFilter: string): ITableColumns { +/** Дата выхода сотрудника есть только у заявки на организацию рабочего места, и показываем её во «Все заявки». */ +function showsStartDate(view: ListView, type: IRequestType | undefined): boolean { + return view === 'all' && type?.pageUrl === WORKPLACE_SETUP_FORM_ID; +} + +function columnsFor(view: ListView, statusFilter: string, startDate = false): ITableColumns { const assignee = showsAssignee(view, statusFilter); const decisionStatus = decisionStatusFor(view, statusFilter); const commentLabel = @@ -171,7 +180,7 @@ function columnsFor(view: ListView, statusFilter: string): ITableColumns { if (view === 'rework') return { author: false, status: false, assignee, commentLabel }; if (view === 'own:created') return { author: false, status: !statusFilter, assignee, commentLabel }; // 'all' — заявки всех авторов вперемешку, поэтому нужны и автор, и статус. - if (view === 'all') return { author: true, status: true, assignee }; + if (view === 'all') return { author: true, status: true, assignee, startDate }; // История решений: заявка чужая, поэтому нужен автор. Статус — только если он // не предопределён самой вкладкой («Отклонённые мной» — это всегда «Отклонено», // а вот у согласованных/выполненных заявка могла уйти дальше по маршруту). @@ -235,6 +244,8 @@ const Table: React.FC<{ rows: IMyRequest[]; view: ListView; statusFilter: string; + /** Показывать колонку «Дата выхода» (см. showsStartDate). */ + withStartDate: boolean; sort: ISortState; onSort: (key: SortKey) => void; onOpen: (request: IMyRequest) => void; @@ -242,8 +253,8 @@ const Table: React.FC<{ onShowHistory: (request: IMyRequest) => void; /** Удалить черновик; задан только там, где в таблице бывают свои черновики. */ onDelete?: (request: IMyRequest) => void; -}> = ({ rows, view, statusFilter, sort, onSort, onOpen, onShowHistory, onDelete }) => { - const cols = columnsFor(view, statusFilter); +}> = ({ rows, view, statusFilter, withStartDate, sort, onSort, onOpen, onShowHistory, onDelete }) => { + const cols = columnsFor(view, statusFilter, withStartDate); const sortable = (label: string, key: SortKey): React.ReactNode => ( ); @@ -261,6 +272,14 @@ const Table: React.FC<{ { key: 'title', title: 'Название', render: (r) => r.title || r.code || `#${r.id}` }, { key: 'date', title: sortable('Дата', 'date'), width: '110px', render: (r) => formatDate(r.created) } ]; + if (cols.startDate) { + columns.push({ + key: 'startDate', + title: 'Дата выхода', + width: '120px', + render: (r) => formatDate(r.startDate ?? '') || '—' + }); + } if (cols.author) { columns.push({ key: 'author', @@ -588,6 +607,7 @@ export const RequestsTableView: React.FC = ({ await Promise.all([ decisionStatus ? hydrateDecisionComments(context, types, result.rows, decisionStatus) : undefined, showsAssignee(view, statusFilter) ? hydrateCurrentAssignees(context, types, result.rows) : undefined, + showsStartDate(view, allRequestsType) ? hydrateStartDates(context, result.rows) : undefined, ]); return result; }; @@ -745,6 +765,7 @@ export const RequestsTableView: React.FC = ({ rows={visible} view={view} statusFilter={statusFilter} + withStartDate={showsStartDate(view, allRequestsType)} sort={sort} onSort={refinement.onSort} onOpen={onOpen} diff --git a/src/webparts/requestPortal/features/myRequests/models/IMyRequest.ts b/src/webparts/requestPortal/features/myRequests/models/IMyRequest.ts index 80bef3e..38a740b 100644 --- a/src/webparts/requestPortal/features/myRequests/models/IMyRequest.ts +++ b/src/webparts/requestPortal/features/myRequests/models/IMyRequest.ts @@ -76,4 +76,12 @@ export interface IMyRequest { * hydrateCurrentAssignees). */ currentAssignees?: IAssignee[]; + /** + * Дата выхода сотрудника на работу (колонка StartDate) — только у заявок на + * организацию рабочего места, колонка «Дата выхода» во «Все заявки». Как и + * decisionComment, живёт в списке данных и дочитывается только для видимой + * страницы (см. hydrateStartDates). ISO-строка; '' — не заполнена, + * undefined — не дочитывалось. + */ + startDate?: string; } diff --git a/src/webparts/requestPortal/features/myRequests/pageCache.ts b/src/webparts/requestPortal/features/myRequests/pageCache.ts index f3f6da8..37285dd 100644 --- a/src/webparts/requestPortal/features/myRequests/pageCache.ts +++ b/src/webparts/requestPortal/features/myRequests/pageCache.ts @@ -53,6 +53,7 @@ export function pageSignature(page: IPage): string { r.created, r.authorName, r.decisionComment ?? '', + r.startDate ?? '', (r.currentAssignees ?? []).map((a) => `${a.name}/${a.email}`).join(','), ].join('~') ) diff --git a/src/webparts/requestPortal/features/myRequests/startDates.service.ts b/src/webparts/requestPortal/features/myRequests/startDates.service.ts new file mode 100644 index 0000000..dfb6749 --- /dev/null +++ b/src/webparts/requestPortal/features/myRequests/startDates.service.ts @@ -0,0 +1,55 @@ +import { WebPartContext } from '@microsoft/sp-webpart-base'; +import '@pnp/sp/webs'; +import '@pnp/sp/lists'; +import '@pnp/sp/items'; +import { spForSite } from '../../shared/pnp/pnpjs'; +import { traced } from '../../shared/perfTrace'; +import { IMyRequest } from './models/IMyRequest'; + +/** + * Проставляет startDate — дату выхода сотрудника (колонка StartDate списка + * данных). Только для видимой страницы; группировка по партиции строки, как в + * hydrateAuthors: в другой партиции тот же Id — чужая заявка. Мутирует строки. + */ +export async function hydrateStartDates(context: WebPartContext, rows: IMyRequest[]): Promise { + if (rows.length === 0) return; + return traced('дата выхода (REST)', () => hydrateStartDatesInner(context, rows)); +} + +async function hydrateStartDatesInner(context: WebPartContext, rows: IMyRequest[]): Promise { + const byPartition = new Map(); + rows.forEach((r) => { + const bucket = byPartition.get(r.listId.toLowerCase()) || { + siteUrl: r.siteUrl, + listId: r.listId, + typeName: r.requestTypeName, + ids: [], + }; + bucket.ids.push(r.id); + byPartition.set(r.listId.toLowerCase(), bucket); + }); + + const byKey = new Map(); + await Promise.all( + Array.from(byPartition.values()).map(async ({ siteUrl, listId, typeName, ids }) => { + try { + const sp = spForSite(siteUrl, context); + const dataRows = (await sp.web + .getList(listId) + .items.filter(ids.map((id) => `Id eq ${id}`).join(' or ')) + .select('Id', 'StartDate') + .top(ids.length)()) as Array>; + dataRows.forEach((r) => byKey.set(`${listId.toLowerCase()}-${r.Id as number}`, (r.StartDate as string) || '')); + } catch (e) { + console.error( + `[Все заявки] Не удалось дочитать дату выхода для «${typeName}» (${listId}):`, + (e as Error).message + ); + } + }) + ); + + rows.forEach((r) => { + r.startDate = byKey.get(`${r.listId.toLowerCase()}-${r.id}`) ?? ''; + }); +}