feat(record): REOPEN operation — вернуть закрытую запись
This commit is contained in:
@@ -359,7 +359,7 @@ export type WebhookDeliveryStats = {
|
||||
// === Approval Workflow v2 ===
|
||||
|
||||
export type DraftStatus = 'PENDING' | 'APPROVED' | 'REJECTED' | 'WITHDRAWN'
|
||||
export type DraftOperation = 'CREATE' | 'UPDATE' | 'CLOSE'
|
||||
export type DraftOperation = 'CREATE' | 'UPDATE' | 'CLOSE' | 'REOPEN'
|
||||
|
||||
export type DraftResponse = {
|
||||
id: string
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Badge, Button, Drawer, LoadingBlock, QueryErrorState } from '@/ui'
|
||||
import { ArrowsLeftRightIcon, ArrowCounterClockwiseIcon, BracketsCurlyIcon, InfoIcon } from '@phosphor-icons/react'
|
||||
import { ArrowsLeftRightIcon, ArrowCounterClockwiseIcon, ArrowUUpLeftIcon, BracketsCurlyIcon, InfoIcon } from '@phosphor-icons/react'
|
||||
import { useRecordHistory } from '@/api/queries'
|
||||
import { RecordDependentsPanel } from '@/components/lineage/RecordDependentsPanel'
|
||||
import { UserCell } from '@/lib/useUserDisplay'
|
||||
@@ -18,10 +18,14 @@ type Props = {
|
||||
/** Optional — invoked для "Откатить к v1.X.X". Parent submits update
|
||||
* mutation с этой версией как payload. Если undefined — кнопка скрыта. */
|
||||
onRevert?: (versionData: unknown, displayVersion: number) => void
|
||||
/** Optional — invoked для "Открыть заново" самой свежей closed-версии.
|
||||
* Parent submits REOPEN draft через submitDraftMut. Если undefined — кнопка
|
||||
* скрыта (нет права record:close / non-admin context). */
|
||||
onReopen?: () => void
|
||||
}
|
||||
|
||||
export const RecordHistoryDrawer = ({
|
||||
open, onClose, dictionaryName, businessKey, onCompare, onRevert,
|
||||
open, onClose, dictionaryName, businessKey, onCompare, onRevert, onReopen,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const { data, isLoading, error, refetch } = useRecordHistory(dictionaryName, open ? businessKey : undefined)
|
||||
@@ -157,6 +161,17 @@ export const RecordHistoryDrawer = ({
|
||||
{t('history.revert', { defaultValue: `Откатить к v${displayVersion}` })}
|
||||
</Button>
|
||||
)}
|
||||
{isLatest && status === 'expired' && onReopen && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="primary"
|
||||
size="sm"
|
||||
onClick={onReopen}
|
||||
leftIcon={<ArrowUUpLeftIcon weight="regular" size={14} />}
|
||||
>
|
||||
{t('history.reopen', { defaultValue: 'Открыть заново' })}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{/* Single-version hint: объясняем, почему Compare/Revert
|
||||
* отсутствуют. Раньше пользователь видел только JSON-ссылку
|
||||
|
||||
@@ -530,6 +530,8 @@ i18n
|
||||
'history.compare': 'Сравнить с текущим',
|
||||
'history.revert': 'Откатить к v{{v}}',
|
||||
'history.revertConfirm': 'Откатить к v{{v}}? Создастся новая активная версия.',
|
||||
'history.reopen': 'Открыть заново',
|
||||
'history.reopenConfirm': 'Открыть запись заново? Будет создан REOPEN-draft, требуется approve.',
|
||||
'history.singleVersionHint': 'Это единственная версия. Кнопки «Сравнить» и «Откатить» появятся, когда у записи будет более одной версии.',
|
||||
// === Changelog (schema timeline + diff) ===
|
||||
'changelog.empty.title': 'История пуста',
|
||||
@@ -1386,6 +1388,8 @@ i18n
|
||||
'history.compare': 'Compare with current',
|
||||
'history.revert': 'Revert to v{{v}}',
|
||||
'history.revertConfirm': 'Revert to v{{v}}? A new active version will be created.',
|
||||
'history.reopen': 'Reopen',
|
||||
'history.reopenConfirm': 'Reopen this record? A REOPEN draft will be created and needs approval.',
|
||||
'history.singleVersionHint': 'This is the only version. Compare and Revert actions will appear once the record has more than one version.',
|
||||
// === Changelog (schema timeline + diff) ===
|
||||
'changelog.empty.title': 'No history yet',
|
||||
|
||||
@@ -1719,6 +1719,26 @@ function DictionaryDetail() {
|
||||
},
|
||||
})
|
||||
} : undefined}
|
||||
// Reopen: submit REOPEN draft. Same approval gate как CLOSE — пройдёт
|
||||
// через DRAFT_SUBMIT → DRAFT_APPROVE. Admin self-approve работает
|
||||
// если admin role есть. Backend применит applyApprovedReopen и
|
||||
// создаст новую active версию с данными последней closed-версии.
|
||||
onReopen={canMutate ? () => {
|
||||
if (!historyKey) return
|
||||
const confirmed = window.confirm(
|
||||
t('history.reopenConfirm', { defaultValue: 'Открыть запись заново? Будет создан REOPEN-draft, требуется approve.' }) as string
|
||||
)
|
||||
if (!confirmed) return
|
||||
submitDraftMut.mutate({
|
||||
businessKey: historyKey,
|
||||
operation: 'REOPEN',
|
||||
comment: null,
|
||||
}, {
|
||||
onSuccess: () => {
|
||||
setHistoryKey(undefined)
|
||||
},
|
||||
})
|
||||
} : undefined}
|
||||
/>
|
||||
|
||||
<CascadeConfirmDialog
|
||||
|
||||
Reference in New Issue
Block a user