feat: same-time checkbox for multi-day class groups

- "Одинаковое время" checkbox (default on) syncs time across all days
- Uncheck to set per-day times (e.g., Mon 12:00, Fri 18:00)
- Checkbox only appears when 2+ days selected
- Single day: just shows one time field, no checkbox
- Unified day selector for both new and edit modes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 13:52:59 +03:00
parent bfa59a8d18
commit fc523b2045

View File

@@ -254,6 +254,13 @@ function ClassModal({
const [dayTimes, setDayTimes] = useState<Record<string, string>>(initialDayTimes);
// "Same time for all days" — default true if all existing times match
const allTimesMatch = useMemo(() => {
const vals = Object.values(initialDayTimes);
return vals.length <= 1 || vals.every((t) => t === vals[0]);
}, [initialDayTimes]);
const [sameTime, setSameTime] = useState(allTimesMatch);
const selectedDays = new Set(Object.keys(dayTimes));
function toggleDay(day: string) {
@@ -265,13 +272,34 @@ function ClassModal({
delete next[day];
return next;
}
// New day gets time from current day
return { ...prev, [day]: prev[currentDay] || cls.time };
// New day gets time from current day or first existing
const refTime = sameTime
? (Object.values(prev)[0] || cls.time)
: (prev[currentDay] || cls.time);
return { ...prev, [day]: refTime };
});
}
function updateDayTime(day: string, time: string) {
setDayTimes((prev) => ({ ...prev, [day]: time }));
if (sameTime) {
// Update all days at once
setDayTimes((prev) => {
const next: Record<string, string> = {};
for (const d of Object.keys(prev)) next[d] = time;
return next;
});
} else {
setDayTimes((prev) => ({ ...prev, [day]: time }));
}
}
function updateSharedTime(time: string) {
setDraft({ ...draft, time });
setDayTimes((prev) => {
const next: Record<string, string> = {};
for (const d of Object.keys(prev)) next[d] = time;
return next;
});
}
// Compute what changed for the hint (edit mode only)
@@ -300,61 +328,25 @@ function ClassModal({
<div>
<label className="block text-sm text-neutral-400 mb-2">Дни</label>
{/* Selected days with per-day time (edit mode) or simple buttons (new mode) */}
{!isNew && selectedDays.size > 0 && (
<div className="space-y-1.5 mb-2">
{allDays.filter((d) => selectedDays.has(d.day)).map((d) => (
<div key={d.day} className="flex items-center gap-2">
<button
type="button"
onClick={() => toggleDay(d.day)}
className="shrink-0 rounded-lg px-2.5 py-1 text-xs font-medium bg-gold/20 text-gold border border-gold/40 min-w-[36px]"
>
{d.dayShort}
</button>
<div className="flex-1">
<TimeRangeField
label=""
value={dayTimes[d.day] || ""}
onChange={(v) => updateDayTime(d.day, v)}
/>
</div>
</div>
))}
</div>
)}
{/* Unselected days — toggle buttons */}
{/* Day toggle buttons */}
<div className="flex flex-wrap gap-1.5">
{isNew
? allDays.map((d) => {
const active = selectedDays.has(d.day);
return (
<button
key={d.day}
type="button"
onClick={() => toggleDay(d.day)}
className={`rounded-lg px-3 py-1.5 text-xs font-medium transition-all ${
active
? "bg-gold/20 text-gold border border-gold/40"
: "border border-white/10 text-neutral-500 hover:text-white hover:border-white/20"
}`}
>
{d.dayShort}
</button>
);
})
: allDays.filter((d) => !selectedDays.has(d.day)).map((d) => (
<button
key={d.day}
type="button"
onClick={() => toggleDay(d.day)}
className="rounded-lg px-3 py-1.5 text-xs font-medium border border-white/10 text-neutral-500 hover:text-white hover:border-white/20 transition-all"
>
+ {d.dayShort}
</button>
))
}
{allDays.map((d) => {
const active = selectedDays.has(d.day);
return (
<button
key={d.day}
type="button"
onClick={() => toggleDay(d.day)}
className={`rounded-lg px-3 py-1.5 text-xs font-medium transition-all ${
active
? "bg-gold/20 text-gold border border-gold/40"
: "border border-white/10 text-neutral-500 hover:text-white hover:border-white/20"
}`}
>
{d.dayShort}
</button>
);
})}
</div>
{!isNew && (addedDays.length > 0 || removedDays.length > 0) && (
@@ -374,21 +366,55 @@ function ClassModal({
</div>
)}
{/* Time — only for new class (edit mode has per-day times above) */}
{isNew && (
{/* Same time checkbox + time fields */}
{selectedDays.size > 1 && (
<label className="flex items-center gap-2 text-sm text-neutral-300 cursor-pointer select-none">
<input
type="checkbox"
checked={sameTime}
onChange={(e) => {
const checked = e.target.checked;
setSameTime(checked);
if (checked) {
// Sync all days to current day's time
const refTime = dayTimes[currentDay] || Object.values(dayTimes)[0] || cls.time;
setDayTimes((prev) => {
const next: Record<string, string> = {};
for (const d of Object.keys(prev)) next[d] = refTime;
return next;
});
}
}}
className="rounded border-white/20 bg-neutral-800 text-gold focus:ring-gold/50"
/>
Одинаковое время
</label>
)}
{sameTime || selectedDays.size <= 1 ? (
<TimeRangeField
label="Время"
value={draft.time}
onChange={(v) => {
setDraft({ ...draft, time: v });
// Update all selected day times
setDayTimes((prev) => {
const next: Record<string, string> = {};
for (const day of Object.keys(prev)) next[day] = v;
return next;
});
}}
value={Object.values(dayTimes)[0] || draft.time}
onChange={updateSharedTime}
/>
) : (
<div className="space-y-1.5">
<label className="block text-sm text-neutral-400">Время по дням</label>
{allDays.filter((d) => selectedDays.has(d.day)).map((d) => (
<div key={d.day} className="flex items-center gap-2">
<span className="shrink-0 text-xs font-medium text-neutral-400 min-w-[28px]">
{d.dayShort}
</span>
<div className="flex-1">
<TimeRangeField
label=""
value={dayTimes[d.day] || ""}
onChange={(v) => updateDayTime(d.day, v)}
/>
</div>
</div>
))}
</div>
)}
<SelectField