Refactor JunctionFiller logic to improve eligibility and rolling mechanics
Updated the JunctionFiller class to adjust the eligibility criteria for junction elements, ensuring that the roll for chance is determined after the selection of a variant. Enhanced the Passes method to exclude the rolling logic, which now occurs in a separate Rolls method. This change clarifies the flow of decision-making in junctions and ensures that the chance of the selected variant is accurately represented. Additionally, introduced new tests to validate the updated rolling behavior and its impact on junction processing.
This commit is contained in:
@@ -54,6 +54,9 @@ export function AirSchedule({
|
||||
const { t } = useTranslation()
|
||||
const [day, setDay] = useState(0)
|
||||
const [page, setPage] = useState(1)
|
||||
// По умолчанию прошедшее скрыто: эфир смотрят, чтобы понять, что будет, а вчерашние страницы
|
||||
// приходилось пролистывать каждый раз заново.
|
||||
const [hidePast, setHidePast] = useState(true)
|
||||
|
||||
const range = useMemo(() => {
|
||||
const from = dayStartUtc(day, utcOffsetMinutes, dayStartTime)
|
||||
@@ -65,9 +68,16 @@ export function AirSchedule({
|
||||
queryFn: () => getSchedule(channelId, range.from, range.to),
|
||||
})
|
||||
|
||||
const entries = data ?? []
|
||||
const totalPages = Math.max(1, Math.ceil(entries.length / PAGE_SIZE))
|
||||
const pageItems = entries.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE)
|
||||
const entries = useMemo(() => data ?? [], [data])
|
||||
const visible = useMemo(() => {
|
||||
if (!hidePast) return entries
|
||||
// Идущая запись остаётся: то, что сейчас в эфире, — ещё не история.
|
||||
const now = Date.now()
|
||||
return entries.filter((entry) => new Date(entry.endsAtUtc).getTime() > now)
|
||||
}, [entries, hidePast])
|
||||
|
||||
const totalPages = Math.max(1, Math.ceil(visible.length / PAGE_SIZE))
|
||||
const pageItems = visible.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
@@ -95,6 +105,18 @@ export function AirSchedule({
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
|
||||
<label className="ml-auto flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={hidePast}
|
||||
onChange={(e) => {
|
||||
setHidePast(e.target.checked)
|
||||
setPage(1)
|
||||
}}
|
||||
/>
|
||||
{t('admin.channels.airHidePast')}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
@@ -103,7 +125,7 @@ export function AirSchedule({
|
||||
<>
|
||||
<SchedulePreview entries={pageItems} onShowTrace={onShowTrace} />
|
||||
<div className="flex items-center justify-between gap-2 text-xs text-muted-foreground">
|
||||
<span>{t('admin.channels.airCount', { count: entries.length })}</span>
|
||||
<span>{t('admin.channels.airCount', { count: visible.length })}</span>
|
||||
<Pager page={page} totalPages={totalPages} onChange={setPage} />
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -93,280 +93,290 @@ export function JunctionElementDialog({
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={(open) => !open && onClose()}>
|
||||
<DialogContent>
|
||||
<DialogContent className="flex max-h-[90vh] w-[calc(100%-2rem)] max-w-3xl flex-col">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('admin.junctions.element')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex max-h-[70vh] flex-col gap-3 overflow-y-auto text-sm">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>{t('admin.junctions.kind')}</Label>
|
||||
<select
|
||||
className="h-9 rounded-md border border-border bg-transparent px-2"
|
||||
value={body.kind}
|
||||
onChange={(e) => patch({ kind: e.target.value as JunctionElementKind })}
|
||||
>
|
||||
{KINDS.map((kind) => (
|
||||
<option key={kind} value={kind}>
|
||||
{t(`admin.junctions.kinds.${kind}`)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{isBumper ? (
|
||||
<>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>{t('admin.junctions.bumperTemplate')}</Label>
|
||||
<select
|
||||
className="h-9 rounded-md border border-border bg-transparent px-2"
|
||||
value={body.bumperTemplateId ?? ''}
|
||||
onChange={(e) =>
|
||||
patch({ bumperTemplateId: e.target.value || null, bumperVariantId: null })
|
||||
}
|
||||
>
|
||||
<option value="">{t('admin.junctions.pickBumper')}</option>
|
||||
{(bumpers ?? []).map((bumper) => (
|
||||
<option key={bumper.id} value={bumper.id}>
|
||||
{bumper.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>{t('admin.junctions.bumperVariant')}</Label>
|
||||
<select
|
||||
className="h-9 rounded-md border border-border bg-transparent px-2"
|
||||
value={body.bumperVariantId ?? ''}
|
||||
onChange={(e) => patch({ bumperVariantId: e.target.value || null })}
|
||||
>
|
||||
<option value="">{t('admin.junctions.bumperVariantAuto')}</option>
|
||||
{(template?.variants ?? []).map((variant) => (
|
||||
<option key={variant.id} value={variant.id}>
|
||||
{variant.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t('admin.junctions.bumperVariantHint')}
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
{/* Две колонки: слева что играет, справа когда. Их читают вместе — «заставка при смене шоу
|
||||
в шести случаях из десяти» — и в одну колонку это разъезжалось на два экрана прокрутки. */}
|
||||
<div className="grid flex-1 grid-cols-1 items-start gap-x-6 gap-y-3 overflow-y-auto text-sm sm:grid-cols-2">
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>{t('admin.junctions.group')}</Label>
|
||||
<Label>{t('admin.junctions.kind')}</Label>
|
||||
<select
|
||||
className="h-9 rounded-md border border-border bg-transparent px-2"
|
||||
value={body.groupId ?? ''}
|
||||
onChange={(e) => patch({ groupId: e.target.value || null })}
|
||||
value={body.kind}
|
||||
onChange={(e) => patch({ kind: e.target.value as JunctionElementKind })}
|
||||
>
|
||||
<option value="">{t('admin.junctions.pickGroup')}</option>
|
||||
{(groups ?? []).map((group) => (
|
||||
<option key={group.id} value={group.id}>
|
||||
{group.name} · {group.itemCount}
|
||||
{KINDS.map((kind) => (
|
||||
<option key={kind} value={kind}>
|
||||
{t(`admin.junctions.kinds.${kind}`)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isBumper && (
|
||||
<>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{isBumper ? (
|
||||
<>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>{t('admin.junctions.amountMode')}</Label>
|
||||
<Label>{t('admin.junctions.bumperTemplate')}</Label>
|
||||
<select
|
||||
className="h-9 rounded-md border border-border bg-transparent px-2"
|
||||
value={body.amountMode}
|
||||
onChange={(e) => patch({ amountMode: e.target.value as JunctionAmountMode })}
|
||||
value={body.bumperTemplateId ?? ''}
|
||||
onChange={(e) =>
|
||||
patch({ bumperTemplateId: e.target.value || null, bumperVariantId: null })
|
||||
}
|
||||
>
|
||||
{AMOUNT_MODES.map((mode) => (
|
||||
<option key={mode} value={mode}>
|
||||
{t(`admin.junctions.amountModes.${mode}`)}
|
||||
<option value="">{t('admin.junctions.pickBumper')}</option>
|
||||
{(bumpers ?? []).map((bumper) => (
|
||||
<option key={bumper.id} value={bumper.id}>
|
||||
{bumper.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>
|
||||
{body.amountMode === 'Count'
|
||||
? t('admin.junctions.count')
|
||||
: t('admin.junctions.minutes')}
|
||||
</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
value={body.amountValue}
|
||||
onChange={(e) => patch({ amountValue: Number(e.target.value) })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">{t('admin.junctions.amountHint')}</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={body.isRequired}
|
||||
onChange={(e) => patch({ isRequired: e.target.checked })}
|
||||
/>
|
||||
{t('admin.junctions.required')}
|
||||
</label>
|
||||
<p className="-mt-2 text-xs text-muted-foreground">{t('admin.junctions.requiredHint')}</p>
|
||||
|
||||
{/* Вес внутри развилки виден только когда врезка в развилке — иначе это лишнее поле. */}
|
||||
{body.choiceKey && (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>{t('admin.junctions.choiceWeight')}</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
max={1000}
|
||||
className="w-32"
|
||||
value={body.choiceWeight}
|
||||
onChange={(e) =>
|
||||
patch({ choiceWeight: Math.max(0, Math.round(Number(e.target.value)) || 0) })
|
||||
}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t('admin.junctions.choiceWeightHint')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="border-t border-border pt-3 text-xs uppercase tracking-wide text-muted-foreground">
|
||||
{t('admin.junctions.conditions')}
|
||||
</div>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={conditions.onlyOnElementChange}
|
||||
onChange={(e) => setConditions({ onlyOnElementChange: e.target.checked })}
|
||||
/>
|
||||
{t('admin.junctions.onlyOnChange')}
|
||||
</label>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>{t('admin.junctions.chance')}</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
max={100}
|
||||
value={conditions.chance}
|
||||
onChange={(e) =>
|
||||
setConditions({
|
||||
chance: Math.min(100, Math.max(0, Math.round(Number(e.target.value)) || 0)),
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>{t('admin.junctions.minInterval')}</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
value={conditions.minMinutesBetween}
|
||||
onChange={(e) => setConditions({ minMinutesBetween: Number(e.target.value) })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="-mt-1 text-xs text-muted-foreground">{t('admin.junctions.chanceHint')}</p>
|
||||
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>{t('admin.junctions.nearHour')}</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
type="number"
|
||||
className="w-32"
|
||||
min={0}
|
||||
max={30}
|
||||
value={conditions.nearHourMinutes}
|
||||
onChange={(e) =>
|
||||
setConditions({
|
||||
nearHourMinutes: Math.min(
|
||||
30,
|
||||
Math.max(0, Math.round(Number(e.target.value)) || 0),
|
||||
),
|
||||
})
|
||||
}
|
||||
/>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('admin.junctions.nearHourUnit')}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">{t('admin.junctions.nearHourHint')}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>{t('admin.junctions.dayparts')}</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{DAYPARTS.map((daypart) => {
|
||||
const active = conditions.dayparts?.includes(daypart) ?? false
|
||||
return (
|
||||
<button
|
||||
key={daypart}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const current = conditions.dayparts ?? []
|
||||
const next = active
|
||||
? current.filter((d) => d !== daypart)
|
||||
: [...current, daypart]
|
||||
setConditions({ dayparts: next.length === 0 ? null : next })
|
||||
}}
|
||||
className={`rounded border px-2 py-1 text-xs ${
|
||||
active ? 'border-primary text-primary' : 'border-border text-muted-foreground'
|
||||
}`}
|
||||
<Label>{t('admin.junctions.bumperVariant')}</Label>
|
||||
<select
|
||||
className="h-9 rounded-md border border-border bg-transparent px-2"
|
||||
value={body.bumperVariantId ?? ''}
|
||||
onChange={(e) => patch({ bumperVariantId: e.target.value || null })}
|
||||
>
|
||||
{t(`admin.channels.dayparts.${daypart}`)}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">{t('admin.junctions.daypartsHint')}</p>
|
||||
<option value="">{t('admin.junctions.bumperVariantAuto')}</option>
|
||||
{(template?.variants ?? []).map((variant) => (
|
||||
<option key={variant.id} value={variant.id}>
|
||||
{variant.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t('admin.junctions.bumperVariantHint')}
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>{t('admin.junctions.group')}</Label>
|
||||
<select
|
||||
className="h-9 rounded-md border border-border bg-transparent px-2"
|
||||
value={body.groupId ?? ''}
|
||||
onChange={(e) => patch({ groupId: e.target.value || null })}
|
||||
>
|
||||
<option value="">{t('admin.junctions.pickGroup')}</option>
|
||||
{(groups ?? []).map((group) => (
|
||||
<option key={group.id} value={group.id}>
|
||||
{group.name} · {group.itemCount}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isBumper && (
|
||||
<>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>{t('admin.junctions.amountMode')}</Label>
|
||||
<select
|
||||
className="h-9 rounded-md border border-border bg-transparent px-2"
|
||||
value={body.amountMode}
|
||||
onChange={(e) => patch({ amountMode: e.target.value as JunctionAmountMode })}
|
||||
>
|
||||
{AMOUNT_MODES.map((mode) => (
|
||||
<option key={mode} value={mode}>
|
||||
{t(`admin.junctions.amountModes.${mode}`)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>
|
||||
{body.amountMode === 'Count'
|
||||
? t('admin.junctions.count')
|
||||
: t('admin.junctions.minutes')}
|
||||
</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
value={body.amountValue}
|
||||
onChange={(e) => patch({ amountValue: Number(e.target.value) })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">{t('admin.junctions.amountHint')}</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={body.isRequired}
|
||||
onChange={(e) => patch({ isRequired: e.target.checked })}
|
||||
/>
|
||||
{t('admin.junctions.required')}
|
||||
</label>
|
||||
<p className="-mt-2 text-xs text-muted-foreground">
|
||||
{t('admin.junctions.requiredHint')}
|
||||
</p>
|
||||
|
||||
{/* Вес внутри развилки виден только когда врезка в развилке — иначе это лишнее поле. */}
|
||||
{body.choiceKey && (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>{t('admin.junctions.choiceWeight')}</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
max={1000}
|
||||
className="w-32"
|
||||
value={body.choiceWeight}
|
||||
onChange={(e) =>
|
||||
patch({ choiceWeight: Math.max(0, Math.round(Number(e.target.value)) || 0) })
|
||||
}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{t('admin.junctions.choiceWeightHint')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>{t('admin.junctions.timeWindow')}</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
type="time"
|
||||
className="w-32"
|
||||
value={window?.from.slice(0, 5) ?? ''}
|
||||
onChange={(e) =>
|
||||
setConditions({
|
||||
timeWindow: e.target.value
|
||||
? { from: e.target.value, to: window?.to ?? '23:59' }
|
||||
: null,
|
||||
})
|
||||
}
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="border-t border-border pt-3 text-xs uppercase tracking-wide text-muted-foreground">
|
||||
{t('admin.junctions.conditions')}
|
||||
</div>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={conditions.onlyOnElementChange}
|
||||
onChange={(e) => setConditions({ onlyOnElementChange: e.target.checked })}
|
||||
/>
|
||||
<span className="text-muted-foreground">—</span>
|
||||
<Input
|
||||
type="time"
|
||||
className="w-32"
|
||||
value={window?.to.slice(0, 5) ?? ''}
|
||||
onChange={(e) =>
|
||||
setConditions({
|
||||
timeWindow: e.target.value
|
||||
? { from: window?.from ?? '00:00', to: e.target.value }
|
||||
: null,
|
||||
})
|
||||
}
|
||||
/>
|
||||
{window && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => setConditions({ timeWindow: null })}
|
||||
>
|
||||
{t('admin.junctions.clearWindow')}
|
||||
</Button>
|
||||
)}
|
||||
{t('admin.junctions.onlyOnChange')}
|
||||
</label>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>{t('admin.junctions.chance')}</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
max={100}
|
||||
value={conditions.chance}
|
||||
onChange={(e) =>
|
||||
setConditions({
|
||||
chance: Math.min(100, Math.max(0, Math.round(Number(e.target.value)) || 0)),
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>{t('admin.junctions.minInterval')}</Label>
|
||||
<Input
|
||||
type="number"
|
||||
min={0}
|
||||
value={conditions.minMinutesBetween}
|
||||
onChange={(e) => setConditions({ minMinutesBetween: Number(e.target.value) })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="-mt-1 text-xs text-muted-foreground">{t('admin.junctions.chanceHint')}</p>
|
||||
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>{t('admin.junctions.nearHour')}</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
type="number"
|
||||
className="w-32"
|
||||
min={0}
|
||||
max={30}
|
||||
value={conditions.nearHourMinutes}
|
||||
onChange={(e) =>
|
||||
setConditions({
|
||||
nearHourMinutes: Math.min(
|
||||
30,
|
||||
Math.max(0, Math.round(Number(e.target.value)) || 0),
|
||||
),
|
||||
})
|
||||
}
|
||||
/>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('admin.junctions.nearHourUnit')}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">{t('admin.junctions.nearHourHint')}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>{t('admin.junctions.dayparts')}</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{DAYPARTS.map((daypart) => {
|
||||
const active = conditions.dayparts?.includes(daypart) ?? false
|
||||
return (
|
||||
<button
|
||||
key={daypart}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const current = conditions.dayparts ?? []
|
||||
const next = active
|
||||
? current.filter((d) => d !== daypart)
|
||||
: [...current, daypart]
|
||||
setConditions({ dayparts: next.length === 0 ? null : next })
|
||||
}}
|
||||
className={`rounded border px-2 py-1 text-xs ${
|
||||
active
|
||||
? 'border-primary text-primary'
|
||||
: 'border-border text-muted-foreground'
|
||||
}`}
|
||||
>
|
||||
{t(`admin.channels.dayparts.${daypart}`)}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">{t('admin.junctions.daypartsHint')}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>{t('admin.junctions.timeWindow')}</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
type="time"
|
||||
className="w-32"
|
||||
value={window?.from.slice(0, 5) ?? ''}
|
||||
onChange={(e) =>
|
||||
setConditions({
|
||||
timeWindow: e.target.value
|
||||
? { from: e.target.value, to: window?.to ?? '23:59' }
|
||||
: null,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<span className="text-muted-foreground">—</span>
|
||||
<Input
|
||||
type="time"
|
||||
className="w-32"
|
||||
value={window?.to.slice(0, 5) ?? ''}
|
||||
onChange={(e) =>
|
||||
setConditions({
|
||||
timeWindow: e.target.value
|
||||
? { from: window?.from ?? '00:00', to: e.target.value }
|
||||
: null,
|
||||
})
|
||||
}
|
||||
/>
|
||||
{window && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => setConditions({ timeWindow: null })}
|
||||
>
|
||||
{t('admin.junctions.clearWindow')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">{t('admin.junctions.timeWindowHint')}</p>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">{t('admin.junctions.timeWindowHint')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -782,6 +782,7 @@ export const en = {
|
||||
noSchedule: 'Schedule not built yet',
|
||||
airToday: 'Today',
|
||||
airCount: 'Entries for the day: {{count}}',
|
||||
airHidePast: 'Hide past entries',
|
||||
},
|
||||
junctions: {
|
||||
title: 'Junctions',
|
||||
@@ -830,7 +831,7 @@ export const en = {
|
||||
onlyOnChange: 'Only on show change',
|
||||
chance: 'Chance, %',
|
||||
chanceHint:
|
||||
'Chance and interval are per break; the roll comes from the generation seed, so rebuilds do not reshuffle breaks.',
|
||||
'The interval is per break. A fork rolls once for all its options: the picked variant rolls, and its chance is the chance of the whole fork. The roll comes from the generation seed, so rebuilds do not reshuffle breaks.',
|
||||
minInterval: 'No more often than once per, min',
|
||||
dayparts: 'Dayparts',
|
||||
daypartsHint: 'Nothing selected — the break plays in any daypart.',
|
||||
|
||||
@@ -777,6 +777,7 @@ export const ru = {
|
||||
noSchedule: 'Расписание ещё не построено',
|
||||
airToday: 'Сегодня',
|
||||
airCount: 'Записей за сутки: {{count}}',
|
||||
airHidePast: 'Скрывать прошедшее',
|
||||
},
|
||||
junctions: {
|
||||
title: 'Стыки',
|
||||
@@ -825,7 +826,7 @@ export const ru = {
|
||||
onlyOnChange: 'Только при смене шоу',
|
||||
chance: 'Вероятность, %',
|
||||
chanceHint:
|
||||
'Вероятность и интервал считаются по этой врезке отдельно; жребий берётся из seed генерации, поэтому пересборка не тасует врезки.',
|
||||
'Интервал считается по этой врезке отдельно. У развилки жребий один на всех: бросает выбранный вариант, и его вероятность — вероятность всей развилки. Жребий берётся из seed генерации, поэтому пересборка не тасует врезки.',
|
||||
minInterval: 'Не чаще, чем раз в, мин',
|
||||
dayparts: 'Дейпарты',
|
||||
daypartsHint: 'Ничего не выбрано — врезка идёт в любых дейпартах.',
|
||||
|
||||
Reference in New Issue
Block a user