feat(campaign): live plan timeline preview in create form
Above the plan editor, adapt each plan entry (offset, scenario, count) into WindowTimeline markers coloured per scenario with a count badge, re-rendering live as the form changes via Form.useWatch. Empty plan shows a placeholder. Purely form-local, no backend change. (v0.6 ticket 09)
This commit is contained in:
parent
f4db90a989
commit
b295f7370d
@ -17,6 +17,7 @@ import {
|
|||||||
import { passRateColor } from '../utils/colors'
|
import { passRateColor } from '../utils/colors'
|
||||||
import { shortDateTime } from '../utils/date'
|
import { shortDateTime } from '../utils/date'
|
||||||
import { deriveTimeScale, acceleratedDuration, formatScale } from '../utils/campaignTime'
|
import { deriveTimeScale, acceleratedDuration, formatScale } from '../utils/campaignTime'
|
||||||
|
import WindowTimeline, { type TimelineMarker } from '../components/WindowTimeline'
|
||||||
import { useResource } from '../hooks/useResource'
|
import { useResource } from '../hooks/useResource'
|
||||||
import { usePolling } from '../hooks/usePolling'
|
import { usePolling } from '../hooks/usePolling'
|
||||||
import { useTabStore } from '../stores/tabStore'
|
import { useTabStore } from '../stores/tabStore'
|
||||||
@ -99,6 +100,25 @@ export default function CampaignsPage() {
|
|||||||
const campaigns = data?.campaigns ?? []
|
const campaigns = data?.campaigns ?? []
|
||||||
const targets = data?.targets ?? []
|
const targets = data?.targets ?? []
|
||||||
const scenarios = data?.scenarios ?? []
|
const scenarios = data?.scenarios ?? []
|
||||||
|
const scenarioNames = Object.fromEntries(scenarios.map((s) => [s.id, s.name]))
|
||||||
|
|
||||||
|
const wPlan = (Form.useWatch('plan', form) as PlanFormEntry[] | undefined) ?? []
|
||||||
|
const planMarkers: TimelineMarker[] = wPlan
|
||||||
|
.map((e, i) => ({ e, i }))
|
||||||
|
.filter(({ e }) => e && e.scenario_id)
|
||||||
|
.map(({ e, i }) => {
|
||||||
|
const name = scenarioNames[e.scenario_id as string] ?? (e.scenario_id as string).slice(0, 8)
|
||||||
|
const count = e.count ?? 1
|
||||||
|
const offsetHours = e.offset_hours ?? 0
|
||||||
|
return {
|
||||||
|
key: `plan-${i}`,
|
||||||
|
offsetSeconds: offsetHours * 3600,
|
||||||
|
colorKey: e.scenario_id as string,
|
||||||
|
label: name,
|
||||||
|
badge: count > 1 ? `×${count}` : undefined,
|
||||||
|
tooltip: `${name} · +${offsetHours}h · ${count} 次`,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Poll the list while this tab is active and a campaign is still working —
|
// Poll the list while this tab is active and a campaign is still working —
|
||||||
// compressed dev-line campaigns change fast. Stop once all are terminal.
|
// compressed dev-line campaigns change fast. Stop once all are terminal.
|
||||||
@ -412,6 +432,25 @@ export default function CampaignsPage() {
|
|||||||
{acceleratedDuration(wWindow, derivedScale)}
|
{acceleratedDuration(wWindow, derivedScale)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: 4, color: colors.textSecondary }}>计划预览</div>
|
||||||
|
<div style={{
|
||||||
|
border: `1px solid ${colors.border}`, borderRadius: 8,
|
||||||
|
padding: '12px 8px', marginBottom: 12,
|
||||||
|
}}>
|
||||||
|
{planMarkers.length > 0 ? (
|
||||||
|
<WindowTimeline
|
||||||
|
windowSeconds={wWindow}
|
||||||
|
markers={planMarkers}
|
||||||
|
colorLabels={scenarioNames}
|
||||||
|
showLegend
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div style={{ textAlign: 'center', color: colors.textMuted, padding: '12px 0' }}>
|
||||||
|
选择场景并设置偏移后,将在此按时间轴预览计划
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style={{ marginBottom: 8, color: colors.textSecondary }}>活动计划(在窗口的哪些时段跑哪个场景、跑几次)</div>
|
<div style={{ marginBottom: 8, color: colors.textSecondary }}>活动计划(在窗口的哪些时段跑哪个场景、跑几次)</div>
|
||||||
<Form.List name="plan" rules={[{ validator: async (_, plan) => { if (!plan || plan.length < 1) return Promise.reject(new Error('至少一条计划条目')) } }]}>
|
<Form.List name="plan" rules={[{ validator: async (_, plan) => { if (!plan || plan.length < 1) return Promise.reject(new Error('至少一条计划条目')) } }]}>
|
||||||
{(fields, { add, remove }, { errors }) => (
|
{(fields, { add, remove }, { errors }) => (
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user