feat(frontend): weekly grid editor with day tabs and copy shortcut

This commit is contained in:
2026-03-17 14:46:34 +01:00
parent c0da075f03
commit ba6abad602
2 changed files with 119 additions and 34 deletions

View File

@@ -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<Weekday, ProgrammingBlock[]> {
const result = {} as Record<Weekday, ProgrammingBlock[]>;
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<ProgrammingBlock[]>([]);
const [dayBlocks, setDayBlocks] = useState<Record<Weekday, ProgrammingBlock[]>>(emptyDayBlocks);
const [recyclePolicy, setRecyclePolicy] = useState<RecyclePolicy>({
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,