From b1557453be8fe84bc421aaa7d5fe268c8a2a05da Mon Sep 17 00:00:00 2001 From: inmake Date: Wed, 3 Dec 2025 17:37:35 +0500 Subject: [PATCH] Enhance Schedule component by resetting time fields on cancel and draft mode toggle; improve session scheduling logic to prevent conflicts by adjusting start times if necessary. --- client/src/components/Schedule.tsx | 13 ++++++-- server/src/routes/scheduledSessions.ts | 46 ++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/client/src/components/Schedule.tsx b/client/src/components/Schedule.tsx index f0427f2..9dc861f 100644 --- a/client/src/components/Schedule.tsx +++ b/client/src/components/Schedule.tsx @@ -96,6 +96,9 @@ function Schedule({ selectedDay, slots, events }: Props) { function handleClickCancel() { setDraftMode(false); + setStartTime(""); + setEndTime(""); + setDuration(undefined); } async function addSchesuledSession() { @@ -129,6 +132,9 @@ function Schedule({ selectedDay, slots, events }: Props) { function handleClickScheduleDemo() { setDateForInstantStart(undefined); setStartAt(new Date()); + setStartTime(""); + setEndTime(""); + setDuration(undefined); setDraftMode(true); } @@ -184,9 +190,12 @@ function Schedule({ selectedDay, slots, events }: Props) { useEffect(() => { function handleEscKey(e: KeyboardEvent) { - if (!draftMode && e.key !== "Escape") return; + if (!draftMode || e.key !== "Escape") return; setDraftMode(false); + setStartTime(""); + setEndTime(""); + setDuration(undefined); } document.addEventListener("keyup", handleEscKey, false); @@ -194,7 +203,7 @@ function Schedule({ selectedDay, slots, events }: Props) { return () => { document.removeEventListener("keyup", handleEscKey, false); }; - }, []); + }, [draftMode]); return (
diff --git a/server/src/routes/scheduledSessions.ts b/server/src/routes/scheduledSessions.ts index 6bec1b6..6916be1 100644 --- a/server/src/routes/scheduledSessions.ts +++ b/server/src/routes/scheduledSessions.ts @@ -4,6 +4,7 @@ import Build from "../models/Build.js"; import Schedule from "../models/Schedule.js"; import { addMinutes, + addSeconds, areIntervalsOverlapping, endOfDay, isValid, @@ -78,7 +79,7 @@ router.get("/:buildId", async (req, res) => { router.post("/", async (req, res) => { const { companyId, buildId, slot, startAt, client, duration } = req.body; - const startAtISO = parseISO(startAt); + let startAtISO = parseISO(startAt); if (!isValid(startAtISO)) { return res.status(400).json({ @@ -96,9 +97,50 @@ router.post("/", async (req, res) => { }); } + // Check for conflicts at the same minute and adjust time if needed + // This prevents multiple sessions from starting simultaneously on the same server + // We check all sessions regardless of company, build, or slot to prevent server conflicts + let adjustedStartAt = startAtISO; + let maxAttempts = 100; // Prevent infinite loop + let attempts = 0; + + while (attempts < maxAttempts) { + // Find sessions starting within 10 seconds of our target time + // This ensures sessions don't start simultaneously on the same session server + const conflictingSessions = await ScheduledSession.find({ + startAt: { + $gte: adjustedStartAt, + $lt: addSeconds(adjustedStartAt, 10), + }, + }); + + if (conflictingSessions.length === 0) { + // No conflict found, use this time + break; + } + + // Find the latest conflicting session and schedule after it + // This gives the first build time to start and fill video memory + // so the second build will start on another server with more available memory + const latestSession = conflictingSessions.reduce((latest, session) => + new Date(session.startAt) > new Date(latest.startAt) ? session : latest + ); + + adjustedStartAt = addSeconds(new Date(latestSession.startAt), 10); + attempts++; + } + + if (attempts >= maxAttempts) { + return res.status(400).json({ + status: "error", + message: "Не удалось найти свободное время для планирования сеанса", + }); + } + + startAtISO = adjustedStartAt; const endAtISO = addMinutes(startAtISO, duration); - // Check for overlapping sessions first + // Check for overlapping sessions in the same slot const scheduledSessions = await ScheduledSession.find({ companyId, buildId,