Lesson 6 Lab — Staff Card Grid: Conditional Badges, Skeletons, and a Dropdown¶
Apply Lesson 6's ideas to your Staff card grid. You already have StaffPage
(fetch + map) and StaffCard (extracted in Lesson 5); here you round out the card, add a
3-dots menu, and add skeleton loading placeholders. The guide built a table
(Orders); this is the same conditional-rendering + skeleton work on a card grid.
Prerequisite: your API is running on the HTTP profile (
http, nothttps) with Staff seed data loaded. You're extending theStaffPage+StaffCardfrom Lesson 5.No status badge or filter here — Staff has role flags, not a workflow status, so you use the plain
{flag && <badge>}conditional (the&&shape), not thegetTextBackgroundByStatuslookup, and there's nouseSearchParamsfilter.
Steps¶
Same arc as the guide's applicable parts: round out the card → add the dropdown → skeletons.
- Round out
StaffCard. First add aformatPhoneNumberhelper tosrc/utility/formatUtilities.ts— the file already exists from the guide (§3, where you addedgetTextBackgroundByStatus). This little formatter is tangential to this lesson, so here it is ready to paste alongside the badge helper:src/utility/formatUtilities.tsThen, alongside name and username, show phone (export function formatPhoneNumber(phoneNumber: string) { if (!phoneNumber) return; const area = phoneNumber.substring(0, 3); const prefix = phoneNumber.substring(3, 6); const line = phoneNumber.substring(6, 10); return `(${area}) ${prefix}-${line}`; }formatPhoneNumber(staff.phone)) and email. Keep the conditional role badges — the&&shape, one badge per flag: - Add the 3-dots
Dropdown. GiveStaffCardanonRemoveprop (addonRemove: (staff: IStaff) => voidtoIStaffCardProps), adddelete(id)tostaffAPI, and add the menu — the same react-bootstrapDropdown+ sprite icon as the guide'sOrderRow(imports:Dropdown,Link,bootstrapIcons,staffAPI):<Dropdown className="d-inline"> <Dropdown.Toggle className="btn btn-light" style={{ background: "none" }}> <svg className="bi pe-none" width={20} height={20} fill="#007AFF"> <use xlinkHref={`${bootstrapIcons}#three-dots-vertical`} /> </svg> </Dropdown.Toggle> <Dropdown.Menu> <Dropdown.Item as={Link} to={`/staff/edit/${staff.id}`}>Edit</Dropdown.Item> <Dropdown.Item as="a" href="#" onClick={async (event) => { event.preventDefault(); if (confirm("Delete this staff member?") && staff.id) { await staffAPI.delete(staff.id); onRemove(staff); } }}>Delete</Dropdown.Item> </Dropdown.Menu> </Dropdown>StaffPagesuppliesonRemoveas aremoveStaffthat filters the deleted member out of state (likeOrdersPage'sremoveOrder). ✅ Checkpoint: the ⋮ menu opens; Delete confirms, removes the card, and Network shows aDELETE. (Edit 404s until Lesson 7.) - Add skeletons. On
StaffPage, add aloadingflag (set around the fetch), build aStaffCardSkeleton(a copy of the card withskeleton skeleton-textbars), and render{loading && staffCardSkeletons}wherestaffCardSkeletonsisArray.from(Array(12), (_v, i) => <StaffCardSkeleton key={i} />).
Verify in the browser¶
Browser checks are covered in the guide — section 7. With your API running and
npm run dev up:
- Open
/staff— a wrapping grid of cards, each showing only the role badges that apply (Manager, Admin, both, or neither). - Throttle to Slow 3G (DevTools → Network) and reload — the skeleton cards show during the fetch, then swap for real cards.
- Open a card's ⋮ menu → Delete confirms and removes the card (check
Network for the
DELETE). - Console clean.
Same conditional-rendering + skeleton patterns, a card grid instead of a table —
exactly how you'll build the PRS Users card grid in the capstone (whose role label
is Admin / Reviewer / no role assigned).
Stretch challenges¶
Optional — for when you finish early. Not needed for the capstone. [Reinforce] builds on what you just did; [Reach] goes past the guide and needs some research.
- Single role label — [Reinforce] — instead of two badges, render one label:
AdminifisAdmin, elseManagerifisManager, elseno role assigned(Admin wins). Write a smallgetRoleLabel(staff)helper — the lookup-function pattern from the guide, applied to roles. This is exactly PRS's User rule. - Empty-list message — [Reinforce] — when
staff.length === 0and not loading, render a "No staff yet" message with a ternary instead of an empty tray. - Dash for missing contact — [Reinforce] — render
—for a member with no phone or email ({staff.email || "—"}), matching the Orders table's empty-notes handling. - Avatar circle with initials — [Reach] — add a circular avatar showing the
member's initials to the left of the details, in a
d-flex gap-4row. Work out the circle from a fixed-size flex box that centers its text withrounded-circle. This pattern is left for you to solve — you'll need it again on PRS's Users cards. Reference: Bootstrap border-radius utilities (rounded-circle).
Finished these and want more? See stretch-react-challenges.md for bigger challenges that span the whole React pass.