Skip to content

Commit

Permalink
Implement temporary badge scanner search feature (#380)
Browse files Browse the repository at this point in the history
- Add **Scan Badge** button to header actions
- Load modal with badge scanner to update participants search filter
  • Loading branch information
taesungh authored Jan 28, 2024
1 parent fe1505f commit b536376
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 70 deletions.
169 changes: 99 additions & 70 deletions apps/site/src/app/admin/participants/components/ParticipantsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import CheckinDayIcon from "./CheckinDayIcon";
import ParticipantAction from "./ParticipantAction";
import ParticipantsFilters from "./ParticipantsFilters";
import RoleBadge from "./RoleBadge";
import SearchScannerModal from "./SearchScannerModal";

const FRIDAY = new Date("2024-01-26T12:00:00");
const SATURDAY = new Date("2024-01-27T12:00:00");
Expand Down Expand Up @@ -149,6 +150,7 @@ function ParticipantsTable({
sorting: {},
selection: {},
});

const allRoles = new Set(participants.map((p) => p.role));
const roleOptions = Array.from(allRoles).map((r) => ({ value: r, label: r }));
const allStatuses = new Set(participants.map((p) => p.status));
Expand Down Expand Up @@ -247,77 +249,104 @@ function ParticipantsTable({
</Box>
);

const [showScanner, setShowScanner] = useState(false);

const openScanner = () => {
setShowScanner(true);
};

const cancelScanner = () => {
setShowScanner(false);
};

const useScannerValue = (value: string) => {
actions.setFiltering(value);
setShowScanner(false);
};

return (
<Table
{...collectionProps}
header={
<Header counter={`(${participants.length})`}>Participants</Header>
}
columnDefinitions={columnDefinitions}
visibleColumns={preferences.visibleContent}
items={items}
loading={loading}
loadingText="Loading participants"
variant="full-page"
stickyColumns={{ first: 1, last: 0 }}
trackBy="_id"
empty={emptyMessage}
filter={
<ParticipantsFilters
filteredItemsCount={filteredItemsCount}
filterProps={filterProps}
roles={roleOptions}
selectedRoles={filterRole}
setSelectedRoles={setFilterRole}
statuses={statusOptions}
selectedStatuses={filterStatus}
setSelectedStatuses={setFilterStatus}
/>
}
pagination={
<Pagination
{...paginationProps}
ariaLabels={{
nextPageLabel: "Next page",
pageLabel: (pageNumber) => `Go to page ${pageNumber}`,
previousPageLabel: "Previous page",
}}
/>
}
preferences={
<CollectionPreferences
pageSizePreference={{
title: "Select page size",
options: [
{ value: 20, label: "20 people" },
{ value: 50, label: "50 people" },
{ value: 100, label: "100 people" },
],
}}
visibleContentPreference={{
title: "Select visible columns",
options: [
{
label: "Participant info",
options: columnDefinitions.map(({ id, header }) => ({
id,
label: header,
})),
},
],
}}
cancelLabel="Cancel"
confirmLabel="Confirm"
title="Preferences"
preferences={preferences}
onConfirm={({ detail }) =>
setPreferences(
detail as { pageSize: number; visibleContent: Array<string> },
)
}
/>
}
/>
<>
<SearchScannerModal
onDismiss={cancelScanner}
onConfirm={useScannerValue}
show={showScanner}
/>
<Table
{...collectionProps}
header={
<Header
counter={`(${participants.length})`}
actions={<Button onClick={openScanner}>Scan Badge</Button>}
>
Participants
</Header>
}
columnDefinitions={columnDefinitions}
visibleColumns={preferences.visibleContent}
items={items}
loading={loading}
loadingText="Loading participants"
variant="full-page"
stickyColumns={{ first: 1, last: 0 }}
trackBy="_id"
empty={emptyMessage}
filter={
<ParticipantsFilters
filteredItemsCount={filteredItemsCount}
filterProps={filterProps}
roles={roleOptions}
selectedRoles={filterRole}
setSelectedRoles={setFilterRole}
statuses={statusOptions}
selectedStatuses={filterStatus}
setSelectedStatuses={setFilterStatus}
/>
}
pagination={
<Pagination
{...paginationProps}
ariaLabels={{
nextPageLabel: "Next page",
pageLabel: (pageNumber) => `Go to page ${pageNumber}`,
previousPageLabel: "Previous page",
}}
/>
}
preferences={
<CollectionPreferences
pageSizePreference={{
title: "Select page size",
options: [
{ value: 20, label: "20 people" },
{ value: 50, label: "50 people" },
{ value: 100, label: "100 people" },
],
}}
visibleContentPreference={{
title: "Select visible columns",
options: [
{
label: "Participant info",
options: columnDefinitions.map(({ id, header }) => ({
id,
label: header,
})),
},
],
}}
cancelLabel="Cancel"
confirmLabel="Confirm"
title="Preferences"
preferences={preferences}
onConfirm={({ detail }) =>
setPreferences(
detail as { pageSize: number; visibleContent: Array<string> },
)
}
/>
}
/>
</>
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { useCallback } from "react";

import Box from "@cloudscape-design/components/box";
import Button from "@cloudscape-design/components/button";
import Modal from "@cloudscape-design/components/modal";

import BadgeScanner from "@/lib/admin/BadgeScanner";

export interface SearchScannerProps {
onDismiss: () => void;
onConfirm: (value: string) => void;
show: boolean;
}

function SearchScannerModal({
onDismiss,
onConfirm,
show,
}: SearchScannerProps) {
const onScanSuccess = useCallback(
(decodedText: string) => {
onConfirm(decodedText);
},
[onConfirm],
);

return (
<Modal
onDismiss={onDismiss}
visible={show}
footer={
<Box float="right">
<Button variant="link" onClick={onDismiss}>
Cancel
</Button>
</Box>
}
header="Scan badge"
>
{show && <BadgeScanner onSuccess={onScanSuccess} onError={() => null} />}
</Modal>
);
}

export default SearchScannerModal;

0 comments on commit b536376

Please sign in to comment.