diff --git a/src/app/admin/_components/FormField.tsx b/src/app/admin/_components/FormField.tsx
index 5c67cf8..a5ab1fa 100644
--- a/src/app/admin/_components/FormField.tsx
+++ b/src/app/admin/_components/FormField.tsx
@@ -140,6 +140,21 @@ export function TimeRangeField({ label, value, onChange, onBlur }: TimeRangeFiel
}
}
+ function handleStartChange(newStart: string) {
+ // Reset end if start >= end
+ if (newStart && end && newStart >= end) {
+ update(newStart, "");
+ } else {
+ update(newStart, end);
+ }
+ }
+
+ function handleEndChange(newEnd: string) {
+ // Ignore if end <= start
+ if (start && newEnd && newEnd <= start) return;
+ update(start, newEnd);
+ }
+
return (
@@ -147,7 +162,7 @@ export function TimeRangeField({ label, value, onChange, onBlur }: TimeRangeFiel
update(e.target.value, end)}
+ onChange={(e) => handleStartChange(e.target.value)}
onBlur={onBlur}
className="flex-1 rounded-lg border border-white/10 bg-neutral-800 px-3 py-2.5 text-white outline-none focus:border-gold transition-colors"
/>
@@ -155,7 +170,7 @@ export function TimeRangeField({ label, value, onChange, onBlur }: TimeRangeFiel
update(start, e.target.value)}
+ onChange={(e) => handleEndChange(e.target.value)}
onBlur={onBlur}
className="flex-1 rounded-lg border border-white/10 bg-neutral-800 px-3 py-2.5 text-white outline-none focus:border-gold transition-colors"
/>
diff --git a/src/app/admin/schedule/page.tsx b/src/app/admin/schedule/page.tsx
index 8a3a4df..f814125 100644
--- a/src/app/admin/schedule/page.tsx
+++ b/src/app/admin/schedule/page.tsx
@@ -302,10 +302,57 @@ function ClassModal({
});
}
- // Compute what changed for the hint (edit mode only)
- const originalDays = new Set(Object.keys(initialDayTimes));
- const addedDays = [...selectedDays].filter((d) => !originalDays.has(d));
- const removedDays = [...originalDays].filter((d) => !selectedDays.has(d));
+ const [touched, setTouched] = useState(false);
+
+ // Validation
+ const errors = useMemo(() => {
+ const errs: string[] = [];
+ if (!draft.trainer) errs.push("Выберите тренера");
+ if (!draft.type) errs.push("Выберите тип занятия");
+ const times = Object.values(dayTimes);
+ for (const t of times) {
+ if (!t || !t.includes("–")) {
+ errs.push("Укажите время");
+ break;
+ }
+ const [s, e] = t.split("–").map((p) => timeToMinutes(p.trim()));
+ if (!s || !e) {
+ errs.push("Неверный формат времени");
+ break;
+ }
+ if (e <= s) {
+ errs.push("Время окончания должно быть позже начала");
+ break;
+ }
+ }
+ return errs;
+ }, [draft.trainer, draft.type, dayTimes]);
+
+ const isValid = errors.length === 0;
+
+ // Check for time overlaps on each selected day
+ const overlaps = useMemo(() => {
+ const result: { day: string; dayShort: string; conflicting: string[] }[] = [];
+ for (const [dayName, time] of Object.entries(dayTimes)) {
+ const dayData = allDays.find((d) => d.day === dayName);
+ if (!dayData || !time) continue;
+ const dayShort = DAYS.find((d) => d.day === dayName)?.dayShort || dayName;
+ // Build a temporary class to check overlap
+ const tempCls: ScheduleClass = { ...draft, time };
+ const conflicts: string[] = [];
+ for (const existing of dayData.classes) {
+ // Skip the class being edited (same group)
+ if (!isNew && isSameGroup(existing, cls)) continue;
+ if (hasOverlap(tempCls, existing)) {
+ conflicts.push(`${existing.time} ${existing.type} (${existing.trainer})`);
+ }
+ }
+ if (conflicts.length > 0) {
+ result.push({ day: dayName, dayShort, conflicting: conflicts });
+ }
+ }
+ return result;
+ }, [dayTimes, allDays, draft, cls, isNew]);
return (
@@ -349,20 +396,6 @@ function ClassModal({
})}
- {!isNew && (addedDays.length > 0 || removedDays.length > 0) && (
-
- {addedDays.length > 0 && (
-
- + {addedDays.map((d) => allDays.find((ad) => ad.day === d)?.dayShort).join(", ")}
-
- )}
- {removedDays.length > 0 && (
-
- − {removedDays.map((d) => allDays.find((ad) => ad.day === d)?.dayShort).join(", ")}
-
- )}
-
- )}
)}
@@ -451,14 +484,40 @@ function ClassModal({
+ {/* Overlap warning */}
+ {overlaps.length > 0 && (
+