Skip to content

Commit 32172f8

Browse files
committed
Add lead title combobox for data standardization
1 parent 77255a0 commit 32172f8

File tree

3 files changed

+63
-8
lines changed

3 files changed

+63
-8
lines changed

src/common/types/organizations.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ export const orgLeadEntry = z.object({
1212
title: z.optional(z.string())
1313
})
1414

15+
export const leadTitleSuggestions = ["Chair", "Co-chair", "Admin", "Lead", "Helper"];
16+
1517
export const MAX_ORG_DESCRIPTION_CHARS = 125;
1618
export type LeadEntry = z.infer<typeof orgLeadEntry>;
1719

src/ui/pages/organization/ManageOrganizationForm.test.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,11 +445,16 @@ describe("ManageOrganizationForm - Lead Management Tests", () => {
445445
await waitFor(() => {
446446
expect(screen.getByText("John Doe")).toBeInTheDocument();
447447
expect(screen.getByText("jdoe@illinois.edu")).toBeInTheDocument();
448-
expect(screen.getByText("Chair")).toBeInTheDocument();
449448
expect(screen.getByText("Jane Smith")).toBeInTheDocument();
450449
expect(screen.getByText("jsmith@illinois.edu")).toBeInTheDocument();
451450
expect(screen.getByText("Vice Chair")).toBeInTheDocument();
452451
});
452+
453+
const table = screen.getByRole("table");
454+
expect(table).toHaveTextContent("John Doe");
455+
expect(table).toHaveTextContent("Chair");
456+
expect(table).toHaveTextContent("Jane Smith");
457+
expect(table).toHaveTextContent("Vice Chair");
453458
});
454459

455460
it("shows 'No leads found' when there are no leads", async () => {

src/ui/pages/organization/ManageOrganizationForm.tsx

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@ import {
1818
Avatar,
1919
Modal,
2020
Divider,
21+
Combobox,
22+
useCombobox,
23+
InputBase,
2124
} from "@mantine/core";
2225
import { useForm } from "@mantine/form";
2326
import { notifications } from "@mantine/notifications";
2427
import { IconPlus, IconTrash, IconUserPlus } from "@tabler/icons-react";
2528
import {
2629
LeadEntry,
30+
leadTitleSuggestions,
2731
MAX_ORG_DESCRIPTION_CHARS,
2832
setOrganizationMetaBody,
2933
validOrgLinkTypes,
@@ -65,6 +69,15 @@ export const ManageOrganizationForm: React.FC<ManageOrganizationFormProps> = ({
6569
const [newLeadEmail, setNewLeadEmail] = useState("");
6670
const [newLeadTitle, setNewLeadTitle] = useState("");
6771

72+
// Combobox for title suggestions
73+
const combobox = useCombobox({
74+
onDropdownClose: () => combobox.resetSelectedOption(),
75+
});
76+
77+
const filteredTitles = leadTitleSuggestions.filter((title) =>
78+
title.toLowerCase().includes(newLeadTitle.toLowerCase()),
79+
);
80+
6881
const form = useForm({
6982
validate: zodResolver(setOrganizationMetaBody),
7083
initialValues: {
@@ -348,7 +361,9 @@ export const ManageOrganizationForm: React.FC<ManageOrganizationFormProps> = ({
348361
Organization Leads
349362
</Title>
350363
<Text size="sm" c="dimmed" mb="md">
351-
Manage who has leadership permissions for this organization
364+
These users will be your Executive Council representatives and
365+
will be given management permissions for your org. These users
366+
must be paid members.
352367
</Text>
353368

354369
<Table verticalSpacing="sm" highlightOnHover>
@@ -460,12 +475,45 @@ export const ManageOrganizationForm: React.FC<ManageOrganizationFormProps> = ({
460475
value={newLeadEmail}
461476
onChange={(e) => setNewLeadEmail(e.currentTarget.value)}
462477
/>
463-
<TextInput
464-
label="Lead Title"
465-
placeholder="Chair"
466-
value={newLeadTitle}
467-
onChange={(e) => setNewLeadTitle(e.currentTarget.value)}
468-
/>
478+
<Combobox
479+
store={combobox}
480+
onOptionSubmit={(val) => {
481+
setNewLeadTitle(val);
482+
combobox.closeDropdown();
483+
}}
484+
>
485+
<Combobox.Target>
486+
<InputBase
487+
label="Lead Title"
488+
placeholder="Chair"
489+
value={newLeadTitle}
490+
onChange={(e) => {
491+
setNewLeadTitle(e.currentTarget.value);
492+
combobox.openDropdown();
493+
combobox.updateSelectedOptionIndex();
494+
}}
495+
onClick={() => combobox.openDropdown()}
496+
onFocus={() => combobox.openDropdown()}
497+
onBlur={() => combobox.closeDropdown()}
498+
rightSection={<Combobox.Chevron />}
499+
rightSectionPointerEvents="none"
500+
/>
501+
</Combobox.Target>
502+
503+
<Combobox.Dropdown>
504+
<Combobox.Options>
505+
{filteredTitles.length > 0 ? (
506+
filteredTitles.map((title) => (
507+
<Combobox.Option value={title} key={title}>
508+
{title}
509+
</Combobox.Option>
510+
))
511+
) : (
512+
<Combobox.Empty>No matches found</Combobox.Empty>
513+
)}
514+
</Combobox.Options>
515+
</Combobox.Dropdown>
516+
</Combobox>
469517
</Stack>
470518

471519
<Button

0 commit comments

Comments
 (0)