feat: добавлено отображение статуса делеганта и того кто его назначил
This commit is contained in:
Generated
+4
-4
@@ -17,7 +17,7 @@
|
||||
"@microsoft/sp-webpart-base": "1.22.1",
|
||||
"@pnp/sp": "^4.20.0",
|
||||
"@pnp/spfx-controls-react": "^3.25.0",
|
||||
"@tnsu/ui-kit": "^0.3.0",
|
||||
"@tnsu/ui-kit": "^0.4.0",
|
||||
"react": "17.0.1",
|
||||
"react-dom": "17.0.1",
|
||||
"tslib": "2.3.1"
|
||||
@@ -9870,9 +9870,9 @@
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/@tnsu/ui-kit": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "http://git.tnsu.kz/api/packages/timur.gorodetskiy/npm/%40tnsu%2Fui-kit/-/0.3.0/ui-kit-0.3.0.tgz",
|
||||
"integrity": "sha512-V9pTS/rBmU9KInczI8jJ/KHT96hfX0OlnjIJ2NZ/iEQTOE93I9xMfmgpj83PSRrI6/xzyybu+8k/qVO6LP926w==",
|
||||
"version": "0.4.0",
|
||||
"resolved": "http://git.tnsu.kz/api/packages/timur.gorodetskiy/npm/%40tnsu%2Fui-kit/-/0.4.0/ui-kit-0.4.0.tgz",
|
||||
"integrity": "sha512-J3cBvoR2vV0SC5GihOagLAqvC0txQE342qIh6viabMt+Um6Jg4mhTff+ZkVahG0g+bF6sqSCUl3x813aO82KuQ==",
|
||||
"license": "ISC",
|
||||
"peerDependencies": {
|
||||
"@fluentui/react": "^8.106.4",
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@
|
||||
"@microsoft/sp-webpart-base": "1.22.1",
|
||||
"@pnp/sp": "^4.20.0",
|
||||
"@pnp/spfx-controls-react": "^3.25.0",
|
||||
"@tnsu/ui-kit": "^0.3.0",
|
||||
"@tnsu/ui-kit": "^0.4.0",
|
||||
"react": "17.0.1",
|
||||
"react-dom": "17.0.1",
|
||||
"tslib": "2.3.1"
|
||||
|
||||
@@ -66,9 +66,8 @@
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
// Цвет пунктов списка — те же цвета, что у StatusBadge (см. statusOptionClassName
|
||||
// в RequestsTableView.tsx). Только color: background-color нативный <option>
|
||||
// показывает не во всех браузерах одинаково, поэтому не используем.
|
||||
// Цвет пунктов списка — те же цвета, что у StatusBadge. Только color:
|
||||
// background-color нативный <option> показывает не во всех браузерах одинаково.
|
||||
.optionDraft { color: $gray-700; }
|
||||
.optionOnApproval { color: $blue-600; }
|
||||
.optionApproved { color: $green-600; }
|
||||
@@ -112,6 +111,8 @@
|
||||
background: $red-50;
|
||||
}
|
||||
|
||||
// Карточка-контейнер вокруг таблицы. Сама таблица (заголовки, строки, ховер,
|
||||
// горизонтальный скролл) теперь в @tnsu/ui-kit DataTable.
|
||||
.tableWrap {
|
||||
background: $white;
|
||||
border: 1px solid $gray-200;
|
||||
@@ -120,97 +121,7 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: $fs-sm;
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
padding: 12px 16px;
|
||||
background: $gray-50;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
color: $gray-500;
|
||||
font-size: $fs-label;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .04em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 14px 16px;
|
||||
border-bottom: 1px solid $gray-100;
|
||||
color: $gray-900;
|
||||
vertical-align: middle;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
tbody tr:last-child td {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Кликабельна вся строка (см. RequestsTableView — onClick на <tr>).
|
||||
.row {
|
||||
cursor: pointer;
|
||||
transition: background .16s;
|
||||
|
||||
&:hover {
|
||||
background: $gold-50;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid $gold-500;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
}
|
||||
|
||||
.colId {
|
||||
width: 70px;
|
||||
color: $gray-400;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.colType {
|
||||
width: 200px;
|
||||
color: $navy-700;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.colTitle {
|
||||
min-width: 240px;
|
||||
}
|
||||
|
||||
.colDate {
|
||||
width: 110px;
|
||||
color: $gray-500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.colAuthor {
|
||||
width: 280px;
|
||||
color: $gray-500;
|
||||
}
|
||||
|
||||
.colStatus {
|
||||
width: 190px;
|
||||
}
|
||||
|
||||
// Причина возврата — свободный текст согласующего, поэтому переносим по словам
|
||||
// (в т.ч. длинное слово без пробелов, иначе оно растянет таблицу).
|
||||
.colComment {
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
color: $gray-700;
|
||||
}
|
||||
|
||||
// Пустой комментарий в колонке «Причина возврата/отклонения».
|
||||
.commentEmpty {
|
||||
color: $gray-400;
|
||||
}
|
||||
|
||||
// Раскраска статуса — теперь только в shared/approval/ui/StatusBadge (см.
|
||||
// использование ниже с size="sm"). Раньше здесь была своя копия этих цветов —
|
||||
// она разошлась с эталоном (На согласовании/Возвращена на доработку оказались
|
||||
// перепутаны), поэтому копию убрали, а не подправили.
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
searchOwnRequests,
|
||||
} from './myRequests.service';
|
||||
import { IMyRequest } from './models/IMyRequest';
|
||||
import { DataTable, IDataTableColumn } from '@tnsu/ui-kit';
|
||||
import { REQUEST_STATUS, REQUEST_STATUS_CHOICES, IRequestType, StatusBadge } from '../../shared/approval';
|
||||
import { onRequestsChanged } from '../../shared/requestEvents';
|
||||
import { useRoutedQuery } from '../../shared/usePersistedQuery';
|
||||
@@ -201,60 +202,49 @@ const Table: React.FC<{
|
||||
statusFilter: string;
|
||||
onOpen: (request: IMyRequest) => void;
|
||||
}> = ({ rows, view, statusFilter, onOpen }) => {
|
||||
const columns = columnsFor(view, statusFilter);
|
||||
const cols = columnsFor(view, statusFilter);
|
||||
|
||||
// Колонки для DataTable строим динамически: базовые всегда, автор/статус/
|
||||
// комментарий — по columnsFor (зависит от раздела и фильтра). Кастомные ячейки
|
||||
// (статус → StatusBadge, дата → формат) задаются через render.
|
||||
const columns: IDataTableColumn<IMyRequest>[] = [
|
||||
{ key: 'id', title: 'ID', width: '70px' },
|
||||
{ key: 'type', title: 'Тип', width: '200px', render: (r) => r.requestTypeName },
|
||||
{ key: 'title', title: 'Название', render: (r) => r.title || `#${r.id}` },
|
||||
{ key: 'date', title: 'Дата', width: '110px', render: (r) => formatDate(r.created) }
|
||||
];
|
||||
if (cols.author) {
|
||||
columns.push({
|
||||
key: 'author',
|
||||
title: 'Автор',
|
||||
width: '280px',
|
||||
render: (r) => [r.authorName, r.authorEmail].filter(Boolean).join(' · ') || '—'
|
||||
});
|
||||
}
|
||||
if (cols.status) {
|
||||
columns.push({
|
||||
key: 'status',
|
||||
title: 'Статус',
|
||||
width: '190px',
|
||||
render: (r) => (r.status ? <StatusBadge status={r.status} size="sm" /> : '—')
|
||||
});
|
||||
}
|
||||
if (cols.commentLabel) {
|
||||
columns.push({
|
||||
key: 'comment',
|
||||
title: cols.commentLabel,
|
||||
render: (r) => r.decisionComment || <span className={styles.commentEmpty}>Без комментария</span>
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.tableWrap}>
|
||||
<table className={styles.table}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className={styles.colId}>ID</th>
|
||||
<th className={styles.colType}>Тип</th>
|
||||
<th className={styles.colTitle}>Название</th>
|
||||
<th className={styles.colDate}>Дата</th>
|
||||
{columns.author && <th className={styles.colAuthor}>Автор</th>}
|
||||
{columns.status && <th className={styles.colStatus}>Статус</th>}
|
||||
{columns.commentLabel && <th className={styles.colComment}>{columns.commentLabel}</th>}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((r) => (
|
||||
<tr
|
||||
className={styles.row}
|
||||
key={`${r.requestTypeId}-${r.id}`}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
aria-label={`Открыть заявку «${r.title || r.id}»`}
|
||||
onClick={() => onOpen(r)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
onOpen(r);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<td className={styles.colId}>{r.id}</td>
|
||||
<td className={styles.colType}>{r.requestTypeName}</td>
|
||||
<td className={styles.colTitle}>{r.title || `#${r.id}`}</td>
|
||||
<td className={styles.colDate}>{formatDate(r.created)}</td>
|
||||
{columns.author && (
|
||||
<td className={styles.colAuthor}>
|
||||
{[r.authorName, r.authorEmail].filter(Boolean).join(' · ') || '—'}
|
||||
</td>
|
||||
)}
|
||||
{columns.status && (
|
||||
<td className={styles.colStatus}>
|
||||
{r.status ? <StatusBadge status={r.status} size="sm" /> : '—'}
|
||||
</td>
|
||||
)}
|
||||
{columns.commentLabel && (
|
||||
<td className={styles.colComment}>
|
||||
{r.decisionComment || <span className={styles.commentEmpty}>Без комментария</span>}
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
rows={rows}
|
||||
rowKey={(r) => `${r.requestTypeId}-${r.id}`}
|
||||
onRowClick={onOpen}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -253,13 +253,15 @@ export class ApprovalListService {
|
||||
FIELDS.slaHours,
|
||||
FIELDS.askOverdueReason,
|
||||
`${FIELDS.approver}/Title`,
|
||||
`${FIELDS.approver}/EMail`
|
||||
`${FIELDS.approver}/EMail`,
|
||||
`${FIELDS.delegatedBy}/Title`
|
||||
)
|
||||
.expand(FIELDS.approver)
|
||||
.expand(FIELDS.approver, FIELDS.delegatedBy)
|
||||
.orderBy(FIELDS.order, true)()) as Array<Record<string, unknown>>;
|
||||
|
||||
return rows.map((r, i) => {
|
||||
const approver = r[FIELDS.approver] as { Title?: string; EMail?: string } | undefined;
|
||||
const delegatedBy = r[FIELDS.delegatedBy] as { Title?: string } | undefined;
|
||||
return {
|
||||
order: (r[FIELDS.order] as number) ?? i + 1,
|
||||
who: approver?.Title || '',
|
||||
@@ -269,6 +271,9 @@ export class ApprovalListService {
|
||||
date: this.formatDate((r[FIELDS.date] as string) || ''),
|
||||
comment: (r[FIELDS.comment] as string) || '',
|
||||
allowReassign: (r[FIELDS.allowReassign] as boolean) ?? false,
|
||||
// Пусто у строк из штатного маршрута; заполнено только у делегатов
|
||||
// (кого согласующий добавил перед собой) — матрица пометит их.
|
||||
addedBy: delegatedBy?.Title || '',
|
||||
phase: (r[FIELDS.phase] as string) || APPROVAL_PHASE.approval,
|
||||
// Читаем, чтобы не потерять при пересохранении: writeRoute удаляет
|
||||
// строки и создаёт их заново из матрицы, и без этих полей SLA,
|
||||
|
||||
@@ -8,6 +8,10 @@ export interface IMatrixRow {
|
||||
date: string; // дата решения (дд.мм.гггг) или ''
|
||||
comment: string;
|
||||
allowReassign: boolean; // может ли этот согласующий добавить делеганта
|
||||
// ФИО согласующего, который добавил ЭТУ строку перед собой (доп. согласующий,
|
||||
// добавленный другим согласующим с правом переназначения — DelegatedBy). ''
|
||||
// для обычных строк маршрута. Матрица показывает это пометкой под именем.
|
||||
addedBy?: string;
|
||||
phase?: string; // фаза строки: «Согласование» | «Исполнение» (см. APPROVAL_PHASE)
|
||||
// Шаг идёт ОДНОВРЕМЕННО с предыдущим (у обоих одинаковый order — так
|
||||
// activateNextQueued активирует их вместе). У первой строки всегда false.
|
||||
|
||||
@@ -141,6 +141,16 @@ export const ApprovalMatrix: React.FC<IApprovalMatrixProps> = ({
|
||||
)}
|
||||
<td data-label={roleLabel}>
|
||||
{r.who}
|
||||
{/* Доп. согласующий, добавленный другим согласующим (с правом
|
||||
переназначения) — помечаем, кто его добавил. Пусто у обычных
|
||||
строк маршрута. Как и комментарий — inline под именем, чтобы
|
||||
не ломать rowSpan колонки «Шаг» у параллельных шагов. */}
|
||||
{r.addedBy && (
|
||||
<span className={styles.addedByInline}>
|
||||
<span className={styles.addedByLabel}>Доп. согласующий, добавил:</span>{' '}
|
||||
{r.addedBy}
|
||||
</span>
|
||||
)}
|
||||
{/* Комментарий решения — мелким текстом под именем (не отдельной
|
||||
строкой: иначе ломается rowSpan колонки «Шаг» у параллельных). */}
|
||||
{r.comment && (
|
||||
|
||||
@@ -142,6 +142,20 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
// Пометка «доп. согласующий, добавил: …» — под именем согласующего. Золотой
|
||||
// акцент, чтобы отличать таких участников от штатных строк маршрута.
|
||||
.addedByInline {
|
||||
display: block;
|
||||
margin-top: 3px;
|
||||
font-size: $fs-caption;
|
||||
color: $gold-600;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.addedByLabel {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
// ВАЖНО: не display:flex — это сняло бы с ячейки display:table-cell, и она
|
||||
// перестала бы вставать в строку рядом с соседней (кнопки уезжали на вторую
|
||||
// строку). Ячейка остаётся табличной, кнопки держатся в строке через nowrap.
|
||||
@@ -287,7 +301,8 @@
|
||||
}
|
||||
|
||||
// Комментарий под именем — на всю ширину ячейки (своя строка).
|
||||
.commentInline {
|
||||
.commentInline,
|
||||
.addedByInline {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user