- {/* Day selector — always visible */}
+ {/* Day selector */}
{allDays.length > 1 && (
+
+ {/* Selected days with per-day time (edit mode) or simple buttons (new mode) */}
+ {!isNew && selectedDays.size > 0 && (
+
+ {allDays.filter((d) => selectedDays.has(d.day)).map((d) => (
+
+
+
+ updateDayTime(d.day, v)}
+ />
+
+
+ ))}
+
+ )}
+
+ {/* Unselected days — toggle buttons */}
- {allDays.map((d) => {
- const active = selectedDays.has(d.day);
- return (
-
- );
- })}
+ {isNew
+ ? allDays.map((d) => {
+ const active = selectedDays.has(d.day);
+ return (
+
+ );
+ })
+ : allDays.filter((d) => !selectedDays.has(d.day)).map((d) => (
+
+ ))
+ }
+
{!isNew && (addedDays.length > 0 || removedDays.length > 0) && (
{addedDays.length > 0 && (
@@ -357,11 +374,23 @@ function ClassModal({
)}
-
setDraft({ ...draft, time: v })}
- />
+ {/* Time — only for new class (edit mode has per-day times above) */}
+ {isNew && (
+ {
+ setDraft({ ...draft, time: v });
+ // Update all selected day times
+ setDayTimes((prev) => {
+ const next: Record = {};
+ for (const day of Object.keys(prev)) next[day] = v;
+ return next;
+ });
+ }}
+ />
+ )}
+
{
- onSave(draft, [...selectedDays]);
+ onSave(draft, dayTimes);
onClose();
}}
className="flex-1 rounded-lg bg-gold px-4 py-2.5 text-sm font-medium text-black hover:opacity-90 transition-opacity"
@@ -431,16 +460,12 @@ function CalendarGrid({
trainers,
addresses,
classTypes,
- classColors,
- onColorChange,
onChange,
}: {
location: ScheduleLocation;
trainers: string[];
addresses: string[];
classTypes: string[];
- classColors: Record;
- onColorChange: (typeName: string, color: string) => void;
onChange: (loc: ScheduleLocation) => void;
}) {
const [editingClass, setEditingClass] = useState<{
@@ -452,15 +477,14 @@ function CalendarGrid({
cls: ScheduleClass;
} | null>(null);
- // Compute color assignments (explicit + smart fallback)
- const colorAssignments = useMemo(
- () => buildColorAssignments(classTypes, classColors),
- [classTypes, classColors]
+ // Compute group-based colors for calendar blocks
+ const sortedDaysForColors = sortDaysByWeekday(location.days);
+ const groupColors = useMemo(
+ () => buildGroupColors(sortedDaysForColors),
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [JSON.stringify(sortedDaysForColors.map((d) => d.classes.map((c) => groupKey(c))))]
);
- // Color picker popup
- const [colorPicker, setColorPicker] = useState(null);
-
// Hover highlight state
const [hover, setHover] = useState<{ dayIndex: number; startMin: number } | null>(null);
@@ -683,7 +707,7 @@ function CalendarGrid({
const dragPreview = drag?.moved ? (() => {
const sourceDay = sortedDays[drag.sourceDayIndex];
const cls = sourceDay.classes[drag.classIndex];
- const colors = getTypeColor(cls.type, colorAssignments);
+ const colors = groupColors[groupKey(cls)] ?? "bg-neutral-600/80 border-neutral-500";
const top = minutesToY(drag.previewStartMin);
const height = (drag.durationMin / 60) * HOUR_HEIGHT;
const newStart = formatMinutes(drag.previewStartMin);
@@ -709,61 +733,6 @@ function CalendarGrid({
/>
- {/* Legend with color picker */}
-
- {classTypes.map((type) => {
- const colors = getTypeColor(type, colorAssignments);
- const bgClass = colors.split(" ")[0] || "bg-neutral-600/80";
- const isOpen = colorPicker === type;
- return (
-
-
- {isOpen && (() => {
- // Build used set from resolved assignments (includes both explicit & fallback)
- const usedColors = new Set(
- Object.entries(colorAssignments)
- .filter(([t]) => t !== type)
- .map(([, cls]) => COLOR_SWATCHES.find((s) => COLOR_MAP[s.value] === cls)?.value)
- .filter(Boolean)
- );
- return (
-
-
- {COLOR_SWATCHES.filter((c) => !usedColors.has(c.value)).map((c) => (
-
-
- );
- })()}
-
- );
- })}
-
-
{/* Calendar */}
{sortedDays.length > 0 && (
@@ -876,7 +845,7 @@ function CalendarGrid({
drag.classIndex === ci &&
drag.moved
}
- colorAssignments={colorAssignments}
+ groupColors={groupColors}
onClick={() => {
if (justDraggedRef.current) return;
setEditingClass({ dayIndex: di, classIndex: ci });
@@ -915,17 +884,15 @@ function CalendarGrid({
classTypes={classTypes}
allDays={sortedDays}
currentDay={sortedDays[editingClass.dayIndex]?.day}
- onSave={(updated, days) => {
+ onSave={(updated, dayTimes) => {
const original = editingData.cls;
- const selectedSet = new Set(days);
const updatedDays = location.days.map((d) => {
- const inSelected = selectedSet.has(d.day);
// Remove old matching group entries
let classes = d.classes.filter((c) => !isSameGroup(c, original));
- // Add updated class to selected days
- if (inSelected) {
- classes = [...classes, updated];
+ // Add updated class with per-day time
+ if (d.day in dayTimes) {
+ classes = [...classes, { ...updated, time: dayTimes[d.day] }];
}
return { ...d, classes };
});
@@ -952,11 +919,10 @@ function CalendarGrid({
classTypes={classTypes}
allDays={sortedDays}
currentDay={sortedDays[newClass.dayIndex]?.day}
- onSave={(created, days) => {
- const targetDayNames = new Set(days);
+ onSave={(created, dayTimes) => {
const updatedDays = location.days.map((d) => {
- if (targetDayNames.has(d.day)) {
- return { ...d, classes: [...d.classes, created] };
+ if (d.day in dayTimes) {
+ return { ...d, classes: [...d.classes, { ...created, time: dayTimes[d.day] }] };
}
return d;
});
@@ -975,8 +941,6 @@ export default function ScheduleEditorPage() {
const [trainers, setTrainers] = useState([]);
const [addresses, setAddresses] = useState([]);
const [classTypes, setClassTypes] = useState([]);
- const [classColors, setClassColors] = useState>({});
- const classesDataRef = useRef<{ title: string; items: { name: string; color?: string; [k: string]: unknown }[] } | null>(null);
useEffect(() => {
fetch("/api/admin/team")
@@ -995,36 +959,12 @@ export default function ScheduleEditorPage() {
fetch("/api/admin/sections/classes")
.then((r) => r.json())
- .then((classes: { title: string; items?: { name: string; color?: string }[] }) => {
- const items = classes.items ?? [];
- classesDataRef.current = { title: classes.title, items };
- setClassTypes(items.map((c) => c.name));
- const colors: Record = {};
- for (const item of items) {
- if (item.color) colors[item.name] = item.color;
- }
- setClassColors(colors);
+ .then((classes: { items?: { name: string }[] }) => {
+ setClassTypes((classes.items ?? []).map((c) => c.name));
})
.catch(() => {});
}, []);
- const handleColorChange = useCallback((typeName: string, color: string) => {
- setClassColors((prev) => ({ ...prev, [typeName]: color }));
-
- // Save to classes section
- const data = classesDataRef.current;
- if (!data) return;
- const updatedItems = data.items.map((item) =>
- item.name === typeName ? { ...item, color } : item
- );
- classesDataRef.current = { ...data, items: updatedItems };
- fetch("/api/admin/sections/classes", {
- method: "PUT",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ ...data, items: updatedItems }),
- }).catch(() => {});
- }, []);
-
return (
sectionKey="schedule" title="Расписание">
{(data, update) => {
@@ -1105,8 +1045,6 @@ export default function ScheduleEditorPage() {
trainers={trainers}
addresses={addresses}
classTypes={classTypes}
- classColors={classColors}
- onColorChange={handleColorChange}
onChange={updateLocation}
/>
)}