Add Prettier to the frontend and gate formatting in CI
Форматтера у фронтенда не было: стиль держался вручную и успел разъехаться в 50 файлах. Ставим Prettier с настройками под уже сложившийся стиль (без точек с запятой, одинарные кавычки, ширина 100 — подобрана замером: при 100 расходится меньше файлов, чем при 96 или 110) и прогоняем его по коду. `src/routeTree.gen.ts` исключён — его переписывает плагин роутера. Чтобы форматирование больше не расходилось незаметно, добавлены проверки в CI: `csharpier check` для бэкенда (его отсутствие и позволило накопиться 79 неотформатированным файлам) и `prettier --check` для фронтенда. Версии форматтеров прибиты точно, без кареток: минорка меняет вывод и красит CI на файлах, которых никто не трогал. `.editorconfig` задаёт редакторам те же отступы и LF ещё до форматтера; значения совпадают с настройками csharpier и Prettier намеренно — оба его читают. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
0442056367
commit
0606ea3e6e
@@ -1,109 +1,105 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { qk } from '@/shared/api/query-keys'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { EntryTraceDto } from '@/shared/api/types'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/shared/ui/dialog'
|
||||
import { getEntryTrace } from '../api'
|
||||
import { formatChannelTime } from '../lib/format'
|
||||
|
||||
/**
|
||||
* «Почему это здесь» (см. 6.5): цепочка происхождения записи. Трейс пишется в момент генерации —
|
||||
* восстановить его потом нельзя, поэтому у старых записей часть строк будет пустой.
|
||||
*/
|
||||
export function EntryTraceDialog({
|
||||
entryId,
|
||||
utcOffsetMinutes,
|
||||
onClose,
|
||||
}: Readonly<{
|
||||
entryId: string
|
||||
utcOffsetMinutes: number
|
||||
onClose: () => void
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
const { data } = useQuery({
|
||||
queryKey: qk.entries.trace(entryId),
|
||||
queryFn: () => getEntryTrace(entryId),
|
||||
})
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={(open) => !open && onClose()}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{data
|
||||
? `${data.showName ?? '—'} · ${formatChannelTime(data.startsAtUtc, utcOffsetMinutes)}`
|
||||
: t('common.loading')}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{data && (
|
||||
<dl className="grid grid-cols-[110px_1fr] gap-x-3 gap-y-1.5 text-sm">
|
||||
<Row label={t('admin.channels.traceLayer')}>{layerSummary(data, t)}</Row>
|
||||
<Row label={t('admin.channels.traceSlot')}>{slotSummary(data, t)}</Row>
|
||||
<Row label={t('admin.channels.traceGroup')}>{groupSummary(data)}</Row>
|
||||
<Row label={t('admin.channels.traceCollection')}>{data.collectionName}</Row>
|
||||
<Row label={t('admin.channels.traceStrategy')}>{strategySummary(data, t)}</Row>
|
||||
<Row label={t('admin.channels.traceJunction')}>{data.junctionName}</Row>
|
||||
</dl>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
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 }: Readonly<{ label: string; children: React.ReactNode }>) {
|
||||
return (
|
||||
<>
|
||||
<dt className="text-muted-foreground">{label}</dt>
|
||||
<dd>{children || '—'}</dd>
|
||||
</>
|
||||
)
|
||||
}
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { qk } from '@/shared/api/query-keys'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { EntryTraceDto } from '@/shared/api/types'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/shared/ui/dialog'
|
||||
import { getEntryTrace } from '../api'
|
||||
import { formatChannelTime } from '../lib/format'
|
||||
|
||||
/**
|
||||
* «Почему это здесь» (см. 6.5): цепочка происхождения записи. Трейс пишется в момент генерации —
|
||||
* восстановить его потом нельзя, поэтому у старых записей часть строк будет пустой.
|
||||
*/
|
||||
export function EntryTraceDialog({
|
||||
entryId,
|
||||
utcOffsetMinutes,
|
||||
onClose,
|
||||
}: Readonly<{
|
||||
entryId: string
|
||||
utcOffsetMinutes: number
|
||||
onClose: () => void
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
const { data } = useQuery({
|
||||
queryKey: qk.entries.trace(entryId),
|
||||
queryFn: () => getEntryTrace(entryId),
|
||||
})
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={(open) => !open && onClose()}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{data
|
||||
? `${data.showName ?? '—'} · ${formatChannelTime(data.startsAtUtc, utcOffsetMinutes)}`
|
||||
: t('common.loading')}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{data && (
|
||||
<dl className="grid grid-cols-[110px_1fr] gap-x-3 gap-y-1.5 text-sm">
|
||||
<Row label={t('admin.channels.traceLayer')}>{layerSummary(data, t)}</Row>
|
||||
<Row label={t('admin.channels.traceSlot')}>{slotSummary(data, t)}</Row>
|
||||
<Row label={t('admin.channels.traceGroup')}>{groupSummary(data)}</Row>
|
||||
<Row label={t('admin.channels.traceCollection')}>{data.collectionName}</Row>
|
||||
<Row label={t('admin.channels.traceStrategy')}>{strategySummary(data, t)}</Row>
|
||||
<Row label={t('admin.channels.traceJunction')}>{data.junctionName}</Row>
|
||||
</dl>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
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 }: Readonly<{ label: string; children: React.ReactNode }>) {
|
||||
return (
|
||||
<>
|
||||
<dt className="text-muted-foreground">{label}</dt>
|
||||
<dd>{children || '—'}</dd>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user