Compare commits
2
Commits
38b6160bd2
...
419aff54fa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
419aff54fa | ||
|
|
f4926848d7 |
@@ -4,5 +4,15 @@
|
|||||||
"rules": {
|
"rules": {
|
||||||
"react/rules-of-hooks": "error",
|
"react/rules-of-hooks": "error",
|
||||||
"react/only-export-components": ["warn", { "allowConstantExport": true }]
|
"react/only-export-components": ["warn", { "allowConstantExport": true }]
|
||||||
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
// File-based routing TanStack Router: файл роута обязан экспортировать `Route` рядом с
|
||||||
|
// компонентом страницы — правило тут неисполнимо в принципе.
|
||||||
|
"files": ["src/routes/**"],
|
||||||
|
"rules": {
|
||||||
|
"react/only-export-components": "off"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import { qk } from '@/shared/api/query-keys'
|
import { qk } from '@/shared/api/query-keys'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import type { EntryTraceDto } from '@/shared/api/types'
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -42,52 +43,11 @@ export function EntryTraceDialog({
|
|||||||
|
|
||||||
{data && (
|
{data && (
|
||||||
<dl className="grid grid-cols-[110px_1fr] gap-x-3 gap-y-1.5 text-sm">
|
<dl className="grid grid-cols-[110px_1fr] gap-x-3 gap-y-1.5 text-sm">
|
||||||
<Row label={t('admin.channels.traceLayer')}>
|
<Row label={t('admin.channels.traceLayer')}>{layerSummary(data, t)}</Row>
|
||||||
{data.layerName
|
<Row label={t('admin.channels.traceSlot')}>{slotSummary(data, t)}</Row>
|
||||||
? `${data.layerName}${data.layerPriority !== null ? ` (${t('admin.channels.priority')} ${data.layerPriority})` : ''}`
|
<Row label={t('admin.channels.traceGroup')}>{groupSummary(data)}</Row>
|
||||||
: null}
|
|
||||||
</Row>
|
|
||||||
<Row label={t('admin.channels.traceSlot')}>
|
|
||||||
{data.slotTitle
|
|
||||||
? [
|
|
||||||
data.slotTitle,
|
|
||||||
data.slotWeekday === null
|
|
||||||
? t('admin.channels.everyDay')
|
|
||||||
: t(`admin.channels.weekdays.${data.slotWeekday}`),
|
|
||||||
data.slotTargetStart?.slice(0, 5),
|
|
||||||
data.slotDurationMinutes ? `${data.slotDurationMinutes} мин` : null,
|
|
||||||
data.driftMinutes !== 0
|
|
||||||
? t('admin.channels.traceDrift', { minutes: data.driftMinutes })
|
|
||||||
: null,
|
|
||||||
data.snapped ? t('admin.channels.traceSnapped') : null,
|
|
||||||
]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(' · ')
|
|
||||||
: null}
|
|
||||||
</Row>
|
|
||||||
<Row label={t('admin.channels.traceGroup')}>
|
|
||||||
{data.groupName
|
|
||||||
? `${data.groupName}${data.groupItemCount !== null ? ` (${data.groupItemCount})` : ''}`
|
|
||||||
: null}
|
|
||||||
</Row>
|
|
||||||
<Row label={t('admin.channels.traceCollection')}>{data.collectionName}</Row>
|
<Row label={t('admin.channels.traceCollection')}>{data.collectionName}</Row>
|
||||||
<Row label={t('admin.channels.traceStrategy')}>
|
<Row label={t('admin.channels.traceStrategy')}>{strategySummary(data, t)}</Row>
|
||||||
{data.strategy
|
|
||||||
? [
|
|
||||||
t(`admin.channels.strategies.${data.strategy}`),
|
|
||||||
data.cooldownDays
|
|
||||||
? t('admin.channels.traceCooldown', { days: data.cooldownDays })
|
|
||||||
: null,
|
|
||||||
data.candidatesAfterCooldown !== null
|
|
||||||
? t('admin.channels.traceCandidates', {
|
|
||||||
count: data.candidatesAfterCooldown,
|
|
||||||
})
|
|
||||||
: null,
|
|
||||||
]
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(' · ')
|
|
||||||
: null}
|
|
||||||
</Row>
|
|
||||||
<Row label={t('admin.channels.traceJunction')}>{data.junctionName}</Row>
|
<Row label={t('admin.channels.traceJunction')}>{data.junctionName}</Row>
|
||||||
</dl>
|
</dl>
|
||||||
)}
|
)}
|
||||||
@@ -96,6 +56,49 @@ export function EntryTraceDialog({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Translate = ReturnType<typeof useTranslation>['t']
|
||||||
|
|
||||||
|
/** Склейка непустых частей строки трейса; пусто — значит строка не заполнена (покажем «—»). */
|
||||||
|
const joinParts = (parts: (string | null | undefined)[]) => parts.filter(Boolean).join(' · ') || null
|
||||||
|
|
||||||
|
function layerSummary(data: EntryTraceDto, t: Translate) {
|
||||||
|
if (!data.layerName) return null
|
||||||
|
const priority =
|
||||||
|
data.layerPriority !== null ? ` (${t('admin.channels.priority')} ${data.layerPriority})` : ''
|
||||||
|
return `${data.layerName}${priority}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function slotSummary(data: EntryTraceDto, t: Translate) {
|
||||||
|
if (!data.slotTitle) return null
|
||||||
|
return joinParts([
|
||||||
|
data.slotTitle,
|
||||||
|
data.slotWeekday === null
|
||||||
|
? t('admin.channels.everyDay')
|
||||||
|
: t(`admin.channels.weekdays.${data.slotWeekday}`),
|
||||||
|
data.slotTargetStart?.slice(0, 5),
|
||||||
|
data.slotDurationMinutes ? `${data.slotDurationMinutes} мин` : null,
|
||||||
|
data.driftMinutes !== 0 ? t('admin.channels.traceDrift', { minutes: data.driftMinutes }) : null,
|
||||||
|
data.snapped ? t('admin.channels.traceSnapped') : null,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
function groupSummary(data: EntryTraceDto) {
|
||||||
|
if (!data.groupName) return null
|
||||||
|
const count = data.groupItemCount !== null ? ` (${data.groupItemCount})` : ''
|
||||||
|
return `${data.groupName}${count}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function strategySummary(data: EntryTraceDto, t: Translate) {
|
||||||
|
if (!data.strategy) return null
|
||||||
|
return joinParts([
|
||||||
|
t(`admin.channels.strategies.${data.strategy}`),
|
||||||
|
data.cooldownDays ? t('admin.channels.traceCooldown', { days: data.cooldownDays }) : null,
|
||||||
|
data.candidatesAfterCooldown !== null
|
||||||
|
? t('admin.channels.traceCandidates', { count: data.candidatesAfterCooldown })
|
||||||
|
: null,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
function Row({ label, children }: { label: string; children: React.ReactNode }) {
|
function Row({ label, children }: { label: string; children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -55,6 +55,9 @@ export function GridTab({
|
|||||||
const [copyTargets, setCopyTargets] = useState<number[]>([])
|
const [copyTargets, setCopyTargets] = useState<number[]>([])
|
||||||
const [copyFromChannel, setCopyFromChannel] = useState('')
|
const [copyFromChannel, setCopyFromChannel] = useState('')
|
||||||
|
|
||||||
|
const toggleCopyTarget = (day: number, checked: boolean) =>
|
||||||
|
setCopyTargets((current) => (checked ? [...current, day] : current.filter((d) => d !== day)))
|
||||||
|
|
||||||
const { data: channels } = useQuery({ queryKey: qk.channels.all, queryFn: listChannels })
|
const { data: channels } = useQuery({ queryKey: qk.channels.all, queryFn: listChannels })
|
||||||
|
|
||||||
const addLayerMutation = useMutation({
|
const addLayerMutation = useMutation({
|
||||||
@@ -318,13 +321,7 @@ export function GridTab({
|
|||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={copyTargets.includes(day)}
|
checked={copyTargets.includes(day)}
|
||||||
onChange={(e) =>
|
onChange={(e) => toggleCopyTarget(day, e.target.checked)}
|
||||||
setCopyTargets((current) =>
|
|
||||||
e.target.checked
|
|
||||||
? [...current, day]
|
|
||||||
: current.filter((d) => d !== day),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
{t(`admin.channels.weekdays.${day}`)}
|
{t(`admin.channels.weekdays.${day}`)}
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -87,6 +87,9 @@ export function RulesCard({
|
|||||||
onError,
|
onError,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const removeWindow | ||||||