fix: schedule status system — auto-key, config order, label lookup

- Auto-generate status key from label (admin doesn't need to set keys)
- Remove visible key field from status config editor
- Order statuses/levels in filters by config order (matches admin panel)
- Shared findStatusConfig() for robust label lookup (by key, label, or derived key)
- Custom status badges in DayCard, GroupCard, MobileSchedule
- Simplified filter logic with clsStatus helper
- Removed dead code: TIME_PRESETS, StatusFilter type
- SelectField: blur input after selection to prevent re-open
This commit is contained in:
2026-03-28 00:33:55 +03:00
parent b322c969f2
commit bdeedcfcc8
9 changed files with 102 additions and 37 deletions
+31 -14
View File
@@ -192,19 +192,37 @@ export function Schedule({ data: schedule, scheduleConfig, classItems, teamMembe
for (const cls of day.classes) {
typeSet.add(cls.type);
trainerSet.add(cls.trainer);
if (cls.status) statusSet.add(cls.status);
if (cls.hasSlots) statusSet.add("hasSlots");
if (cls.recruiting) statusSet.add("recruiting");
const clsStatus = cls.status || (cls.recruiting ? "recruiting" : cls.hasSlots ? "hasSlots" : "");
if (clsStatus) statusSet.add(clsStatus);
if (cls.level) levelSet.add(cls.level);
}
}
// Also include all configured statuses/levels so they appear in filters
if (scheduleConfig?.statuses) {
for (const s of scheduleConfig.statuses) if (s.key) statusSet.add(s.key);
}
if (scheduleConfig?.levels) {
for (const l of scheduleConfig.levels) if (l.value) levelSet.add(l.value);
}
// Order statuses by config order, then any extras from data
const configStatusOrder = (scheduleConfig?.statuses ?? []).map((s) => s.key).filter(Boolean);
const orderedStatuses = [
...configStatusOrder.filter((k) => statusSet.has(k)),
...Array.from(statusSet).filter((k) => !configStatusOrder.includes(k)),
];
// Order levels by config order
const configLevelOrder = (scheduleConfig?.levels ?? []).map((l) => l.value).filter(Boolean);
const orderedLevels = [
...configLevelOrder.filter((v) => levelSet.has(v)),
...Array.from(levelSet).filter((v) => !configLevelOrder.includes(v)),
];
return {
types: Array.from(typeSet).sort(),
availableStatuses: Array.from(statusSet),
levels: Array.from(levelSet).sort(),
availableStatuses: orderedStatuses,
levels: orderedLevels,
trainerNames: Array.from(trainerSet).sort(),
};
}, [activeDays]);
}, [activeDays, scheduleConfig]);
// Parse time range for filtering
const activeTimeRange = isTimeFilterActive(filterTime)
@@ -227,19 +245,17 @@ export function Schedule({ data: schedule, scheduleConfig, classItems, teamMembe
.map((day) => ({
...day,
classes: day.classes.filter(
(cls) =>
(filterTrainerSet.size === 0 || filterTrainerSet.has(cls.trainer)) &&
(cls) => {
const clsStatus = cls.status || (cls.recruiting ? "recruiting" : cls.hasSlots ? "hasSlots" : "");
return (filterTrainerSet.size === 0 || filterTrainerSet.has(cls.trainer)) &&
(filterTypes.size === 0 || filterTypes.has(cls.type)) &&
(filterStatusSet.size === 0 ||
(cls.status && filterStatusSet.has(cls.status as StatusTag)) ||
(filterStatusSet.has("hasSlots" as StatusTag) && cls.hasSlots) ||
(filterStatusSet.has("recruiting" as StatusTag) && cls.recruiting)) &&
(filterStatusSet.size === 0 || (clsStatus && filterStatusSet.has(clsStatus))) &&
(!filterLevel || cls.level === filterLevel) &&
(!activeTimeRange || (() => {
const m = startTimeMinutes(cls.time);
return m >= activeTimeRange[0] && m < activeTimeRange[1];
})())
),
})());
}),
}))
.filter((day) => day.classes.length > 0);
}, [activeDays, filterTrainerSet, filterTypes, filterStatusSet, filterLevel, filterTime, activeTimeRange, filterDaySet]);
@@ -443,6 +459,7 @@ export function Schedule({ data: schedule, scheduleConfig, classItems, teamMembe
showLocation={isAllMode}
onBook={(v) => dispatch({ type: "SET_BOOKING", value: v })}
trainerPhotos={trainerPhotos}
scheduleConfig={scheduleConfig}
/>
</Reveal>
)}