feat: MC admin — collapsible cards, filters, photo preview, validation, archive
- Collapsible cards with title + hall in label, archive badge - Archived MCs sorted to bottom, dimmed with "Архив" badge - Cards have hover + focus-within gold border highlight - Date validation: error text for missing dates and invalid time ranges - Search by title/trainer + filter by date (upcoming/past) and hall - Photo preview with hover overlay (like trainer page) - ArrayEditor: hiddenItems, getItemBadge props, focus-within styles
This commit is contained in:
@@ -13,6 +13,8 @@ interface ArrayEditorProps<T> {
|
||||
addLabel?: string;
|
||||
collapsible?: boolean;
|
||||
getItemTitle?: (item: T, index: number) => string;
|
||||
getItemBadge?: (item: T, index: number) => React.ReactNode;
|
||||
hiddenItems?: Set<number>;
|
||||
}
|
||||
|
||||
export function ArrayEditor<T>({
|
||||
@@ -24,6 +26,8 @@ export function ArrayEditor<T>({
|
||||
addLabel = "Добавить",
|
||||
collapsible = false,
|
||||
getItemTitle,
|
||||
getItemBadge,
|
||||
hiddenItems,
|
||||
}: ArrayEditorProps<T>) {
|
||||
const [dragIndex, setDragIndex] = useState<number | null>(null);
|
||||
const [insertAt, setInsertAt] = useState<number | null>(null);
|
||||
@@ -146,14 +150,15 @@ export function ArrayEditor<T>({
|
||||
if (dragIndex === null || insertAt === null) {
|
||||
return items.map((item, i) => {
|
||||
const isCollapsed = collapsible && collapsed.has(i) && newItemIndex !== i;
|
||||
const isHidden = hiddenItems?.has(i) ?? false;
|
||||
const title = getItemTitle?.(item, i) || `#${i + 1}`;
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
ref={(el) => { itemRefs.current[i] = el; }}
|
||||
className={`rounded-lg border bg-neutral-900/50 mb-3 hover:border-white/25 hover:bg-neutral-800/50 transition-all ${
|
||||
className={`rounded-lg border bg-neutral-900/50 mb-3 hover:border-white/25 hover:bg-neutral-800/50 focus-within:border-gold/50 focus-within:bg-neutral-800 transition-all ${
|
||||
newItemIndex === i ? "border-gold/40 ring-1 ring-gold/20" : "border-white/10"
|
||||
}`}
|
||||
} ${isHidden ? "hidden" : ""}`}
|
||||
>
|
||||
<div className={`flex items-center justify-between gap-2 p-4 ${isCollapsed ? "" : "pb-0 mb-3"}`}>
|
||||
<div className="flex items-center gap-2 flex-1 min-w-0">
|
||||
@@ -170,6 +175,7 @@ export function ArrayEditor<T>({
|
||||
className="flex items-center gap-2 flex-1 min-w-0 text-left cursor-pointer group"
|
||||
>
|
||||
<span className="text-sm font-medium text-neutral-300 truncate group-hover:text-white transition-colors">{title}</span>
|
||||
{getItemBadge?.(item, i)}
|
||||
<ChevronDown size={14} className={`text-neutral-500 transition-transform duration-200 shrink-0 ${isCollapsed ? "" : "rotate-180"}`} />
|
||||
</button>
|
||||
)}
|
||||
@@ -231,7 +237,7 @@ export function ArrayEditor<T>({
|
||||
<div
|
||||
key={i}
|
||||
ref={(el) => { itemRefs.current[i] = el; }}
|
||||
className="rounded-lg border border-white/10 bg-neutral-900/50 p-4 mb-3 hover:border-white/25 hover:bg-neutral-800/50 transition-colors"
|
||||
className="rounded-lg border border-white/10 bg-neutral-900/50 p-4 mb-3 hover:border-white/25 hover:bg-neutral-800/50 focus-within:border-gold/50 focus-within:bg-neutral-800 transition-colors"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2 mb-3">
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user