Scanner: large Adults / Kids party panel on scan
Gate-enforcement aid against adults signing up under a free/cheaper kid bracket. When a ticket is scanned, a prominent amber-bordered panel shows big "# ADULTS # KIDS" counts (Adults = the 18+ bracket; Kids = youth 13-16 + all under-13), plus a per-bracket detail line (e.g. "1× 13-16 · 2× 5-9") so staff can eyeball the claimed ages against the actual party. Shown on the confirm card (before check-in) and the success overlay; hidden in Ice mode. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
049f433930
commit
63e00fae61
1 changed files with 50 additions and 0 deletions
|
|
@ -253,6 +253,7 @@ export default function ScannerScreen() {
|
||||||
<Text style={styles.counts}>
|
<Text style={styles.counts}>
|
||||||
{ticket.redeemed} of {ticket.total} redeemed · {ticket.remaining} remaining
|
{ticket.redeemed} of {ticket.total} redeemed · {ticket.remaining} remaining
|
||||||
</Text>
|
</Text>
|
||||||
|
<PartyPanel ticket={ticket} />
|
||||||
<AdultNames names={ticket.adultNames} />
|
<AdultNames names={ticket.adultNames} />
|
||||||
<ExtrasRow ticket={ticket} />
|
<ExtrasRow ticket={ticket} />
|
||||||
</>
|
</>
|
||||||
|
|
@ -394,6 +395,37 @@ function AdultNames({ names }: { names: string[] }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Big Adults / Kids breakdown so gate staff can eyeball the party against the
|
||||||
|
* ticket — a deterrent for adults signing up under a (free/cheaper) kid bracket.
|
||||||
|
* Adults = the 18+ bracket; Kids = everyone else (youth 13-16 + all under-13).
|
||||||
|
*/
|
||||||
|
function PartyPanel({ ticket }: { ticket: TicketView }) {
|
||||||
|
const adults = ticket.ages.find((a) => a.bracket === "Adults")?.count ?? 0;
|
||||||
|
const kidBrackets = ticket.ages.filter((a) => a.bracket !== "Adults");
|
||||||
|
const kids = kidBrackets.reduce((s, a) => s + a.count, 0);
|
||||||
|
return (
|
||||||
|
<View style={styles.party}>
|
||||||
|
<View style={styles.partyRow}>
|
||||||
|
<View style={styles.partyCell}>
|
||||||
|
<Text style={styles.partyNum}>{adults}</Text>
|
||||||
|
<Text style={styles.partyLbl}>{adults === 1 ? "ADULT" : "ADULTS"}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.partyDivider} />
|
||||||
|
<View style={styles.partyCell}>
|
||||||
|
<Text style={styles.partyNum}>{kids}</Text>
|
||||||
|
<Text style={styles.partyLbl}>{kids === 1 ? "KID" : "KIDS"}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
{kidBrackets.length > 0 && (
|
||||||
|
<Text style={styles.partyDetail}>
|
||||||
|
{kidBrackets.map((a) => `${a.count}× ${a.bracket.replace(/^(Kids|Youth)\s*/, "")}`).join(" · ")}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function ConfirmCard({
|
function ConfirmCard({
|
||||||
ticket,
|
ticket,
|
||||||
isIce,
|
isIce,
|
||||||
|
|
@ -422,6 +454,7 @@ function ConfirmCard({
|
||||||
<Text style={styles.cardName}>{ticket.name}</Text>
|
<Text style={styles.cardName}>{ticket.name}</Text>
|
||||||
<TypeBadge type={ticket.ticketType} />
|
<TypeBadge type={ticket.ticketType} />
|
||||||
<Text style={styles.cardCode}>{ticket.code}</Text>
|
<Text style={styles.cardCode}>{ticket.code}</Text>
|
||||||
|
{!isIce && <PartyPanel ticket={ticket} />}
|
||||||
<Text style={styles.cardCounts}>
|
<Text style={styles.cardCounts}>
|
||||||
<Text style={{ color: theme.successBright, fontWeight: "800" }}>{remaining}</Text> of {total} {unit} remaining
|
<Text style={{ color: theme.successBright, fontWeight: "800" }}>{remaining}</Text> of {total} {unit} remaining
|
||||||
</Text>
|
</Text>
|
||||||
|
|
@ -535,6 +568,23 @@ const styles = StyleSheet.create({
|
||||||
typeBadgeText: { color: "#fff", fontSize: 20, fontWeight: "900", letterSpacing: 1 },
|
typeBadgeText: { color: "#fff", fontSize: 20, fontWeight: "900", letterSpacing: 1 },
|
||||||
namesBox: { marginTop: 12, alignItems: "center", gap: 3 },
|
namesBox: { marginTop: 12, alignItems: "center", gap: 3 },
|
||||||
nameLine: { color: "#fff", fontSize: 18, fontWeight: "600", textAlign: "center" },
|
nameLine: { color: "#fff", fontSize: 18, fontWeight: "600", textAlign: "center" },
|
||||||
|
party: {
|
||||||
|
alignSelf: "stretch",
|
||||||
|
backgroundColor: "#1d2a1f",
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: theme.warn,
|
||||||
|
borderRadius: 16,
|
||||||
|
paddingVertical: 18,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
marginTop: 16,
|
||||||
|
marginBottom: 2,
|
||||||
|
},
|
||||||
|
partyRow: { flexDirection: "row", alignItems: "center", justifyContent: "center" },
|
||||||
|
partyCell: { flex: 1, alignItems: "center" },
|
||||||
|
partyNum: { color: theme.text, fontSize: 60, fontWeight: "900", lineHeight: 64 },
|
||||||
|
partyLbl: { color: theme.warn, fontSize: 15, fontWeight: "800", letterSpacing: 2, marginTop: 2 },
|
||||||
|
partyDivider: { width: 2, alignSelf: "stretch", backgroundColor: theme.cardBorder, marginVertical: 6 },
|
||||||
|
partyDetail: { color: theme.textDim, fontSize: 14, textAlign: "center", marginTop: 12, fontWeight: "600" },
|
||||||
tags: { flexDirection: "row", flexWrap: "wrap", justifyContent: "center", gap: 8, marginTop: 14 },
|
tags: { flexDirection: "row", flexWrap: "wrap", justifyContent: "center", gap: 8, marginTop: 14 },
|
||||||
tag: { color: "#fff", backgroundColor: "rgba(255,255,255,0.18)", paddingHorizontal: 10, paddingVertical: 5, borderRadius: 999, fontSize: 13, overflow: "hidden" },
|
tag: { color: "#fff", backgroundColor: "rgba(255,255,255,0.18)", paddingHorizontal: 10, paddingVertical: 5, borderRadius: 999, fontSize: 13, overflow: "hidden" },
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue