feat: add D4 play grouping with tab navigation and improve header layout
Some checks failed
lotteryfront CI / build (push) Has been cancelled
Some checks failed
lotteryfront CI / build (push) Has been cancelled
- Introduced D4 play groups (big/small, position, combo, roll/straight, head/tail/odd/even, digit big/small) with tab-based filtering to improve betting grid usability - Updated PlayerPanel header grid layout from percentage-based to auto/1fr/auto columns with increased gap for better responsiveness - Removed "placed" status from ticket orders list filter options - Added D4 group translations in English, Nepali, and Chinese - Refactored play columns
This commit is contained in:
@@ -135,6 +135,23 @@ const D4_PLAY_ORDER = [
|
||||
"digit_small",
|
||||
] as const;
|
||||
|
||||
type D4PlayGroupId =
|
||||
| "big_small"
|
||||
| "position"
|
||||
| "combo"
|
||||
| "roll_straight"
|
||||
| "head_tail_odd_even"
|
||||
| "digit_big_small";
|
||||
|
||||
const D4_PLAY_GROUPS: { id: D4PlayGroupId; labelKey: string; playCodes: readonly string[] }[] = [
|
||||
{ id: "big_small", labelKey: "hall.d4Group.big_small", playCodes: ["big", "small"] },
|
||||
{ id: "position", labelKey: "hall.d4Group.position", playCodes: ["pos_4a", "pos_4b", "pos_4c", "pos_4d", "pos_4e"] },
|
||||
{ id: "combo", labelKey: "hall.d4Group.combo", playCodes: ["box", "ibox", "mbox"] },
|
||||
{ id: "roll_straight", labelKey: "hall.d4Group.roll_straight", playCodes: ["roll", "straight"] },
|
||||
{ id: "head_tail_odd_even", labelKey: "hall.d4Group.head_tail_odd_even", playCodes: ["head", "tail", "odd", "even"] },
|
||||
{ id: "digit_big_small", labelKey: "hall.d4Group.digit_big_small", playCodes: ["digit_big", "digit_small"] },
|
||||
];
|
||||
|
||||
const categoryPlayOrders: Record<Exclude<HallCategory, "JACKPOT">, readonly string[]> = {
|
||||
D2: D2_PLAY_ORDER,
|
||||
D3: D3_PLAY_ORDER,
|
||||
@@ -446,6 +463,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
const creditMode = isCreditFundingPlayer(usePlayerSessionStore((state) => state.profile));
|
||||
|
||||
const [activeCategory, setActiveCategory] = useState<HallCategory>("D2");
|
||||
const [d4PlayGroup, setD4PlayGroup] = useState<D4PlayGroupId>("big_small");
|
||||
const [rows, setRows] = useState<DraftRow[]>(() => [newDraftRow()]);
|
||||
const [activeRowId, setActiveRowId] = useState<string | null>(null);
|
||||
const [catalogState, setCatalogState] = useState<
|
||||
@@ -557,11 +575,19 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
);
|
||||
}, [activeCategory, catalogState, openPlays]);
|
||||
|
||||
const playColumns = useMemo(() => {
|
||||
const allPlayColumns = useMemo(() => {
|
||||
if (activeCategory === "JACKPOT") return [];
|
||||
return playColumnsForCategory(categoryPlays, activeCategory);
|
||||
}, [activeCategory, categoryPlays]);
|
||||
|
||||
const playColumns = useMemo(() => {
|
||||
if (activeCategory !== "D4") return allPlayColumns;
|
||||
const group = D4_PLAY_GROUPS.find((g) => g.id === d4PlayGroup);
|
||||
if (!group) return allPlayColumns;
|
||||
const allowedCodes = new Set(group.playCodes);
|
||||
return allPlayColumns.filter((col) => allowedCodes.has(col.play.play_code));
|
||||
}, [activeCategory, allPlayColumns, d4PlayGroup]);
|
||||
|
||||
const tableMinWidthPx = useMemo(() => {
|
||||
const indexCol = 40;
|
||||
const numberCol = activeCategory === "D4" ? 112 : activeCategory === "D3" ? 88 : 72;
|
||||
@@ -570,7 +596,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
return indexCol + numberCol + playColumns.length * amountCol + deleteCol;
|
||||
}, [activeCategory, playColumns.length]);
|
||||
|
||||
const showWideTableHint = activeCategory === "D4" && playColumns.length > 8;
|
||||
const showWideTableHint = activeCategory === "D4" && allPlayColumns.length > 8 && playColumns.length > 8;
|
||||
|
||||
const activeRow = useMemo(
|
||||
() => rows.find((row) => row.id === activeRowId) ?? rows[0] ?? null,
|
||||
@@ -598,7 +624,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
if (activeCategory !== "D4") return defaultNumberPlaceholder;
|
||||
const targetRow = activeRow ?? rows[0];
|
||||
if (!targetRow) return defaultNumberPlaceholder;
|
||||
const hasRollStake = playColumns.some((column) => {
|
||||
const hasRollStake = allPlayColumns.some((column) => {
|
||||
if (column.play.play_code !== "roll") return false;
|
||||
const amount = parseDecimalInputToMinor(targetRow.amounts[column.key] ?? "", currencyCode);
|
||||
return amount !== null && amount > 0;
|
||||
@@ -609,11 +635,19 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
activeRow,
|
||||
currencyCode,
|
||||
defaultNumberPlaceholder,
|
||||
playColumns,
|
||||
allPlayColumns,
|
||||
rows,
|
||||
t,
|
||||
]);
|
||||
|
||||
const availableD4Groups = useMemo(() => {
|
||||
if (activeCategory !== "D4") return [];
|
||||
return D4_PLAY_GROUPS.filter((group) => {
|
||||
const allowedCodes = new Set(group.playCodes);
|
||||
return allPlayColumns.some((col) => allowedCodes.has(col.play.play_code));
|
||||
});
|
||||
}, [activeCategory, allPlayColumns]);
|
||||
|
||||
const updateRowNumber = (id: string, value: string) => {
|
||||
setRows((current) =>
|
||||
current.map((row) =>
|
||||
@@ -809,7 +843,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
if (activeCategory === "JACKPOT") return [];
|
||||
const issues: DraftLineIssue[] = [];
|
||||
rows.forEach((row, rowIndex) => {
|
||||
playColumns.forEach((column) => {
|
||||
allPlayColumns.forEach((column) => {
|
||||
const amount = parseDecimalInputToMinor(row.amounts[column.key] ?? "", currencyCode);
|
||||
if (amount === null || amount <= 0) return;
|
||||
const reason = draftLineIssueReason(column.play.play_code, row.number, column.digitSlot);
|
||||
@@ -823,7 +857,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
});
|
||||
});
|
||||
return issues;
|
||||
}, [activeCategory, currencyCode, playColumns, rows]);
|
||||
}, [activeCategory, currencyCode, allPlayColumns, rows]);
|
||||
|
||||
const formatDraftLineIssue = useCallback(
|
||||
(issue: DraftLineIssue): string => {
|
||||
@@ -837,7 +871,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
if (activeCategory === "JACKPOT") return [];
|
||||
const entries: DraftEntry[] = [];
|
||||
rows.forEach((row, rowIndex) => {
|
||||
playColumns.forEach((column) => {
|
||||
allPlayColumns.forEach((column) => {
|
||||
const amount = parseDecimalInputToMinor(row.amounts[column.key] ?? "", currencyCode);
|
||||
if (amount === null || amount <= 0) return;
|
||||
const line = lineForPlay(activeCategory, column.play, row.number, amount, column.digitSlot);
|
||||
@@ -855,7 +889,7 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
});
|
||||
});
|
||||
return entries;
|
||||
}, [activeCategory, currencyCode, playColumns, rows]);
|
||||
}, [activeCategory, currencyCode, allPlayColumns, rows]);
|
||||
|
||||
const draftEntries = collectEntries();
|
||||
const draftSummary = useMemo(() => {
|
||||
@@ -1155,6 +1189,29 @@ export function HallBettingGrid({ drawLive }: { drawLive: HallDrawLiveSnapshot }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{activeCategory === "D4" && availableD4Groups.length > 1 ? (
|
||||
<div className="flex gap-1 overflow-x-auto overscroll-x-contain pb-0.5">
|
||||
{availableD4Groups.map((group) => {
|
||||
const active = d4PlayGroup === group.id;
|
||||
return (
|
||||
<button
|
||||
key={group.id}
|
||||
type="button"
|
||||
onClick={() => setD4PlayGroup(group.id)}
|
||||
className={cn(
|
||||
"shrink-0 rounded-full px-3 py-1.5 text-xs font-bold transition-colors",
|
||||
active
|
||||
? "bg-[#07459f] text-white"
|
||||
: "border border-[#dce7f7] bg-white text-[#32518d] hover:bg-[#f4f7fb]",
|
||||
)}
|
||||
>
|
||||
{t(group.labelKey)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{jackpot?.enabled ? (
|
||||
<div className="rounded-xl border border-amber-200 bg-gradient-to-r from-amber-50 via-white to-[#f8fbff] px-3 py-2.5">
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
|
||||
Reference in New Issue
Block a user