fix(1539): единицы измерения в сводной ОСВ, полоса прокрутки
This commit is contained in:
parent
723c84cc72
commit
71ef6333b5
@ -1,6 +1,6 @@
|
||||
import { DownloadOutlined, FilterOutlined } from '@ant-design/icons';
|
||||
import { Alert, Button, Space, Tooltip, message } from 'antd';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Alert, Button, InputNumber, Select, Space, Tooltip, message } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { ApiError } from '../api/client';
|
||||
import PageTitle from '../components/PageTitle';
|
||||
@ -13,7 +13,7 @@ import type { ConsRussianStatus } from '../types/consolidations/common_types';
|
||||
import type { VersionCard } from '../types/consolidations/version_members';
|
||||
import { exportFilename, saveBlob } from '../utils/download';
|
||||
import { quarterShortLabel } from '../utils/labels';
|
||||
import { statusColorMapping, statusMapping } from '../widgets/ConsoleTable/consts';
|
||||
import { consStatusDescriptionMapping, statusColorMapping, statusMapping } from '../widgets/ConsoleTable/consts';
|
||||
import { CounterpartyMapModal } from '../widgets/ConsolidatedOsv/CounterpartyMapModal';
|
||||
import { OsvGaps } from '../widgets/ConsolidatedOsv/OsvGaps';
|
||||
import { OsvTable } from '../widgets/ConsolidatedOsv/OsvTable';
|
||||
@ -22,6 +22,7 @@ import { mapEntryId } from '../widgets/ConsolidatedOsv/counterpartyMapping';
|
||||
import { OSV_PAGE_SIZE, useConsolidatedOsv } from '../widgets/ConsolidatedOsv/useConsolidatedOsv';
|
||||
import { useCurrentUser } from '../auth/MeContext';
|
||||
import ChangeStatusModal from '../widgets/StatusChangeModal/ChangeStatusModal';
|
||||
import { UNITS, useNumberDisplay } from '../widgets/ConsolidatedOsv/useNumberDisplay';
|
||||
|
||||
function OsvContent({ versionId }: { versionId: string }) {
|
||||
const [card, setCard] = useState<VersionCard | null>(null);
|
||||
@ -31,6 +32,7 @@ function OsvContent({ versionId }: { versionId: string }) {
|
||||
const [mapping, setMapping] = useState<ConsolidatedRowOut | null>(null);
|
||||
const osv = useConsolidatedOsv(versionId);
|
||||
const currentUser = useCurrentUser();
|
||||
const { unit, setUnit, precision, setPrecision, format } = useNumberDisplay();
|
||||
|
||||
useEffect(() => {
|
||||
let actual = true;
|
||||
@ -62,6 +64,7 @@ function OsvContent({ versionId }: { versionId: string }) {
|
||||
const unmapped = s?.unmapped_total ?? 0;
|
||||
const onlyUnmapped = osv.filters.unmapped === true;
|
||||
const russianStatus = card ? (statusMapping[card.status] ?? card.status) : '';
|
||||
const description = card ? (consStatusDescriptionMapping[card.status] ?? russianStatus) : '';
|
||||
const preset = statusColorMapping[russianStatus as ConsRussianStatus] ?? TAG_PRESETS.default;
|
||||
const groupName = card?.company_group.name ?? '';
|
||||
const period = card ? `${card.year} · ${quarterShortLabel(card.quarter)}` : '';
|
||||
@ -71,7 +74,7 @@ function OsvContent({ versionId }: { versionId: string }) {
|
||||
backable
|
||||
location={['Отчётность', groupName, period]}
|
||||
header={card ? `${groupName} — ${card.year} ${quarterShortLabel(card.quarter)}` : 'Сводная ОСВ'}
|
||||
description={russianStatus}
|
||||
description={description}
|
||||
extra={
|
||||
card && (
|
||||
<Space size={8} style={{ display: 'flex', gap: 4 }}>
|
||||
@ -121,7 +124,23 @@ function OsvContent({ versionId }: { versionId: string }) {
|
||||
flexDirection: 'column',
|
||||
gap: 12,
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||
<div style={{ display: 'flex', gap: 8 }}>
|
||||
<div>
|
||||
<div style={{ fontSize: 10, color: '#667085' }}>Единицы</div>
|
||||
<Select variant='underlined' value={unit} onChange={setUnit} options={UNITS} style={{ width: 140 }} />
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ fontSize: 10, color: '#667085' }}>Знаков после запятой</div>
|
||||
<Select
|
||||
variant='underlined'
|
||||
options={[0, 1, 2, 3].map((i) => ({ label: i, value: i }))}
|
||||
value={precision}
|
||||
onChange={(v) => setPrecision(v ?? 2)}
|
||||
style={{ width: 90 }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Button icon={<DownloadOutlined />} loading={exporting} onClick={download}>
|
||||
Экспорт
|
||||
</Button>
|
||||
@ -163,6 +182,7 @@ function OsvContent({ versionId }: { versionId: string }) {
|
||||
filters={osv.filters}
|
||||
members={(card?.members ?? []).map((m) => ({ id: m.id, name: m.name }))}
|
||||
onPage={osv.setPage}
|
||||
numFormat={format}
|
||||
onFilter={osv.patchFilters}
|
||||
onMap={setMapping}
|
||||
/>
|
||||
|
||||
@ -10,7 +10,7 @@ import { Avatar, type TableColumnsType } from 'antd';
|
||||
import type { ReactNode } from 'react';
|
||||
import { type Preset, TAG_PRESETS } from '../../components/TableTag/presets';
|
||||
import type { ChecklistBackendShape, FileStatus } from '../../types/consolidations/version_members';
|
||||
import { statusMapping } from './consts';
|
||||
import { fileStatusMapping } from './consts';
|
||||
|
||||
/** Строка таблицы чек-листа: ЮЛ и статусы ББ, ОФР и ОСВ по счетам. */
|
||||
export type ChecklistTableRow = {
|
||||
@ -60,7 +60,7 @@ export function getMarksItems() {
|
||||
fontSize: 11,
|
||||
}}>
|
||||
<b>Условные обозначения</b>
|
||||
{(Object.entries(statusMapping) as [FileStatus, string][]).map(([status, label]) => (
|
||||
{(Object.entries(fileStatusMapping) as [FileStatus, string][]).map(([status, label]) => (
|
||||
<div key={status}>
|
||||
{mapStatus(status)} <span style={{ color: '#667085' }}>{label}</span>
|
||||
</div>
|
||||
|
||||
@ -2,13 +2,12 @@ import { FileTextOutlined } from '@ant-design/icons';
|
||||
import { Button } from 'antd';
|
||||
import type { TableConfig } from '../../components/GenericTable';
|
||||
import { TableTag } from '../../components/TableTag/TableTag';
|
||||
import { TAG_PRESETS } from '../../components/TableTag/presets';
|
||||
import type { FileRussianStatus, MemberRow } from '../../types/consolidations/version_members';
|
||||
import { statusColorMapping } from './consts';
|
||||
import { fileStatusColorMapping } from './consts';
|
||||
|
||||
function StatusTag({ status }: { status: FileRussianStatus | undefined }) {
|
||||
if (!status) return null;
|
||||
return <TableTag {...statusColorMapping[status]} text={status} />;
|
||||
return <TableTag {...fileStatusColorMapping[status]} text={status} />;
|
||||
}
|
||||
|
||||
export const membersConfig: TableConfig<MemberRow> = {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { type Preset, TAG_PRESETS } from '../../components/TableTag/presets';
|
||||
import type { FileRussianStatus, FileStatus } from '../../types/consolidations/version_members';
|
||||
|
||||
export const statusMapping: Record<FileStatus, FileRussianStatus> = {
|
||||
export const fileStatusMapping: Record<FileStatus, FileRussianStatus> = {
|
||||
CONFIRMED: 'Загружен',
|
||||
ERROR: 'Ошибка',
|
||||
MISSING: 'Отсутствует',
|
||||
@ -10,7 +10,7 @@ export const statusMapping: Record<FileStatus, FileRussianStatus> = {
|
||||
RECOGNIZED: 'Распознан',
|
||||
};
|
||||
|
||||
export const statusColorMapping: Record<FileRussianStatus, Preset> = {
|
||||
export const fileStatusColorMapping: Record<FileRussianStatus, Preset> = {
|
||||
'В обработке': TAG_PRESETS.geekblue,
|
||||
Загружен: TAG_PRESETS.green,
|
||||
Отсутствует: TAG_PRESETS.default,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { FileRussianStatus, IVersionCard, MemberRow } from '../../types/consolidations/version_members';
|
||||
import { statusMapping } from './consts';
|
||||
import { fileStatusMapping } from './consts';
|
||||
|
||||
export function normalizeData(cons_data: IVersionCard | null): MemberRow[] {
|
||||
if (cons_data === null) {
|
||||
@ -10,9 +10,9 @@ export function normalizeData(cons_data: IVersionCard | null): MemberRow[] {
|
||||
let ofr_status: FileRussianStatus | undefined;
|
||||
for (const c of m.checklist) {
|
||||
if (c.document_type === 'BALANCE') {
|
||||
bb_status = statusMapping[c.status];
|
||||
bb_status = fileStatusMapping[c.status];
|
||||
} else if (c.document_type === 'INCOME') {
|
||||
ofr_status = statusMapping[c.status];
|
||||
ofr_status = fileStatusMapping[c.status];
|
||||
}
|
||||
}
|
||||
return {
|
||||
|
||||
@ -208,7 +208,14 @@ export function ConsoleTableWidget() {
|
||||
|
||||
const onRow: TableProps<ConsRow>['onRow'] = (row) => ({
|
||||
// выбранная версия живёт в URL (?version=): переживает уход на «К отчётности» и «Назад»
|
||||
onClick: () => setSearchParams({ version: row.id }, { replace: true }),
|
||||
onClick: () =>
|
||||
setSearchParams(
|
||||
(searchParams) => {
|
||||
searchParams.set('version', row.id);
|
||||
return searchParams;
|
||||
},
|
||||
{ replace: true },
|
||||
),
|
||||
// выбранная версия подсвечена, как в макете [34]
|
||||
style: row.id === searchParams.get('version') ? { background: '#f0f7ee' } : undefined,
|
||||
});
|
||||
|
||||
@ -30,3 +30,12 @@ export const statusColorMapping: Record<ConsRussianStatus, Preset> = {
|
||||
Отклонён: TAG_PRESETS.red,
|
||||
Отменён: TAG_PRESETS.red,
|
||||
};
|
||||
|
||||
export const consStatusDescriptionMapping: Partial<Record<ConsStatus, string>> = {
|
||||
CONSOLIDATION_RF: 'Формирование и проверка КФО, внесение корректировок на уровне РФ',
|
||||
CONSOLIDATION_GO: 'Формирование и проверка КФО, внесение корректировок на уровне ГО',
|
||||
VERIFICATION_GO: 'Проверка и подтверждение КФО на уровне ГО',
|
||||
VERIFICATION_RF: 'Проверка и подтверждение КФО на уровне РФ',
|
||||
CANCELLED: 'Формирование КФО отменено',
|
||||
VERIFIED: 'КФО верифицирована, внесены номер и дата решения УОБ',
|
||||
};
|
||||
|
||||
@ -1,24 +1,23 @@
|
||||
import { FilterFilled } from '@ant-design/icons';
|
||||
import { Button, Checkbox, Input, Select, Space, Table } from 'antd';
|
||||
import type { TableColumnsType } from 'antd';
|
||||
import { useState } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { TableTag } from '../../components/TableTag/TableTag';
|
||||
import type { ConsolidatedRowOut, CounterpartyType, OsvFilters } from '../../types/api';
|
||||
import { counterpartyTypeLabel, osvPeriodLabel } from '../../utils/labels';
|
||||
import { formatAmount } from '../../utils/money';
|
||||
import { innCell, needsMapping } from './counterpartyMapping';
|
||||
import { COUNTERPARTY_TYPES, counterpartyTypeBadge } from './counterpartyTypeBadge';
|
||||
import type { OsvFilterDraft } from './osvFilterState';
|
||||
|
||||
type MoneyKey = 'opening_debit' | 'opening_credit' | 'turnover_debit' | 'turnover_credit' | 'closing_debit' | 'closing_credit';
|
||||
|
||||
const money = (key: MoneyKey, title: string) => ({
|
||||
const money = (key: MoneyKey, title: string, format: (raw: string | null) => string) => ({
|
||||
key,
|
||||
title,
|
||||
dataIndex: key,
|
||||
align: 'right' as const,
|
||||
width: 140,
|
||||
render: (v: string | null) => formatAmount(v, { zeroDash: true }),
|
||||
render: (v: string | null) => format(v),
|
||||
});
|
||||
|
||||
const group = (title: string, children: TableColumnsType<ConsolidatedRowOut>): TableColumnsType<ConsolidatedRowOut>[number] => ({
|
||||
@ -195,6 +194,7 @@ type Props = {
|
||||
filters: OsvFilters;
|
||||
members: Member[];
|
||||
onPage: (p: number) => void;
|
||||
numFormat: (raw: string | null) => string;
|
||||
onFilter: OnChange;
|
||||
/** «Указать» или ИНН сопоставленного ЮЛ — открыть выбор организации [32-osv-map-counterparty]. */
|
||||
onMap: (row: ConsolidatedRowOut) => void;
|
||||
@ -237,74 +237,91 @@ function InnCell({ row, onMap }: { row: ConsolidatedRowOut; onMap: (row: Consoli
|
||||
);
|
||||
}
|
||||
|
||||
export function OsvTable({ rows, total, page, pageSize, loading, filters, members, onPage, onFilter, onMap }: Props) {
|
||||
export function OsvTable({ rows, total, page, pageSize, loading, filters, members, numFormat, onPage, onFilter, onMap }: Props) {
|
||||
const filtered = Object.keys(filters).length > 0;
|
||||
const columns: TableColumnsType<ConsolidatedRowOut> = [
|
||||
{
|
||||
key: 'legal_entity_name',
|
||||
title: 'Компания ГК',
|
||||
dataIndex: 'legal_entity_name',
|
||||
width: 230,
|
||||
fixed: 'left',
|
||||
filterIcon: filterIcon(!!filters.legal_entity_id),
|
||||
// ключ в key выбирается, чтобы dropdown пересоздавался при внешнем сбросе фильтра
|
||||
filterDropdown: ({ close }) => (
|
||||
<EntityFilter
|
||||
key={filters.legal_entity_id ?? ''}
|
||||
members={members}
|
||||
value={filters.legal_entity_id}
|
||||
onChange={onFilter}
|
||||
close={close}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'account_code',
|
||||
title: 'Счёт',
|
||||
dataIndex: 'account_code',
|
||||
width: 110,
|
||||
filterIcon: filterIcon(!!filters.account_root),
|
||||
filterDropdown: ({ close }) => (
|
||||
<AccountFilter key={filters.account_root ?? ''} value={filters.account_root} onChange={onFilter} close={close} />
|
||||
),
|
||||
},
|
||||
{ key: 'period', title: 'Период', dataIndex: 'period', width: 140, render: (p: string) => osvPeriodLabel(p) },
|
||||
{
|
||||
key: 'counterparty',
|
||||
title: 'Контрагенты',
|
||||
width: 260,
|
||||
render: (_: unknown, r) => r.counterparty_entity_name ?? r.counterparty_name ?? '—',
|
||||
filterIcon: filterIcon(!!filters.q),
|
||||
filterDropdown: ({ close }) => <CounterpartyFilter key={filters.q ?? ''} q={filters.q} onChange={onFilter} close={close} />,
|
||||
},
|
||||
{
|
||||
key: 'counterparty_inn',
|
||||
title: 'ИНН',
|
||||
align: 'center',
|
||||
// шире макетных 128: «таблетка» с 12-значным ИНН ИП
|
||||
width: 140,
|
||||
onCell: (r) => ({ style: needsMapping(r) ? REQUIRED_CELL : INN_CELL }),
|
||||
render: (_: unknown, r) => <InnCell row={r} onMap={onMap} />,
|
||||
filterIcon: filterIcon(!!filters.unmapped),
|
||||
filterDropdown: ({ close }) => (
|
||||
<UnmappedFilter key={String(!!filters.unmapped)} value={!!filters.unmapped} onChange={onFilter} close={close} />
|
||||
),
|
||||
},
|
||||
group('Сальдо на начало периода', [money('opening_debit', 'Дебет'), money('opening_credit', 'Кредит')]),
|
||||
group('Обороты за период', [money('turnover_debit', 'Дебет'), money('turnover_credit', 'Кредит')]),
|
||||
group('Сальдо на конец периода', [money('closing_debit', 'Дебет'), money('closing_credit', 'Кредит')]),
|
||||
{
|
||||
key: 'counterparty_type',
|
||||
title: 'Тип контрагента',
|
||||
dataIndex: 'counterparty_type',
|
||||
width: 130,
|
||||
render: (t: CounterpartyType) => <TypeBadge type={t} />,
|
||||
filterIcon: filterIcon(!!filters.counterparty_type),
|
||||
filterDropdown: ({ close }) => (
|
||||
<CounterpartyTypeFilter key={filters.counterparty_type ?? ''} type={filters.counterparty_type} onChange={onFilter} close={close} />
|
||||
),
|
||||
},
|
||||
];
|
||||
const columns: TableColumnsType<ConsolidatedRowOut> = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
key: 'legal_entity_name',
|
||||
title: 'Компания ГК',
|
||||
dataIndex: 'legal_entity_name',
|
||||
width: 230,
|
||||
fixed: 'left',
|
||||
filterIcon: filterIcon(!!filters.legal_entity_id),
|
||||
// ключ в key выбирается, чтобы dropdown пересоздавался при внешнем сбросе фильтра
|
||||
filterDropdown: ({ close }) => (
|
||||
<EntityFilter
|
||||
key={filters.legal_entity_id ?? ''}
|
||||
members={members}
|
||||
value={filters.legal_entity_id}
|
||||
onChange={onFilter}
|
||||
close={close}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'account_code',
|
||||
title: 'Счёт',
|
||||
dataIndex: 'account_code',
|
||||
width: 110,
|
||||
filterIcon: filterIcon(!!filters.account_root),
|
||||
filterDropdown: ({ close }) => (
|
||||
<AccountFilter key={filters.account_root ?? ''} value={filters.account_root} onChange={onFilter} close={close} />
|
||||
),
|
||||
},
|
||||
{ key: 'period', title: 'Период', dataIndex: 'period', width: 140, render: (p: string) => osvPeriodLabel(p) },
|
||||
{
|
||||
key: 'counterparty',
|
||||
title: 'Контрагенты',
|
||||
width: 260,
|
||||
render: (_: unknown, r) => r.counterparty_entity_name ?? r.counterparty_name ?? '—',
|
||||
filterIcon: filterIcon(!!filters.q),
|
||||
filterDropdown: ({ close }) => <CounterpartyFilter key={filters.q ?? ''} q={filters.q} onChange={onFilter} close={close} />,
|
||||
},
|
||||
{
|
||||
key: 'counterparty_inn',
|
||||
title: 'ИНН',
|
||||
align: 'center',
|
||||
// шире макетных 128: «таблетка» с 12-значным ИНН ИП
|
||||
width: 140,
|
||||
onCell: (r) => ({ style: needsMapping(r) ? REQUIRED_CELL : INN_CELL }),
|
||||
render: (_: unknown, r) => <InnCell row={r} onMap={onMap} />,
|
||||
filterIcon: filterIcon(!!filters.unmapped),
|
||||
filterDropdown: ({ close }) => (
|
||||
<UnmappedFilter key={String(!!filters.unmapped)} value={!!filters.unmapped} onChange={onFilter} close={close} />
|
||||
),
|
||||
},
|
||||
group('Сальдо на начало периода', [money('opening_debit', 'Дебет', numFormat), money('opening_credit', 'Кредит', numFormat)]),
|
||||
group('Обороты за период', [money('turnover_debit', 'Дебет', numFormat), money('turnover_credit', 'Кредит', numFormat)]),
|
||||
group('Сальдо на конец периода', [money('closing_debit', 'Дебет', numFormat), money('closing_credit', 'Кредит', numFormat)]),
|
||||
{
|
||||
key: 'counterparty_type',
|
||||
title: 'Тип контрагента',
|
||||
dataIndex: 'counterparty_type',
|
||||
width: 130,
|
||||
render: (t: CounterpartyType) => <TypeBadge type={t} />,
|
||||
filterIcon: filterIcon(!!filters.counterparty_type),
|
||||
filterDropdown: ({ close }) => (
|
||||
<CounterpartyTypeFilter
|
||||
key={filters.counterparty_type ?? ''}
|
||||
type={filters.counterparty_type}
|
||||
onChange={onFilter}
|
||||
close={close}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
}, [
|
||||
filters.account_root,
|
||||
filters.counterparty_type,
|
||||
filters.legal_entity_id,
|
||||
filters.q,
|
||||
filters.unmapped,
|
||||
members,
|
||||
numFormat,
|
||||
onFilter,
|
||||
onMap,
|
||||
]);
|
||||
return (
|
||||
<Table<ConsolidatedRowOut>
|
||||
bordered
|
||||
|
||||
@ -8,7 +8,7 @@ export function OsvTabs() {
|
||||
return (
|
||||
<Segmented
|
||||
value='ОСВ'
|
||||
style={{ alignSelf: 'flex-start', maxWidth: '100%', overflowX: 'auto' }}
|
||||
style={{ alignSelf: 'flex-start', maxWidth: '100%', overflowX: 'auto', overflowY: 'hidden' }}
|
||||
options={[
|
||||
{ value: 'ОСВ', label: <span style={{ color: '#2a8c3a', fontWeight: 600 }}>ОСВ</span> },
|
||||
...TABS.map((t) => ({
|
||||
|
||||
42
web/src/widgets/ConsolidatedOsv/useNumberDisplay.tsx
Normal file
42
web/src/widgets/ConsolidatedOsv/useNumberDisplay.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import { useState, useMemo, useCallback } from 'react';
|
||||
|
||||
export type Unit = 'руб.' | 'тыс. руб.' | 'млн. руб.' | 'млрд. руб.';
|
||||
|
||||
export const UNITS: { value: Unit; label: string }[] = [
|
||||
{ value: 'руб.', label: 'руб.' },
|
||||
{ value: 'тыс. руб.', label: 'тыс. руб.' },
|
||||
{ value: 'млн. руб.', label: 'млн. руб.' },
|
||||
{ value: 'млрд. руб.', label: 'млрд. руб.' },
|
||||
];
|
||||
|
||||
const DIVISORS: Record<Unit, number> = {
|
||||
'руб.': 1,
|
||||
'тыс. руб.': 1e3,
|
||||
'млн. руб.': 1e6,
|
||||
'млрд. руб.': 1e9,
|
||||
};
|
||||
|
||||
export function createFormatter(precision: number): Intl.NumberFormat {
|
||||
return new Intl.NumberFormat('ru-RU', {
|
||||
minimumFractionDigits: precision,
|
||||
maximumFractionDigits: precision,
|
||||
});
|
||||
}
|
||||
|
||||
export function formatNumeric(raw: string | null, unit: Unit, nf: Intl.NumberFormat): string {
|
||||
if (raw == null || raw.trim() === '') return '—';
|
||||
const n = Number(raw);
|
||||
if (!Number.isFinite(n)) return raw;
|
||||
return nf.format(n / DIVISORS[unit]);
|
||||
}
|
||||
|
||||
export function useNumberDisplay(initial?: { unit?: Unit; precision?: number }) {
|
||||
const [unit, setUnit] = useState<Unit>(initial?.unit ?? 'руб.');
|
||||
const [precision, setPrecision] = useState(initial?.precision ?? 2);
|
||||
|
||||
const nf = useMemo(() => createFormatter(precision), [precision]);
|
||||
|
||||
const format = useCallback((raw: string | null) => formatNumeric(raw, unit, nf), [unit, nf]);
|
||||
|
||||
return { unit, setUnit, precision, setPrecision, format };
|
||||
}
|
||||
@ -23,11 +23,6 @@ const errorCodeMap = {
|
||||
TRANSITION_NOT_ALLOWED: 'Данный переход недоступен',
|
||||
};
|
||||
|
||||
/*
|
||||
"Инициация", "Обработка документов", "Корректировка данных" -> "Отменена" (комментарий обязателен);
|
||||
из "Обработка документов", "Корректировка данных" -> "Инициация" (комментарий обязателен во фронте)
|
||||
*/
|
||||
|
||||
async function mockStatusMappingFetch() {
|
||||
const mapping: Record<ConsStatus, ConsStatus[]> = {
|
||||
INITIATED: ['CANCELLED'],
|
||||
@ -95,17 +90,19 @@ export default function ChangeStatusModal({ open, versionId, setClose, onSuccess
|
||||
Текущий этап: <TableTag text={russianStatus} {...badgePreset} />
|
||||
</div>
|
||||
|
||||
<Form form={form} layout='vertical' requiredMark={false} initialValues={{ comment: '', to_status: nextStatusOptions[0] }}>
|
||||
<Form.Item name='to_status' label='Новый этап'>
|
||||
<Select
|
||||
options={nextStatusOptions.map((i) => ({ label: i, value: i }))}
|
||||
labelRender={(v) => (v.value ? statusMapping[v.value as ConsStatus] : v.value)}
|
||||
optionRender={(v) => (v.value ? statusMapping[v.value as ConsStatus] : v.value)}></Select>
|
||||
</Form.Item>
|
||||
<Form.Item name='comment' rules={[{ required: true, message: 'Комментарий необходим' }]} label='Комментарий'>
|
||||
<Input.TextArea maxLength={1000} placeholder='Укажите комментарий к изменению статуса'></Input.TextArea>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
{nextStatusOptions && (
|
||||
<Form form={form} layout='vertical' requiredMark={false} initialValues={{ comment: '', to_status: nextStatusOptions[0] }}>
|
||||
<Form.Item name='to_status' label='Новый этап'>
|
||||
<Select
|
||||
options={nextStatusOptions.map((i) => ({ label: i, value: i }))}
|
||||
labelRender={(v) => (v.value ? statusMapping[v.value as ConsStatus] : v.value)}
|
||||
optionRender={(v) => (v.value ? statusMapping[v.value as ConsStatus] : v.value)}></Select>
|
||||
</Form.Item>
|
||||
<Form.Item name='comment' rules={[{ required: true, message: 'Комментарий необходим' }]} label='Комментарий'>
|
||||
<Input.TextArea maxLength={1000} placeholder='Укажите комментарий к изменению статуса'></Input.TextArea>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user