Add grid auto-build functionality with profile management and preview capabilities
Implemented new API endpoints and frontend components for grid auto-building based on predefined profiles. Added functionality to list grid profiles, preview generated grids, and generate grids with specified parameters. Enhanced the TemplateEndpoints and related services to support these features, improving the user experience for channel scheduling. Localization updates were made to accommodate new features in both English and Russian.
This commit is contained in:
@@ -13,7 +13,7 @@ import { toast } from '@/shared/ui/toast-store'
|
||||
import { useTableSort } from '@/shared/lib/table-sort'
|
||||
import { SortHeader } from '@/shared/ui/sortable'
|
||||
import { deleteMedia, getMediaStats, listMedia } from './api'
|
||||
import { formatDuration } from './format'
|
||||
import { formatDuration, splitEta } from './format'
|
||||
import { ManualInboxDialog } from './ManualInboxDialog'
|
||||
import { UploadToShowDialog } from './UploadToShowDialog'
|
||||
import { useUploadStore } from './upload-store'
|
||||
@@ -145,6 +145,15 @@ export function MediaPanel() {
|
||||
{formatDuration(stats.averageProcessingSeconds)}
|
||||
</span>
|
||||
</span>
|
||||
{/* Оценка появляется только когда есть что ждать: «Осталось: —» бесполезно. */}
|
||||
{stats.estimatedRemainingSeconds !== null && (
|
||||
<span title={t('admin.media.stats.eta')}>
|
||||
{t('admin.media.stats.etaShort')}:{' '}
|
||||
<span className="text-foreground tabular-nums">
|
||||
<EtaValue seconds={stats.estimatedRemainingSeconds} />
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -267,6 +276,21 @@ export function MediaPanel() {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* «Осталось: ~2 ч 40 мин». Тильда и округление до минут намеренные: это оценка по среднему времени,
|
||||
* и точная до секунды подпись читалась бы как обещание.
|
||||
*/
|
||||
function EtaValue({ seconds }: Readonly<{ seconds: number }>) {
|
||||
const { t } = useTranslation()
|
||||
const parts = splitEta(seconds)
|
||||
if (!parts) return <>—</>
|
||||
if (parts.hours === 0 && parts.minutes === 0) return <>{t('admin.media.stats.etaSoon')}</>
|
||||
|
||||
const hours = parts.hours > 0 ? `${parts.hours} ${t('admin.media.stats.hoursShort')}` : ''
|
||||
const minutes = parts.minutes > 0 ? `${parts.minutes} ${t('admin.media.stats.minutesShort')}` : ''
|
||||
return <>~{[hours, minutes].filter(Boolean).join(' ')}</>
|
||||
}
|
||||
|
||||
function MediaRow({ asset, onDelete }: Readonly<{ asset: MediaAssetDto; onDelete: () => void }>) {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
|
||||
@@ -8,3 +8,14 @@ export function formatDuration(seconds: number | null): string {
|
||||
const pad = (n: number) => String(n).padStart(2, '0')
|
||||
return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${m}:${pad(s)}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Оценка «сколько ещё ждать» — часами и минутами, без секунд. Точность здесь была бы мнимой: оценка
|
||||
* строится на среднем времени обработки, и «13:27:42» обещало бы то, чего никто не гарантирует.
|
||||
* Меньше минуты — не разбиваем на части, вернём нули: подпись всё равно скажет «меньше минуты».
|
||||
*/
|
||||
export function splitEta(seconds: number | null): { hours: number; minutes: number } | null {
|
||||
if (seconds == null) return null
|
||||
const total = Math.max(0, Math.round(seconds))
|
||||
return { hours: Math.floor(total / 3600), minutes: Math.round((total % 3600) / 60) }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user