143 lines
7.4 KiB
TypeScript
143 lines
7.4 KiB
TypeScript
import React from "react";
|
|
import type { Event } from "../../../services/api/types";
|
|
import type { ParticipantListRecord } from "../../../services/api/participantLists";
|
|
|
|
export type BulkDialogKind = null | "addTags" | "removeTags" | "assignEvents" | "newList" | "addToList";
|
|
|
|
export function ParticipantBulkDialogs({
|
|
bulkDialog,
|
|
bulkBusy,
|
|
selectedCount,
|
|
tagBulkInput,
|
|
onTagBulkInputChange,
|
|
newListName,
|
|
onNewListNameChange,
|
|
newListDesc,
|
|
onNewListDescChange,
|
|
eventsPick,
|
|
eventPickIds,
|
|
onToggleEventPick,
|
|
fixedLists,
|
|
addToListId,
|
|
onAddToListIdChange,
|
|
onClose,
|
|
onApplyTags,
|
|
onAssignEvents,
|
|
onCreateList,
|
|
onAddToList,
|
|
}: {
|
|
bulkDialog: BulkDialogKind;
|
|
bulkBusy: boolean;
|
|
selectedCount: number;
|
|
tagBulkInput: string;
|
|
onTagBulkInputChange: (v: string) => void;
|
|
newListName: string;
|
|
onNewListNameChange: (v: string) => void;
|
|
newListDesc: string;
|
|
onNewListDescChange: (v: string) => void;
|
|
eventsPick: Event[];
|
|
eventPickIds: Set<string>;
|
|
onToggleEventPick: (id: string) => void;
|
|
fixedLists: ParticipantListRecord[];
|
|
addToListId: string;
|
|
onAddToListIdChange: (id: string) => void;
|
|
onClose: () => void;
|
|
onApplyTags: (action: "add_tags" | "remove_tags") => void;
|
|
onAssignEvents: () => void;
|
|
onCreateList: () => void;
|
|
onAddToList: () => void;
|
|
}) {
|
|
const overlay = (children: React.ReactNode) => (
|
|
<div
|
|
role="dialog"
|
|
aria-modal="true"
|
|
style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.45)", zIndex: 205, display: "flex", alignItems: "center", justifyContent: "center" }}
|
|
onClick={() => !bulkBusy && onClose()}
|
|
>
|
|
<div
|
|
style={{ background: "var(--card)", borderRadius: "var(--radius-card)", padding: "20px", width: "min(440px, 92vw)", maxHeight: "80vh", overflow: "auto", boxShadow: "var(--shadow-xl)" }}
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
{children}
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
const footer = (primary: React.ReactNode) => (
|
|
<div style={{ display: "flex", justifyContent: "flex-end", gap: "8px", marginTop: "14px" }}>
|
|
<button type="button" disabled={bulkBusy} onClick={onClose} style={{ padding: "7px 14px", borderRadius: "var(--radius-button)", border: "1px solid var(--border)", background: "var(--card)", cursor: "pointer", fontSize: "var(--text-sm)" }}>Cancel</button>
|
|
{primary}
|
|
</div>
|
|
);
|
|
|
|
if (bulkDialog === "addTags" || bulkDialog === "removeTags") {
|
|
return overlay(
|
|
<>
|
|
<h3 style={{ fontFamily: "var(--font-inter)", fontSize: "var(--text-base)", margin: "0 0 8px" }}>{bulkDialog === "addTags" ? "Add tags" : "Remove tags"}</h3>
|
|
<p style={{ fontSize: "var(--text-xs)", color: "var(--muted)", margin: "0 0 12px", fontFamily: "var(--font-noto-sans)" }}>Apply to {selectedCount} selected (comma-separated).</p>
|
|
<input value={tagBulkInput} onChange={(e) => onTagBulkInputChange(e.target.value)} placeholder="e.g. VIP, Sponsor" style={{ width: "100%", padding: "8px 12px", borderRadius: "var(--radius-button)", border: "1px solid var(--border)", fontSize: "var(--text-sm)", boxSizing: "border-box" }} />
|
|
{footer(
|
|
<button type="button" disabled={bulkBusy} onClick={() => onApplyTags(bulkDialog === "addTags" ? "add_tags" : "remove_tags")} style={{ padding: "7px 14px", borderRadius: "var(--radius-button)", border: "none", background: "var(--primary)", color: "var(--primary-foreground)", cursor: "pointer", fontSize: "var(--text-sm)" }}>Apply</button>
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
|
|
if (bulkDialog === "assignEvents") {
|
|
return overlay(
|
|
<>
|
|
<h3 style={{ fontFamily: "var(--font-inter)", fontSize: "var(--text-base)", margin: "0 0 8px" }}>Assign to event(s)</h3>
|
|
<p style={{ fontSize: "var(--text-xs)", color: "var(--muted)", margin: "0 0 12px", fontFamily: "var(--font-noto-sans)" }}>{selectedCount} people · adds pending membership where missing.</p>
|
|
<div style={{ maxHeight: "240px", overflowY: "auto", border: "1px solid var(--border)", borderRadius: "var(--radius-button)", padding: "8px" }}>
|
|
{eventsPick.map((ev) => (
|
|
<label key={ev._id} style={{ display: "flex", alignItems: "center", gap: "8px", padding: "6px 4px", cursor: "pointer", fontSize: "var(--text-sm)", fontFamily: "var(--font-noto-sans)" }}>
|
|
<input type="checkbox" checked={eventPickIds.has(ev._id)} onChange={() => onToggleEventPick(ev._id)} />
|
|
{ev.name}
|
|
</label>
|
|
))}
|
|
</div>
|
|
{footer(
|
|
<button type="button" disabled={bulkBusy || eventPickIds.size === 0} onClick={onAssignEvents} style={{ padding: "7px 14px", borderRadius: "var(--radius-button)", border: "none", background: "var(--primary)", color: "var(--primary-foreground)", cursor: "pointer", fontSize: "var(--text-sm)" }}>Assign</button>
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
|
|
if (bulkDialog === "newList") {
|
|
return overlay(
|
|
<>
|
|
<h3 style={{ fontFamily: "var(--font-inter)", fontSize: "var(--text-base)", margin: "0 0 8px" }}>New custom list</h3>
|
|
<p style={{ fontSize: "var(--text-xs)", color: "var(--muted)", margin: "0 0 12px", fontFamily: "var(--font-noto-sans)" }}>Fixed list with {selectedCount} selected people.</p>
|
|
<input value={newListName} onChange={(e) => onNewListNameChange(e.target.value)} placeholder="List name" style={{ width: "100%", padding: "8px 12px", borderRadius: "var(--radius-button)", border: "1px solid var(--border)", marginBottom: "8px", boxSizing: "border-box", fontSize: "var(--text-sm)" }} />
|
|
<textarea value={newListDesc} onChange={(e) => onNewListDescChange(e.target.value)} placeholder="Description (optional)" rows={3} style={{ width: "100%", padding: "8px 12px", borderRadius: "var(--radius-button)", border: "1px solid var(--border)", boxSizing: "border-box", fontSize: "var(--text-sm)", resize: "vertical" }} />
|
|
{footer(
|
|
<button type="button" disabled={bulkBusy || !newListName.trim()} onClick={onCreateList} style={{ padding: "7px 14px", borderRadius: "var(--radius-button)", border: "none", background: "var(--primary)", color: "var(--primary-foreground)", cursor: "pointer", fontSize: "var(--text-sm)" }}>Create</button>
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
|
|
if (bulkDialog === "addToList") {
|
|
return overlay(
|
|
<>
|
|
<h3 style={{ fontFamily: "var(--font-inter)", fontSize: "var(--text-base)", margin: "0 0 8px" }}>Add to existing list</h3>
|
|
<p style={{ fontSize: "var(--text-xs)", color: "var(--muted)", margin: "0 0 12px", fontFamily: "var(--font-noto-sans)" }}>Append {selectedCount} people to a fixed list.</p>
|
|
{fixedLists.length === 0 ? (
|
|
<p style={{ fontSize: "var(--text-sm)", color: "var(--muted)" }}>No fixed lists yet. Create one first.</p>
|
|
) : (
|
|
<select value={addToListId} onChange={(e) => onAddToListIdChange(e.target.value)} style={{ width: "100%", padding: "8px 12px", borderRadius: "var(--radius-button)", border: "1px solid var(--border)", fontSize: "var(--text-sm)" }}>
|
|
{fixedLists.map((L) => (
|
|
<option key={L._id} value={L._id}>{L.name}</option>
|
|
))}
|
|
</select>
|
|
)}
|
|
{footer(
|
|
<button type="button" disabled={bulkBusy || fixedLists.length === 0 || !addToListId} onClick={onAddToList} style={{ padding: "7px 14px", borderRadius: "var(--radius-button)", border: "none", background: "var(--primary)", color: "var(--primary-foreground)", cursor: "pointer", fontSize: "var(--text-sm)" }}>Add</button>
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
|
|
return null;
|
|
}
|