feat: история согласования по кнопке в строке таблиц заявок
В каждой строке таблиц заявок — кнопка «История согласования»: окно с той же лентой, что в карточке, без открытия самой заявки. Колонка действий теперь есть всегда (история у всех строк, корзина — у своих черновиков). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
import * as React from 'react';
|
||||
import { Alert, Dialog } from '@tnsu/ui-kit';
|
||||
import { ApprovalHistoryFeed, ApprovalListService, approvalServiceFor, HISTORY_TEXT } from '../../shared/approval';
|
||||
import { friendlyErrorMessage } from '../../shared/diagnostics';
|
||||
import { IMyRequest } from './models/IMyRequest';
|
||||
|
||||
interface Props {
|
||||
/** Заявка, чью историю показываем; undefined — окно закрыто. */
|
||||
request: IMyRequest | undefined;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Список согласования ИМЕННО той партиции, где лежит заявка (listId строки), —
|
||||
* как и остальные дочитывания в таблице. Если партицию не удалось сопоставить
|
||||
* с формой, approvalServiceFor бросает — показываем ошибку в окне, а не роняем таблицу.
|
||||
*/
|
||||
function approvalFor(request: IMyRequest): { approval?: ApprovalListService; error?: string } {
|
||||
try {
|
||||
return { approval: approvalServiceFor(request.formKey, request.listId) };
|
||||
} catch (e) {
|
||||
return { error: friendlyErrorMessage(e) };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* «История согласования» заявки прямо из таблицы — по кнопке в строке, без
|
||||
* открытия самой заявки. Та же лента, что в карточке (ApprovalHistoryFeed).
|
||||
*/
|
||||
export const RequestHistoryDialog: React.FC<Props> = ({ request, onClose }) => {
|
||||
const target = React.useMemo(() => (request ? approvalFor(request) : {}), [request]);
|
||||
const number = request ? request.code || `#${request.id}` : '';
|
||||
|
||||
return (
|
||||
<Dialog open={!!request} title={`${HISTORY_TEXT.title} · №${number}`} onDismiss={onClose} width="md">
|
||||
{request && target.error && <Alert variant="err">{target.error}</Alert>}
|
||||
{request && target.approval && (
|
||||
<ApprovalHistoryFeed approval={target.approval} dataListRef={request.listId} requestId={request.id} />
|
||||
)}
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@@ -79,6 +79,13 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// Кнопки действий строки (история, корзина) — в ряд, прижаты вправо.
|
||||
.rowActions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: $sp-1;
|
||||
}
|
||||
|
||||
// Пустой комментарий в колонке «Причина возврата/отклонения».
|
||||
.commentEmpty {
|
||||
color: $gray-400;
|
||||
|
||||
@@ -29,6 +29,7 @@ import { useRoutedQuery } from '../../shared/usePersistedQuery';
|
||||
import { IPage, pageKey, pageSignature, readPage, writePage } from './pageCache';
|
||||
import { Pager } from './Pager';
|
||||
import { RequestsFilterBar } from './RequestsFilterBar';
|
||||
import { RequestHistoryDialog } from './RequestHistoryDialog';
|
||||
import { SortableHeader } from './SortableHeader';
|
||||
import { filterRows, hasActiveFilters, ISortState, SortKey, sortRows } from './requestsTableFilters';
|
||||
import { useTableRefinement } from './useTableRefinement';
|
||||
@@ -237,9 +238,11 @@ const Table: React.FC<{
|
||||
sort: ISortState;
|
||||
onSort: (key: SortKey) => void;
|
||||
onOpen: (request: IMyRequest) => void;
|
||||
/** Показать историю согласования заявки (окно, без открытия заявки). */
|
||||
onShowHistory: (request: IMyRequest) => void;
|
||||
/** Удалить черновик; задан только там, где в таблице бывают свои черновики. */
|
||||
onDelete?: (request: IMyRequest) => void;
|
||||
}> = ({ rows, view, statusFilter, sort, onSort, onOpen, onDelete }) => {
|
||||
}> = ({ rows, view, statusFilter, sort, onSort, onOpen, onShowHistory, onDelete }) => {
|
||||
const cols = columnsFor(view, statusFilter);
|
||||
const sortable = (label: string, key: SortKey): React.ReactNode => (
|
||||
<SortableHeader label={label} sortKey={key} sort={sort} onSort={onSort} />
|
||||
@@ -297,27 +300,41 @@ const Table: React.FC<{
|
||||
render: (r) => r.decisionComment || <span className={styles.commentEmpty}>Без комментария</span>
|
||||
});
|
||||
}
|
||||
if (onDelete && rows.some(canDeleteRow)) {
|
||||
columns.push({
|
||||
key: 'actions',
|
||||
title: '',
|
||||
width: '56px',
|
||||
render: (r) =>
|
||||
canDeleteRow(r) ? (
|
||||
// Действия строки: история согласования — у всех, корзина — только у своих
|
||||
// черновиков. Клик по всей строке открывает заявку — кнопкам это не нужно,
|
||||
// поэтому stopPropagation.
|
||||
const hasDeletable = !!onDelete && rows.some(canDeleteRow);
|
||||
columns.push({
|
||||
key: 'actions',
|
||||
title: '',
|
||||
// Место под одну или две иконки-кнопки (sm) с зазором.
|
||||
width: hasDeletable ? '92px' : '56px',
|
||||
render: (r) => (
|
||||
<div className={styles.rowActions}>
|
||||
<IconButton
|
||||
icon="calendar"
|
||||
title="История согласования"
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onShowHistory(r);
|
||||
}}
|
||||
/>
|
||||
{onDelete && canDeleteRow(r) && (
|
||||
<IconButton
|
||||
icon="trash"
|
||||
title="Удалить черновик"
|
||||
danger
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
// Клик по всей строке открывает заявку — кнопке это не нужно.
|
||||
e.stopPropagation();
|
||||
onDelete(r);
|
||||
}}
|
||||
/>
|
||||
) : null
|
||||
});
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={styles.tableWrap}>
|
||||
@@ -379,6 +396,8 @@ export const RequestsTableView: React.FC<IRequestsTableViewProps> = ({
|
||||
const [totalRows, setTotalRows] = React.useState<number>(0);
|
||||
const [error, setError] = React.useState<string>('');
|
||||
const [deleteError, setDeleteError] = React.useState<string>('');
|
||||
// Заявка, чья история согласования открыта в окне; undefined — окно закрыто.
|
||||
const [historyFor, setHistoryFor] = React.useState<IMyRequest | undefined>(undefined);
|
||||
|
||||
// Список перечитается сам: confirmAndDeleteDraft шлёт rp-requests-changed.
|
||||
const handleDelete = (request: IMyRequest): void => {
|
||||
@@ -729,11 +748,13 @@ export const RequestsTableView: React.FC<IRequestsTableViewProps> = ({
|
||||
sort={sort}
|
||||
onSort={refinement.onSort}
|
||||
onOpen={onOpen}
|
||||
onShowHistory={setHistoryFor}
|
||||
onDelete={view === 'own:created' ? handleDelete : undefined}
|
||||
/>
|
||||
<Pager page={page} totalPages={totalPages} onChange={onPageChange} />
|
||||
</>
|
||||
)}
|
||||
<RequestHistoryDialog request={historyFor} onClose={() => setHistoryFor(undefined)} />
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user