diff --git a/k-tv-frontend/app/(main)/dashboard/components/edit-channel-sheet.tsx b/k-tv-frontend/app/(main)/dashboard/components/edit-channel-sheet.tsx index 856b418..27aaed5 100644 --- a/k-tv-frontend/app/(main)/dashboard/components/edit-channel-sheet.tsx +++ b/k-tv-frontend/app/(main)/dashboard/components/edit-channel-sheet.tsx @@ -29,7 +29,8 @@ import type { RecyclePolicy, Weekday, } from "@/lib/types"; -import { WEEKDAYS } from "@/lib/types"; +import { WEEKDAYS, WEEKDAY_LABELS } from "@/lib/types"; +import { cn } from "@/lib/utils"; // --------------------------------------------------------------------------- // Local shared primitives (only used inside this file) @@ -366,6 +367,29 @@ export function EditChannelSheet({ }: EditChannelSheetProps) { const form = useChannelForm(channel); const [fieldErrors, setFieldErrors] = useState({}); + const [activeDay, setActiveDay] = useState('monday'); + const [copyTarget, setCopyTarget] = useState(''); + const [configHistoryOpen, setConfigHistoryOpen] = useState(false); + + const handleCopyTo = () => { + if (!copyTarget) return; + const sourceBlocks = form.dayBlocks[activeDay] ?? []; + if (copyTarget === 'all') { + const newDayBlocks = { ...form.dayBlocks }; + for (const day of WEEKDAYS) { + if (day !== activeDay) { + newDayBlocks[day] = sourceBlocks.map(b => ({ ...b, id: crypto.randomUUID() })); + } + } + form.setDayBlocks(newDayBlocks); + } else { + form.setDayBlocks({ + ...form.dayBlocks, + [copyTarget]: sourceBlocks.map(b => ({ ...b, id: crypto.randomUUID() })), + }); + } + setCopyTarget(''); + }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); @@ -375,9 +399,7 @@ export function EditChannelSheet({ name: form.name, description: form.description, timezone: form.timezone, - day_blocks: Object.fromEntries( - WEEKDAYS.map(d => [d, d === 'monday' ? form.blocks : []]) - ) as Record, + day_blocks: form.dayBlocks, recycle_policy: form.recyclePolicy, auto_schedule: form.autoSchedule, access_mode: form.accessMode, @@ -394,11 +416,7 @@ export function EditChannelSheet({ name: form.name, description: form.description, timezone: form.timezone, - schedule_config: { - day_blocks: Object.fromEntries( - WEEKDAYS.map(d => [d, d === 'monday' ? form.blocks : []]) - ) as Record, - }, + schedule_config: { day_blocks: form.dayBlocks }, recycle_policy: form.recyclePolicy, auto_schedule: form.autoSchedule, access_mode: form.accessMode !== "public" ? form.accessMode : "public", @@ -418,6 +436,7 @@ export function EditChannelSheet({ }); }; + return ( + {/* Day tab bar */} +
+ {WEEKDAYS.map(day => ( + + ))} + {/* Copy-to control */} +
+ Copy to + + +
+
+

@@ -559,31 +619,31 @@ export function EditChannelSheet({ type="button" variant="outline" size="xs" - onClick={() => form.addBlock()} + onClick={() => form.addBlock(activeDay)} className="border-zinc-700 text-zinc-300 hover:text-zinc-100" > - Add block + Add block for {WEEKDAY_LABELS[activeDay]}

form.setDayBlocks(prev => ({ ...prev, [activeDay]: blocks }))} onCreateBlock={(startMins, durationMins) => - form.addBlock(startMins, durationMins) + form.addBlock(activeDay, startMins, durationMins) } /> - {form.blocks.length === 0 ? ( + {(form.dayBlocks[activeDay] ?? []).length === 0 ? (

- No blocks yet. Drag on the timeline or click Add block. + No blocks for {WEEKDAY_LABELS[activeDay]}. Drag on the timeline or click Add block.

) : (
- {form.blocks.map((block, idx) => ( + {(form.dayBlocks[activeDay] ?? []).map((block, idx) => (
); } - const selectedIdx = form.blocks.findIndex( + const activeDayBlocks = form.dayBlocks[activeDay] ?? []; + const selectedIdx = activeDayBlocks.findIndex( (b) => b.id === form.selectedBlockId, ); const selectedBlock = - selectedIdx >= 0 ? form.blocks[selectedIdx] : null; + selectedIdx >= 0 ? activeDayBlocks[selectedIdx] : null; if (!selectedBlock) { return (
@@ -650,7 +711,7 @@ export function EditChannelSheet({ index={selectedIdx} errors={fieldErrors} providers={providers} - onChange={(b) => form.updateBlock(selectedIdx, b)} + onChange={(b) => form.updateBlock(activeDay, selectedIdx, b)} /> ); })()} @@ -665,6 +726,15 @@ export function EditChannelSheet({

)}
+
+ {/* TODO: ConfigHistorySheet — wired in Task 16 */} diff --git a/k-tv-frontend/hooks/use-channel-form.ts b/k-tv-frontend/hooks/use-channel-form.ts index eec99a8..8f6db17 100644 --- a/k-tv-frontend/hooks/use-channel-form.ts +++ b/k-tv-frontend/hooks/use-channel-form.ts @@ -9,7 +9,9 @@ import type { ProgrammingBlock, MediaFilter, RecyclePolicy, + Weekday, } from "@/lib/types"; +import { WEEKDAYS } from "@/lib/types"; export const WEBHOOK_PRESETS = { discord: `{ @@ -54,11 +56,17 @@ export function defaultBlock(startMins = 20 * 60, durationMins = 60): Programmin }; } +function emptyDayBlocks(): Record { + const result = {} as Record; + for (const d of WEEKDAYS) result[d] = []; + return result; +} + export function useChannelForm(channel: ChannelResponse | null) { const [name, setName] = useState(""); const [description, setDescription] = useState(""); const [timezone, setTimezone] = useState("UTC"); - const [blocks, setBlocks] = useState([]); + const [dayBlocks, setDayBlocks] = useState>(emptyDayBlocks); const [recyclePolicy, setRecyclePolicy] = useState({ cooldown_days: null, cooldown_generations: null, @@ -84,7 +92,10 @@ export function useChannelForm(channel: ChannelResponse | null) { setName(channel.name); setDescription(channel.description ?? ""); setTimezone(channel.timezone); - setBlocks(channel.schedule_config.day_blocks['monday'] ?? []); + setDayBlocks({ + ...emptyDayBlocks(), + ...channel.schedule_config.day_blocks, + }); setRecyclePolicy(channel.recycle_policy); setAutoSchedule(channel.auto_schedule); setAccessMode(channel.access_mode ?? "public"); @@ -110,20 +121,23 @@ export function useChannelForm(channel: ChannelResponse | null) { } }, [channel]); - const addBlock = (startMins = 20 * 60, durationMins = 60) => { + const addBlock = (day: Weekday, startMins = 20 * 60, durationMins = 60) => { const block = defaultBlock(startMins, durationMins); - setBlocks((prev) => [...prev, block]); + setDayBlocks((prev) => ({ ...prev, [day]: [...(prev[day] ?? []), block] })); setSelectedBlockId(block.id); }; - const updateBlock = (idx: number, block: ProgrammingBlock) => - setBlocks((prev) => prev.map((b, i) => (i === idx ? block : b))); + const updateBlock = (day: Weekday, idx: number, block: ProgrammingBlock) => + setDayBlocks((prev) => ({ + ...prev, + [day]: (prev[day] ?? []).map((b, i) => (i === idx ? block : b)), + })); - const removeBlock = (idx: number) => { - setBlocks((prev) => { - const next = prev.filter((_, i) => i !== idx); - if (selectedBlockId === prev[idx].id) setSelectedBlockId(null); - return next; + const removeBlock = (day: Weekday, idx: number) => { + setDayBlocks((prev) => { + const dayArr = prev[day] ?? []; + if (selectedBlockId === dayArr[idx]?.id) setSelectedBlockId(null); + return { ...prev, [day]: dayArr.filter((_, i) => i !== idx) }; }); }; @@ -147,8 +161,8 @@ export function useChannelForm(channel: ChannelResponse | null) { webhookFormat, setWebhookFormat, webhookBodyTemplate, setWebhookBodyTemplate, webhookHeaders, setWebhookHeaders, - // Blocks - blocks, setBlocks, + // Blocks (day-keyed) + dayBlocks, setDayBlocks, selectedBlockId, setSelectedBlockId, recyclePolicy, setRecyclePolicy, addBlock,