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:
@@ -254,6 +254,13 @@ function ClassModal({
|
|||||||
|
|
||||||
const [dayTimes, setDayTimes] = useState<Record<string, string>>(initialDayTimes);
|
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));
|
const selectedDays = new Set(Object.keys(dayTimes));
|
||||||
|
|
||||||
function toggleDay(day: string) {
|
function toggleDay(day: string) {
|
||||||
@@ -265,13 +272,34 @@ function ClassModal({
|
|||||||
delete next[day];
|
delete next[day];
|
||||||
return next;
|
return next;
|
||||||
}
|
}
|
||||||
// New day gets time from current day
|
// New day gets time from current day or first existing
|
||||||
return { ...prev, [day]: prev[currentDay] || cls.time };
|
const refTime = sameTime
|
||||||
|
? (Object.values(prev)[0] || cls.time)
|
||||||
|
: (prev[currentDay] || cls.time);
|
||||||
|
return { ...prev, [day]: refTime };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateDayTime(day: string, time: string) {
|
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)
|
// Compute what changed for the hint (edit mode only)
|
||||||
@@ -300,61 +328,25 @@ function ClassModal({
|
|||||||
<div>
|
<div>
|
||||||
<label className="block text-sm text-neutral-400 mb-2">Дни</label>
|
<label className="block text-sm text-neutral-400 mb-2">Дни</label>
|
||||||
|
|
||||||
{/* Selected days with per-day time (edit mode) or simple buttons (new mode) */}
|
{/* Day toggle buttons */}
|
||||||
{!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 */}
|
|
||||||
<div className="flex flex-wrap gap-1.5">
|
<div className="flex flex-wrap gap-1.5">
|
||||||
{isNew
|
{allDays.map((d) => {
|
||||||
? allDays.map((d) => {
|
const active = selectedDays.has(d.day);
|
||||||
const active = selectedDays.has(d.day);
|
return (
|
||||||
return (
|
<button
|
||||||
<button
|
key={d.day}
|
||||||
key={d.day}
|
type="button"
|
||||||
type="button"
|
onClick={() => toggleDay(d.day)}
|
||||||
onClick={() => toggleDay(d.day)}
|
className={`rounded-lg px-3 py-1.5 text-xs font-medium transition-all ${
|
||||||
className={`rounded-lg px-3 py-1.5 text-xs font-medium transition-all ${
|
active
|
||||||
active
|
? "bg-gold/20 text-gold border border-gold/40"
|
||||||
? "bg-gold/20 text-gold border border-gold/40"
|
: "border border-white/10 text-neutral-500 hover:text-white hover:border-white/20"
|
||||||
: "border border-white/10 text-neutral-500 hover:text-white hover:border-white/20"
|
}`}
|
||||||
}`}
|
>
|
||||||
>
|
{d.dayShort}
|
||||||
{d.dayShort}
|
</button>
|
||||||
</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>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!isNew && (addedDays.length > 0 || removedDays.length > 0) && (
|
{!isNew && (addedDays.length > 0 || removedDays.length > 0) && (
|
||||||
@@ -374,21 +366,55 @@ function ClassModal({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Time — only for new class (edit mode has per-day times above) */}
|
{/* Same time checkbox + time fields */}
|
||||||
{isNew && (
|
{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
|
<TimeRangeField
|
||||||
label="Время"
|
label="Время"
|
||||||
value={draft.time}
|
value={Object.values(dayTimes)[0] || draft.time}
|
||||||
onChange={(v) => {
|
onChange={updateSharedTime}
|
||||||
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;
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<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
|
<SelectField
|
||||||
|
|||||||
Reference in New Issue
Block a user