Skip to content

Commit 9115329

Browse files
authored
Merge pull request #158 from FracturedShader/STA-175-bundles-filter-search
[STA-175] Filters & Searches for Bundles
2 parents fa040fd + 2ee485b commit 9115329

2 files changed

Lines changed: 169 additions & 37 deletions

File tree

apps/stardew.app/src/components/cards/bundle-item-card.tsx

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Dispatch, SetStateAction } from "react";
55
import { usePlayers } from "@/contexts/players-context";
66

77
import { categoryIcons, goldIcons } from "@/lib/constants";
8-
import { BundleItemWithLocation } from "@/types/bundles";
8+
import { BundleItem, BundleItemWithLocation } from "@/types/bundles";
99
import { BooleanCard } from "./boolean-card";
1010

1111
interface BundleItemCardProps {
@@ -29,6 +29,23 @@ interface BundleItemCardProps {
2929
setPromptOpen?: Dispatch<SetStateAction<boolean>>;
3030
}
3131

32+
const categoryItems: Record<string, string> = {
33+
"-4": "Any Fish",
34+
"-5": "Any Egg",
35+
"-6": "Any Milk",
36+
"-777": "Wild Seeds (Any)",
37+
};
38+
39+
export function bundleItemName<T extends BundleItem>(item: T): string {
40+
if (item.itemID == "-1") {
41+
return "Gold";
42+
} else if (item.itemID in categoryItems) {
43+
return categoryItems[item.itemID];
44+
}
45+
46+
return objects[item.itemID as keyof typeof objects]?.name;
47+
}
48+
3249
export const BundleItemCard = ({
3350
item,
3451
show,
@@ -52,13 +69,6 @@ export const BundleItemCard = ({
5269
let overrides: Record<string, string | number | boolean | undefined> = {};
5370
let unknownItem: Boolean = false;
5471

55-
const categoryItems: Record<string, string> = {
56-
"-4": "Any Fish",
57-
"-5": "Any Egg",
58-
"-6": "Any Milk",
59-
"-777": "Wild Seeds (Any)",
60-
};
61-
6272
if (item.itemID == "-1") {
6373
//Special case for handling gold in Vault bundles
6474
iconURL = goldIcons[item.itemQuantity.toString()];

apps/stardew.app/src/pages/bundles.tsx

Lines changed: 151 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ import { PlayerType, usePlayers } from "@/contexts/players-context";
3737
import { usePreferences } from "@/contexts/preferences-context";
3838

3939
import { AchievementCard } from "@/components/cards/achievement-card";
40-
import { BundleItemCard } from "@/components/cards/bundle-item-card";
40+
import {
41+
BundleItemCard,
42+
bundleItemName,
43+
} from "@/components/cards/bundle-item-card";
4144
import { UnblurDialog } from "@/components/dialogs/unblur-dialog";
4245
import BundleSheet from "@/components/sheets/bundle-sheet";
4346
import {
@@ -47,7 +50,10 @@ import {
4750
AccordionTrigger,
4851
AccordionTriggerNoToggle,
4952
} from "@/components/ui/accordion";
53+
import { Command, CommandInput } from "@/components/ui/command";
5054
import { Progress } from "@/components/ui/progress";
55+
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
56+
import { cn } from "@/lib/utils";
5157
import { useMediaQuery } from "@react-hook/media-query";
5258
import { IconSettings } from "@tabler/icons-react";
5359
import clsx from "clsx";
@@ -60,6 +66,12 @@ export const ItemQualityToString = {
6066
"3": "Iridium",
6167
};
6268

69+
const bubbleColors: Record<string, string> = {
70+
"0": "border-neutral-200 bg-white dark:border-neutral-800 dark:bg-neutral-950", // unknown or not completed
71+
"1": "border-yellow-900 bg-yellow-500/20", // known, but not completed
72+
"2": "border-green-900 bg-green-500/20", // completed
73+
};
74+
6375
type BundleAccordionProps = {
6476
bundleWithStatus: BundleWithStatus;
6577
children: JSX.Element | JSX.Element[];
@@ -73,6 +85,12 @@ type AccordionSectionProps = {
7385
completedCount?: number;
7486
};
7587

88+
type FilteredBundle = {
89+
bundleWithStatus: BundleWithStatus;
90+
items: (BundleItem | Randomizer)[];
91+
matchesSearch: boolean;
92+
};
93+
7694
const CommunityCenterRooms: CommunityCenterRoomName[] = [
7795
"Pantry",
7896
"Crafts Room",
@@ -417,6 +435,11 @@ export default function Bundles() {
417435
let [open, setIsOpen] = useState(false);
418436
let [object, setObject] = useState<BundleItemWithLocation | null>(null);
419437
let [bundles, setBundles] = useState<BundleWithStatus[]>([]);
438+
let [completeCount, setCompleteCount] = useState(0);
439+
let [incompleteCount, setIncompleteCount] = useState(0);
440+
let [filter, setFilter] = useState("all");
441+
let [search, setSearch] = useState("");
442+
420443
const { activePlayer, patchPlayer } = usePlayers();
421444

422445
function GetActiveBundles(
@@ -477,7 +500,13 @@ export default function Bundles() {
477500
}
478501

479502
useEffect(() => {
480-
setBundles(GetActiveBundles(activePlayer));
503+
const activeBundles = GetActiveBundles(activePlayer);
504+
const _completeCount = activeBundles.filter(BundleCompleted).length;
505+
const _incompleteCount = activeBundles.length - _completeCount;
506+
507+
setBundles(activeBundles);
508+
setCompleteCount(_completeCount);
509+
setIncompleteCount(_incompleteCount);
481510
}, [activePlayer]);
482511

483512
const getAchievementProgress = (name: string) => {
@@ -563,9 +592,53 @@ export default function Bundles() {
563592
);
564593
})}
565594
</AccordionSection>
595+
{/* Filters and Actions Row */}
596+
<div className="flex w-full flex-row items-center justify-between">
597+
<ToggleGroup
598+
variant="outline"
599+
type="single"
600+
value={filter}
601+
onValueChange={(val: string) =>
602+
setFilter(val === filter ? "all" : val)
603+
}
604+
className="gap-2"
605+
>
606+
<ToggleGroupItem value="0" aria-label="Show Incomplete">
607+
<span
608+
className={cn(
609+
"inline-block h-4 w-4 rounded-full border align-middle",
610+
bubbleColors["0"],
611+
)}
612+
/>
613+
<span className="align-middle">
614+
Incomplete ({incompleteCount})
615+
</span>
616+
</ToggleGroupItem>
617+
<ToggleGroupItem value="2" aria-label="Show Complete">
618+
<span
619+
className={cn(
620+
"inline-block h-4 w-4 rounded-full border align-middle",
621+
bubbleColors["2"],
622+
)}
623+
/>
624+
<span className="align-middle">Complete ({completeCount})</span>
625+
</ToggleGroupItem>
626+
</ToggleGroup>
627+
</div>
628+
{/* Search Bar Row */}
629+
<div className="mt-2 w-full">
630+
<Command className="w-full border border-b-0 dark:border-neutral-800">
631+
<CommandInput
632+
onValueChange={(v) => setSearch(v?.toLowerCase())}
633+
placeholder="Search Bundles"
634+
/>
635+
</Command>
636+
</div>
566637
{CommunityCenterRooms.map((roomName: CommunityCenterRoomName) => {
567638
let roomBundles: BundleWithStatus[] = [];
568639
let completedCount = 0;
640+
const roomMatched =
641+
!search || roomName.toLowerCase().includes(search);
569642
if (activePlayer && Array.isArray(activePlayer.bundles)) {
570643
roomBundles = activePlayer.bundles.filter((bundleWithStatus) => {
571644
if (bundleWithStatus?.bundle) {
@@ -584,13 +657,64 @@ export default function Bundles() {
584657
bundleWithStatus.bundle.areaName === roomName,
585658
);
586659
}
660+
const filteredBundles: FilteredBundle[] = roomBundles
661+
.filter((bundleWithStatus) => {
662+
switch (filter) {
663+
case "0":
664+
return !BundleCompleted(bundleWithStatus);
665+
case "2":
666+
return BundleCompleted(bundleWithStatus);
667+
case "all":
668+
default:
669+
return true;
670+
}
671+
})
672+
.map((bundleWithStatus): FilteredBundle => {
673+
const bundleMatched =
674+
roomMatched ||
675+
bundleWithStatus.bundle.name.toLowerCase().includes(search);
676+
677+
return {
678+
bundleWithStatus: bundleWithStatus,
679+
items: bundleWithStatus.bundle.items
680+
.filter((_, idx) => {
681+
switch (filter) {
682+
case "0":
683+
return !bundleWithStatus.bundleStatus[idx];
684+
case "2":
685+
return bundleWithStatus.bundleStatus[idx];
686+
case "all":
687+
default:
688+
return true;
689+
}
690+
})
691+
.filter((item) => {
692+
if (bundleMatched) {
693+
return true;
694+
}
695+
696+
return (
697+
!isRandomizer(item) &&
698+
bundleItemName(item).toLowerCase().includes(search)
699+
);
700+
}),
701+
matchesSearch: bundleMatched,
702+
};
703+
})
704+
.filter((filteredBundle) => filteredBundle.items.length !== 0);
705+
706+
if (filteredBundles.length === 0) {
707+
return;
708+
}
709+
587710
return (
588711
<AccordionSection
589712
key={roomName}
590713
title={roomName}
591714
completedCount={completedCount}
592715
>
593-
{roomBundles.map((bundleWithStatus: BundleWithStatus) => {
716+
{filteredBundles.map((filteredBundle: FilteredBundle) => {
717+
const bundleWithStatus = filteredBundle.bundleWithStatus;
594718
return (
595719
<BundleAccordion
596720
key={bundleWithStatus.bundle.localizedName}
@@ -606,32 +730,30 @@ export default function Bundles() {
606730
})}
607731
onChangeBundle={SwapBundle}
608732
>
609-
{bundleWithStatus.bundle.items.map ? (
610-
bundleWithStatus.bundle.items.map(
611-
(item, index: number) => {
612-
if (isRandomizer(item)) {
613-
// Guard clause for type coercion
614-
return <></>;
615-
}
616-
const BundleItemWithLocation: BundleItemWithLocation =
617-
{
618-
...item,
619-
index: index,
620-
bundleID: bundleWithStatus.bundle.name,
621-
};
622-
return (
623-
<BundleItemCard
624-
key={item.itemID + "-" + index}
625-
item={BundleItemWithLocation}
626-
setIsOpen={setIsOpen}
627-
completed={bundleWithStatus.bundleStatus[index]}
628-
setObject={setObject}
629-
show={show}
630-
setPromptOpen={setPromptOpen}
631-
/>
632-
);
633-
},
634-
)
733+
{filteredBundle.items ? (
734+
filteredBundle.items.map((item, index: number) => {
735+
if (isRandomizer(item)) {
736+
// Guard clause for type coercion
737+
return <></>;
738+
}
739+
const BundleItemWithLocation: BundleItemWithLocation =
740+
{
741+
...item,
742+
index: index,
743+
bundleID: bundleWithStatus.bundle.name,
744+
};
745+
return (
746+
<BundleItemCard
747+
key={item.itemID + "-" + index}
748+
item={BundleItemWithLocation}
749+
setIsOpen={setIsOpen}
750+
completed={bundleWithStatus.bundleStatus[index]}
751+
setObject={setObject}
752+
show={show}
753+
setPromptOpen={setPromptOpen}
754+
/>
755+
);
756+
})
635757
) : (
636758
<>error</>
637759
)}

0 commit comments

Comments
 (0)