Merge branch 'feat/info-panel-export-button' into 'main'

feat(editor): Export button в InfoPanel per redesign

See merge request 2-6/2-6-4/terravault/ordinis!73
This commit is contained in:
Александр Зимин
2026-05-11 15:04:01 +00:00
2 changed files with 28 additions and 2 deletions
@@ -1,7 +1,7 @@
import { useMemo } from 'react' import { useMemo } from 'react'
import { Link } from '@tanstack/react-router' import { Link } from '@tanstack/react-router'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { ClockCounterClockwiseIcon, MapTrifoldIcon } from '@phosphor-icons/react' import { ClockCounterClockwiseIcon, MapTrifoldIcon, DownloadSimpleIcon } from '@phosphor-icons/react'
import { Badge } from '@/ui' import { Badge } from '@/ui'
import { useDictionaryDependents } from '@/api/queries' import { useDictionaryDependents } from '@/api/queries'
import type { DictionaryDetail } from '@/api/client' import type { DictionaryDetail } from '@/api/client'
@@ -25,13 +25,15 @@ import { cn } from '@/lib/utils'
export type EditorInfoPanelProps = { export type EditorInfoPanelProps = {
detail: DictionaryDetail detail: DictionaryDetail
recordCount: number recordCount: number
/** Optional action buttons (Time-travel / AOI filter) — bottom of panel /** Optional action buttons (Time-travel / AOI / Export) — bottom of panel
* per redesign prototype. Toggle handlers come from route's state. */ * per redesign prototype. Toggle handlers come from route's state. */
actions?: { actions?: {
onTimeTravel?: () => void onTimeTravel?: () => void
timeTravelActive?: boolean timeTravelActive?: boolean
onAoi?: () => void onAoi?: () => void
aoiActive?: boolean aoiActive?: boolean
onExport?: () => void
exportPending?: boolean
} }
} }
@@ -109,6 +111,19 @@ export function EditorInfoPanel({ detail, recordCount, actions }: EditorInfoPane
{t('aoi.button', { defaultValue: 'AOI фильтр…' })} {t('aoi.button', { defaultValue: 'AOI фильтр…' })}
</button> </button>
)} )}
{actions.onExport && (
<button
type="button"
onClick={actions.onExport}
disabled={actions.exportPending}
className="w-full h-8 rounded-md border border-line bg-surface text-ink-2 hover:border-line-2 hover:bg-surface-2 disabled:opacity-50 disabled:cursor-not-allowed text-cell flex items-center justify-center gap-1.5 transition-colors"
>
<DownloadSimpleIcon weight="regular" size={14} />
{actions.exportPending
? t('export.pending', { defaultValue: 'Экспорт…' })
: t('export.button', { defaultValue: '↓ Экспорт…' })}
</button>
)}
</div> </div>
)} )}
@@ -570,6 +570,17 @@ function DictionaryDetail() {
timeTravelActive: Boolean(timeTravelAt), timeTravelActive: Boolean(timeTravelAt),
onAoi: () => setAoiOpen(true), onAoi: () => setAoiOpen(true),
aoiActive: Boolean(aoi), aoiActive: Boolean(aoi),
onExport: () => {
// Export all visible records (filtered + paginated set OR full
// dictionary). Если selection непустой — exports selection,
// иначе — все filtered records.
const keys =
selection.size > 0
? Array.from(selection)
: filteredRecords.map((r) => r.businessKey)
if (keys.length > 0) bulkExportMut.mutate(keys)
},
exportPending: bulkExportMut.isPending,
}} }}
/> />
)} )}