style: run format:apply
This commit is contained in:
+99
-61
@@ -7,75 +7,113 @@ import QuantityCard from '../../components/QuantityCard';
|
||||
import { Addon, getAddonIconUrl, randInRange } from './types';
|
||||
|
||||
type Props = {
|
||||
addons: Addon[];
|
||||
quantities: Record<string, number>;
|
||||
randMin: number;
|
||||
randMax: number;
|
||||
onSetQty: (id: string, qty: number) => void;
|
||||
addons: Addon[];
|
||||
quantities: Record<string, number>;
|
||||
randMin: number;
|
||||
randMax: number;
|
||||
onSetQty: (id: string, qty: number) => void;
|
||||
};
|
||||
|
||||
export default function AddonGrid({ addons, quantities, randMin, randMax, onSetQty }: Props) {
|
||||
const [search, setSearch] = useState('');
|
||||
const [roleFilter, setRoleFilter] = useState<'all' | 'killer' | 'survivor'>('all');
|
||||
export default function AddonGrid({
|
||||
addons,
|
||||
quantities,
|
||||
randMin,
|
||||
randMax,
|
||||
onSetQty
|
||||
}: Props) {
|
||||
const [search, setSearch] = useState('');
|
||||
const [roleFilter, setRoleFilter] = useState<'all' | 'killer' | 'survivor'>(
|
||||
'all'
|
||||
);
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
return addons.filter(addon => {
|
||||
if (roleFilter === 'killer' && addon.role !== 1) return false;
|
||||
if (roleFilter === 'survivor' && addon.role !== 2) return false;
|
||||
if (search.trim() && !addon.name.toLowerCase().includes(search.toLowerCase())) return false;
|
||||
return true;
|
||||
});
|
||||
}, [addons, search, roleFilter]);
|
||||
const filtered = useMemo(() => {
|
||||
return addons.filter((addon) => {
|
||||
if (roleFilter === 'killer' && addon.role !== 1) return false;
|
||||
if (roleFilter === 'survivor' && addon.role !== 2) return false;
|
||||
if (
|
||||
search.trim() &&
|
||||
!addon.name.toLowerCase().includes(search.toLowerCase())
|
||||
)
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
}, [addons, search, roleFilter]);
|
||||
|
||||
const handleGive100Visible = () => filtered.forEach(a => onSetQty(a.id, 100));
|
||||
const handleRandVisible = () => filtered.forEach(a => onSetQty(a.id, randInRange(randMin, randMax)));
|
||||
const handleLockVisible = () => filtered.forEach(a => onSetQty(a.id, 0));
|
||||
const handleGive100Visible = () =>
|
||||
filtered.forEach((a) => onSetQty(a.id, 100));
|
||||
const handleRandVisible = () =>
|
||||
filtered.forEach((a) => onSetQty(a.id, randInRange(randMin, randMax)));
|
||||
const handleLockVisible = () => filtered.forEach((a) => onSetQty(a.id, 0));
|
||||
|
||||
const activeCount = Object.values(quantities).filter(q => q > 0).length;
|
||||
const activeCount = Object.values(quantities).filter((q) => q > 0).length;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={shared.toolbar}>
|
||||
<input
|
||||
className={shared.searchInput}
|
||||
type="text"
|
||||
placeholder="Search addons..."
|
||||
value={search}
|
||||
onChange={e => setSearch(e.target.value)}
|
||||
/>
|
||||
return (
|
||||
<>
|
||||
<div className={shared.toolbar}>
|
||||
<input
|
||||
className={shared.searchInput}
|
||||
type='text'
|
||||
placeholder='Search addons...'
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
|
||||
<div className={shared.roleFilter}>
|
||||
<button className={`${shared.roleBtn} ${roleFilter === 'all' ? shared.roleBtnActive : ''}`} onClick={() => setRoleFilter('all')}>All</button>
|
||||
<button className={`${shared.roleBtn} ${roleFilter === 'survivor' ? shared.roleBtnActive : ''}`} onClick={() => setRoleFilter('survivor')}>Survivor</button>
|
||||
<button className={`${shared.roleBtn} ${roleFilter === 'killer' ? shared.roleBtnActive : ''}`} onClick={() => setRoleFilter('killer')}>Killer</button>
|
||||
</div>
|
||||
<div className={shared.roleFilter}>
|
||||
<button
|
||||
className={`${shared.roleBtn} ${roleFilter === 'all' ? shared.roleBtnActive : ''}`}
|
||||
onClick={() => setRoleFilter('all')}
|
||||
>
|
||||
All
|
||||
</button>
|
||||
<button
|
||||
className={`${shared.roleBtn} ${roleFilter === 'survivor' ? shared.roleBtnActive : ''}`}
|
||||
onClick={() => setRoleFilter('survivor')}
|
||||
>
|
||||
Survivor
|
||||
</button>
|
||||
<button
|
||||
className={`${shared.roleBtn} ${roleFilter === 'killer' ? shared.roleBtnActive : ''}`}
|
||||
onClick={() => setRoleFilter('killer')}
|
||||
>
|
||||
Killer
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<span className={shared.spacer} />
|
||||
<span className={shared.resultCount}>{filtered.length} shown · {activeCount} active out of {addons.length} total</span>
|
||||
<span className={shared.spacer} />
|
||||
<span className={shared.resultCount}>
|
||||
{filtered.length} shown · {activeCount} active out of {addons.length}{' '}
|
||||
total
|
||||
</span>
|
||||
|
||||
<button className={shared.unlockAllBtn} onClick={handleGive100Visible}>Set all to 100</button>
|
||||
<button className={styles.randBtn} onClick={handleRandVisible}>Randomize</button>
|
||||
<button className={shared.lockAllBtn} onClick={handleLockVisible}>Remove all visible</button>
|
||||
</div>
|
||||
<button className={shared.unlockAllBtn} onClick={handleGive100Visible}>
|
||||
Set all to 100
|
||||
</button>
|
||||
<button className={styles.randBtn} onClick={handleRandVisible}>
|
||||
Randomize
|
||||
</button>
|
||||
<button className={shared.lockAllBtn} onClick={handleLockVisible}>
|
||||
Remove all visible
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{filtered.length === 0 ? (
|
||||
<div className={shared.empty}>No addons match</div>
|
||||
) : (
|
||||
<div className={styles.grid}>
|
||||
{filtered.map(addon => (
|
||||
<QuantityCard
|
||||
key={addon.id}
|
||||
id={addon.id}
|
||||
name={addon.name}
|
||||
iconUrl={getAddonIconUrl(addon.iconFilePath)}
|
||||
qty={quantities[addon.id] ?? 0}
|
||||
randMin={randMin}
|
||||
randMax={randMax}
|
||||
onSetQty={onSetQty}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
{filtered.length === 0 ? (
|
||||
<div className={shared.empty}>No addons match</div>
|
||||
) : (
|
||||
<div className={styles.grid}>
|
||||
{filtered.map((addon) => (
|
||||
<QuantityCard
|
||||
key={addon.id}
|
||||
id={addon.id}
|
||||
name={addon.name}
|
||||
iconUrl={getAddonIconUrl(addon.iconFilePath)}
|
||||
qty={quantities[addon.id] ?? 0}
|
||||
randMin={randMin}
|
||||
randMax={randMax}
|
||||
onSetQty={onSetQty}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
+95
-68
@@ -4,84 +4,111 @@ import { useState, useMemo } from 'react';
|
||||
import shared from '../../styles/shared.module.css';
|
||||
import styles from '../../styles/Items.module.css';
|
||||
import QuantityCard from '../../components/QuantityCard';
|
||||
import { Item, ItemType, ITEM_TYPE_LABELS, getItemType, getItemIconUrl } from './types';
|
||||
import {
|
||||
Item,
|
||||
ItemType,
|
||||
ITEM_TYPE_LABELS,
|
||||
getItemType,
|
||||
getItemIconUrl
|
||||
} from './types';
|
||||
import { randInRange } from '@/lib/utils';
|
||||
|
||||
type Props = {
|
||||
items: Item[];
|
||||
quantities: Record<string, number>;
|
||||
randMin: number;
|
||||
randMax: number;
|
||||
onSetQty: (id: string, qty: number) => void;
|
||||
items: Item[];
|
||||
quantities: Record<string, number>;
|
||||
randMin: number;
|
||||
randMax: number;
|
||||
onSetQty: (id: string, qty: number) => void;
|
||||
};
|
||||
|
||||
export default function ItemGrid({ items, quantities, randMin, randMax, onSetQty }: Props) {
|
||||
const [search, setSearch] = useState('');
|
||||
const [typeFilter, setTypeFilter] = useState<ItemType>('all');
|
||||
export default function ItemGrid({
|
||||
items,
|
||||
quantities,
|
||||
randMin,
|
||||
randMax,
|
||||
onSetQty
|
||||
}: Props) {
|
||||
const [search, setSearch] = useState('');
|
||||
const [typeFilter, setTypeFilter] = useState<ItemType>('all');
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
return items.filter(item => {
|
||||
if (typeFilter !== 'all' && getItemType(item.id) !== typeFilter) return false;
|
||||
if (search.trim() && !item.name.toLowerCase().includes(search.toLowerCase())) return false;
|
||||
return true;
|
||||
});
|
||||
}, [items, typeFilter, search]);
|
||||
const filtered = useMemo(() => {
|
||||
return items.filter((item) => {
|
||||
if (typeFilter !== 'all' && getItemType(item.id) !== typeFilter)
|
||||
return false;
|
||||
if (
|
||||
search.trim() &&
|
||||
!item.name.toLowerCase().includes(search.toLowerCase())
|
||||
)
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
}, [items, typeFilter, search]);
|
||||
|
||||
const handleGive100Visible = () => filtered.forEach(i => onSetQty(i.id, 100));
|
||||
const handleRandVisible = () => filtered.forEach(i => onSetQty(i.id, randInRange(randMin, randMax)));
|
||||
const handleLockVisible = () => filtered.forEach(i => onSetQty(i.id, 0));
|
||||
const handleGive100Visible = () =>
|
||||
filtered.forEach((i) => onSetQty(i.id, 100));
|
||||
const handleRandVisible = () =>
|
||||
filtered.forEach((i) => onSetQty(i.id, randInRange(randMin, randMax)));
|
||||
const handleLockVisible = () => filtered.forEach((i) => onSetQty(i.id, 0));
|
||||
|
||||
const activeCount = Object.values(quantities).filter(q => q > 0).length;
|
||||
const activeCount = Object.values(quantities).filter((q) => q > 0).length;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={shared.toolbar}>
|
||||
<input
|
||||
className={shared.searchInput}
|
||||
type="text"
|
||||
placeholder="Search items..."
|
||||
value={search}
|
||||
onChange={e => setSearch(e.target.value)}
|
||||
/>
|
||||
return (
|
||||
<>
|
||||
<div className={shared.toolbar}>
|
||||
<input
|
||||
className={shared.searchInput}
|
||||
type='text'
|
||||
placeholder='Search items...'
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
|
||||
<div className={shared.roleFilter}>
|
||||
{(Object.keys(ITEM_TYPE_LABELS) as ItemType[]).map(t => (
|
||||
<button
|
||||
key={t}
|
||||
className={`${shared.roleBtn} ${typeFilter === t ? shared.roleBtnActive : ''}`}
|
||||
onClick={() => setTypeFilter(t)}
|
||||
>
|
||||
{ITEM_TYPE_LABELS[t]}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className={shared.roleFilter}>
|
||||
{(Object.keys(ITEM_TYPE_LABELS) as ItemType[]).map((t) => (
|
||||
<button
|
||||
key={t}
|
||||
className={`${shared.roleBtn} ${typeFilter === t ? shared.roleBtnActive : ''}`}
|
||||
onClick={() => setTypeFilter(t)}
|
||||
>
|
||||
{ITEM_TYPE_LABELS[t]}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<span className={shared.spacer} />
|
||||
<span className={shared.resultCount}>{filtered.length} shown · {activeCount} active</span>
|
||||
<span className={shared.spacer} />
|
||||
<span className={shared.resultCount}>
|
||||
{filtered.length} shown · {activeCount} active
|
||||
</span>
|
||||
|
||||
<button className={shared.unlockAllBtn} onClick={handleGive100Visible}>Set all to 100</button>
|
||||
<button className={styles.randBtn} onClick={handleRandVisible}>Randomize</button>
|
||||
<button className={shared.lockAllBtn} onClick={handleLockVisible}>Remove all visible</button>
|
||||
</div>
|
||||
<button className={shared.unlockAllBtn} onClick={handleGive100Visible}>
|
||||
Set all to 100
|
||||
</button>
|
||||
<button className={styles.randBtn} onClick={handleRandVisible}>
|
||||
Randomize
|
||||
</button>
|
||||
<button className={shared.lockAllBtn} onClick={handleLockVisible}>
|
||||
Remove all visible
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{filtered.length === 0 ? (
|
||||
<div className={shared.empty}>No items match</div>
|
||||
) : (
|
||||
<div className={styles.grid}>
|
||||
{filtered.map(item => (
|
||||
<QuantityCard
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
name={item.name}
|
||||
iconUrl={getItemIconUrl(item.iconFilePath)}
|
||||
qty={quantities[item.id] ?? 0}
|
||||
randMin={randMin}
|
||||
randMax={randMax}
|
||||
onSetQty={onSetQty}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
{filtered.length === 0 ? (
|
||||
<div className={shared.empty}>No items match</div>
|
||||
) : (
|
||||
<div className={styles.grid}>
|
||||
{filtered.map((item) => (
|
||||
<QuantityCard
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
name={item.name}
|
||||
iconUrl={getItemIconUrl(item.iconFilePath)}
|
||||
qty={quantities[item.id] ?? 0}
|
||||
randMin={randMin}
|
||||
randMax={randMax}
|
||||
onSetQty={onSetQty}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
+93
-73
@@ -8,92 +8,112 @@ import { Offering, OfferingRole, getOfferingIconUrl } from './types';
|
||||
import { randInRange } from '@/lib/utils';
|
||||
|
||||
type Props = {
|
||||
offerings: Offering[];
|
||||
quantities: Record<string, number>;
|
||||
randMin: number;
|
||||
randMax: number;
|
||||
onSetQty: (id: string, qty: number) => void;
|
||||
offerings: Offering[];
|
||||
quantities: Record<string, number>;
|
||||
randMin: number;
|
||||
randMax: number;
|
||||
onSetQty: (id: string, qty: number) => void;
|
||||
};
|
||||
|
||||
const ROLE_LABELS: Record<OfferingRole, string> = {
|
||||
all: 'All', shared: 'Shared', killer: 'Killer', survivor: 'Survivor',
|
||||
all: 'All',
|
||||
shared: 'Shared',
|
||||
killer: 'Killer',
|
||||
survivor: 'Survivor'
|
||||
};
|
||||
|
||||
const roleMatches = (offeringRole: number, filter: OfferingRole) => {
|
||||
if (filter === 'all') return true;
|
||||
if (filter === 'shared') return offeringRole === 0;
|
||||
if (filter === 'killer') return offeringRole === 1;
|
||||
if (filter === 'survivor') return offeringRole === 2;
|
||||
return true;
|
||||
if (filter === 'all') return true;
|
||||
if (filter === 'shared') return offeringRole === 0;
|
||||
if (filter === 'killer') return offeringRole === 1;
|
||||
if (filter === 'survivor') return offeringRole === 2;
|
||||
return true;
|
||||
};
|
||||
|
||||
export default function OfferingGrid({ offerings, quantities, randMin, randMax, onSetQty }: Props) {
|
||||
const [search, setSearch] = useState('');
|
||||
const [roleFilter, setRoleFilter] = useState<OfferingRole>('all');
|
||||
export default function OfferingGrid({
|
||||
offerings,
|
||||
quantities,
|
||||
randMin,
|
||||
randMax,
|
||||
onSetQty
|
||||
}: Props) {
|
||||
const [search, setSearch] = useState('');
|
||||
const [roleFilter, setRoleFilter] = useState<OfferingRole>('all');
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
return offerings.filter(o => {
|
||||
if (!roleMatches(o.role, roleFilter)) return false;
|
||||
if (search.trim() && !o.name.toLowerCase().includes(search.toLowerCase())) return false;
|
||||
return true;
|
||||
});
|
||||
}, [offerings, roleFilter, search]);
|
||||
const filtered = useMemo(() => {
|
||||
return offerings.filter((o) => {
|
||||
if (!roleMatches(o.role, roleFilter)) return false;
|
||||
if (search.trim() && !o.name.toLowerCase().includes(search.toLowerCase()))
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
}, [offerings, roleFilter, search]);
|
||||
|
||||
const handleGive100Visible = () => filtered.forEach(o => onSetQty(o.id, 100));
|
||||
const handleRandVisible = () => filtered.forEach(o => onSetQty(o.id, randInRange(randMin, randMax)));
|
||||
const handleLockVisible = () => filtered.forEach(o => onSetQty(o.id, 0));
|
||||
const handleGive100Visible = () =>
|
||||
filtered.forEach((o) => onSetQty(o.id, 100));
|
||||
const handleRandVisible = () =>
|
||||
filtered.forEach((o) => onSetQty(o.id, randInRange(randMin, randMax)));
|
||||
const handleLockVisible = () => filtered.forEach((o) => onSetQty(o.id, 0));
|
||||
|
||||
const activeCount = Object.values(quantities).filter(q => q > 0).length;
|
||||
const activeCount = Object.values(quantities).filter((q) => q > 0).length;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={shared.toolbar}>
|
||||
<input
|
||||
className={shared.searchInput}
|
||||
type="text"
|
||||
placeholder="Search offerings..."
|
||||
value={search}
|
||||
onChange={e => setSearch(e.target.value)}
|
||||
/>
|
||||
return (
|
||||
<>
|
||||
<div className={shared.toolbar}>
|
||||
<input
|
||||
className={shared.searchInput}
|
||||
type='text'
|
||||
placeholder='Search offerings...'
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
|
||||
<div className={shared.roleFilter}>
|
||||
{(Object.keys(ROLE_LABELS) as OfferingRole[]).map(r => (
|
||||
<button
|
||||
key={r}
|
||||
className={`${shared.roleBtn} ${roleFilter === r ? shared.roleBtnActive : ''}`}
|
||||
onClick={() => setRoleFilter(r)}
|
||||
>
|
||||
{ROLE_LABELS[r]}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className={shared.roleFilter}>
|
||||
{(Object.keys(ROLE_LABELS) as OfferingRole[]).map((r) => (
|
||||
<button
|
||||
key={r}
|
||||
className={`${shared.roleBtn} ${roleFilter === r ? shared.roleBtnActive : ''}`}
|
||||
onClick={() => setRoleFilter(r)}
|
||||
>
|
||||
{ROLE_LABELS[r]}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<span className={shared.spacer} />
|
||||
<span className={shared.resultCount}>{filtered.length} shown · {activeCount} active</span>
|
||||
<span className={shared.spacer} />
|
||||
<span className={shared.resultCount}>
|
||||
{filtered.length} shown · {activeCount} active
|
||||
</span>
|
||||
|
||||
<button className={shared.unlockAllBtn} onClick={handleGive100Visible}>Set all to 100</button>
|
||||
<button className={styles.randBtn} onClick={handleRandVisible}>Randomize</button>
|
||||
<button className={shared.lockAllBtn} onClick={handleLockVisible}>Remove all visible</button>
|
||||
</div>
|
||||
<button className={shared.unlockAllBtn} onClick={handleGive100Visible}>
|
||||
Set all to 100
|
||||
</button>
|
||||
<button className={styles.randBtn} onClick={handleRandVisible}>
|
||||
Randomize
|
||||
</button>
|
||||
<button className={shared.lockAllBtn} onClick={handleLockVisible}>
|
||||
Remove all visible
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{filtered.length === 0 ? (
|
||||
<div className={shared.empty}>No offerings match</div>
|
||||
) : (
|
||||
<div className={styles.grid}>
|
||||
{filtered.map(offering => (
|
||||
<QuantityCard
|
||||
key={offering.id}
|
||||
id={offering.id}
|
||||
name={offering.name}
|
||||
iconUrl={getOfferingIconUrl(offering.iconFilePath)}
|
||||
qty={quantities[offering.id] ?? 0}
|
||||
randMin={randMin}
|
||||
randMax={randMax}
|
||||
onSetQty={onSetQty}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
{filtered.length === 0 ? (
|
||||
<div className={shared.empty}>No offerings match</div>
|
||||
) : (
|
||||
<div className={styles.grid}>
|
||||
{filtered.map((offering) => (
|
||||
<QuantityCard
|
||||
key={offering.id}
|
||||
id={offering.id}
|
||||
name={offering.name}
|
||||
iconUrl={getOfferingIconUrl(offering.iconFilePath)}
|
||||
qty={quantities[offering.id] ?? 0}
|
||||
randMin={randMin}
|
||||
randMax={randMax}
|
||||
onSetQty={onSetQty}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
+105
-95
@@ -15,110 +15,120 @@ import AddonGrid from './AddonGrid';
|
||||
type Tab = 'items' | 'offerings' | 'addons';
|
||||
|
||||
export default function ItemsPage() {
|
||||
const store = useInventoryStore();
|
||||
const store = useInventoryStore();
|
||||
|
||||
const [tab, setTab] = useState<Tab>('items');
|
||||
const [items, setItems] = useState<Item[]>([]);
|
||||
const [offerings, setOfferings] = useState<Offering[]>([]);
|
||||
const [addons, setAddons] = useState<Addon[]>([]);
|
||||
const [tab, setTab] = useState<Tab>('items');
|
||||
const [items, setItems] = useState<Item[]>([]);
|
||||
const [offerings, setOfferings] = useState<Offering[]>([]);
|
||||
const [addons, setAddons] = useState<Addon[]>([]);
|
||||
|
||||
const [randMin, setRandMin] = useState(50);
|
||||
const [randMax, setRandMax] = useState(200);
|
||||
const [randMin, setRandMin] = useState(50);
|
||||
const [randMax, setRandMax] = useState(200);
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([fetchItems(), fetchOfferings(), fetchAddons()])
|
||||
.then(([i, o, a]) => {
|
||||
setItems(i);
|
||||
setOfferings(o);
|
||||
setAddons(a);
|
||||
});
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
Promise.all([fetchItems(), fetchOfferings(), fetchAddons()]).then(
|
||||
([i, o, a]) => {
|
||||
setItems(i);
|
||||
setOfferings(o);
|
||||
setAddons(a);
|
||||
}
|
||||
);
|
||||
}, []);
|
||||
|
||||
const handleClearAll = () => {
|
||||
store.clearCategory('items');
|
||||
store.clearCategory('offerings');
|
||||
store.clearCategory('addons');
|
||||
};
|
||||
const handleClearAll = () => {
|
||||
store.clearCategory('items');
|
||||
store.clearCategory('offerings');
|
||||
store.clearCategory('addons');
|
||||
};
|
||||
|
||||
const activeItems = Object.values(store.items).filter(q => q > 0).length;
|
||||
const activeOfferings = Object.values(store.offerings).filter(q => q > 0).length;
|
||||
const activeAddons = Object.values(store.addons).filter(q => q > 0).length;
|
||||
const activeItems = Object.values(store.items).filter((q) => q > 0).length;
|
||||
const activeOfferings = Object.values(store.offerings).filter(
|
||||
(q) => q > 0
|
||||
).length;
|
||||
const activeAddons = Object.values(store.addons).filter((q) => q > 0).length;
|
||||
|
||||
return (
|
||||
<div className={shared.container}>
|
||||
<header className={shared.header}>
|
||||
<div>
|
||||
<h1 className={shared.title}>Items & Offerings</h1>
|
||||
<p className={shared.subtitle}>
|
||||
{activeItems} items · {activeOfferings} offerings · {activeAddons} addons
|
||||
</p>
|
||||
</div>
|
||||
return (
|
||||
<div className={shared.container}>
|
||||
<header className={shared.header}>
|
||||
<div>
|
||||
<h1 className={shared.title}>Items & Offerings</h1>
|
||||
<p className={shared.subtitle}>
|
||||
{activeItems} items · {activeOfferings} offerings · {activeAddons}{' '}
|
||||
addons
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={styles.rangeGroup}>
|
||||
<span className={styles.rangeLabel}>Rand range</span>
|
||||
<input
|
||||
className={styles.rangeInput}
|
||||
type="number"
|
||||
value={randMin}
|
||||
min={0}
|
||||
max={5000}
|
||||
onChange={e => setRandMin(Math.max(0, parseInt(e.target.value) || 0))}
|
||||
/>
|
||||
<span className={styles.rangeSep}>–</span>
|
||||
<input
|
||||
className={styles.rangeInput}
|
||||
type="number"
|
||||
value={randMax}
|
||||
min={0}
|
||||
max={5000}
|
||||
onChange={e => setRandMax(Math.min(5000, parseInt(e.target.value) || 0))}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.rangeGroup}>
|
||||
<span className={styles.rangeLabel}>Rand range</span>
|
||||
<input
|
||||
className={styles.rangeInput}
|
||||
type='number'
|
||||
value={randMin}
|
||||
min={0}
|
||||
max={5000}
|
||||
onChange={(e) =>
|
||||
setRandMin(Math.max(0, parseInt(e.target.value) || 0))
|
||||
}
|
||||
/>
|
||||
<span className={styles.rangeSep}>–</span>
|
||||
<input
|
||||
className={styles.rangeInput}
|
||||
type='number'
|
||||
value={randMax}
|
||||
min={0}
|
||||
max={5000}
|
||||
onChange={(e) =>
|
||||
setRandMax(Math.min(5000, parseInt(e.target.value) || 0))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button className={shared.clearBtn} onClick={handleClearAll}>Clear All</button>
|
||||
</header>
|
||||
<button className={shared.clearBtn} onClick={handleClearAll}>
|
||||
Clear All
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<div className={styles.tabs}>
|
||||
{(['items', 'offerings', 'addons'] as Tab[]).map(t => (
|
||||
<button
|
||||
key={t}
|
||||
className={`${styles.tab} ${tab === t ? styles.tabActive : ''}`}
|
||||
onClick={() => setTab(t)}
|
||||
>
|
||||
{t}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className={styles.tabs}>
|
||||
{(['items', 'offerings', 'addons'] as Tab[]).map((t) => (
|
||||
<button
|
||||
key={t}
|
||||
className={`${styles.tab} ${tab === t ? styles.tabActive : ''}`}
|
||||
onClick={() => setTab(t)}
|
||||
>
|
||||
{t}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{tab === 'items' && (
|
||||
<ItemGrid
|
||||
items={items}
|
||||
quantities={store.items}
|
||||
randMin={randMin}
|
||||
randMax={randMax}
|
||||
onSetQty={store.setItemQuantity}
|
||||
/>
|
||||
)}
|
||||
{tab === 'items' && (
|
||||
<ItemGrid
|
||||
items={items}
|
||||
quantities={store.items}
|
||||
randMin={randMin}
|
||||
randMax={randMax}
|
||||
onSetQty={store.setItemQuantity}
|
||||
/>
|
||||
)}
|
||||
|
||||
{tab === 'offerings' && (
|
||||
<OfferingGrid
|
||||
offerings={offerings}
|
||||
quantities={store.offerings}
|
||||
randMin={randMin}
|
||||
randMax={randMax}
|
||||
onSetQty={store.setOfferingQuantity}
|
||||
/>
|
||||
)}
|
||||
{tab === 'offerings' && (
|
||||
<OfferingGrid
|
||||
offerings={offerings}
|
||||
quantities={store.offerings}
|
||||
randMin={randMin}
|
||||
randMax={randMax}
|
||||
onSetQty={store.setOfferingQuantity}
|
||||
/>
|
||||
)}
|
||||
|
||||
{tab === 'addons' && (
|
||||
<AddonGrid
|
||||
addons={addons}
|
||||
quantities={store.addons}
|
||||
randMin={randMin}
|
||||
randMax={randMax}
|
||||
onSetQty={store.setAddonQuantity}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
{tab === 'addons' && (
|
||||
<AddonGrid
|
||||
addons={addons}
|
||||
quantities={store.addons}
|
||||
randMin={randMin}
|
||||
randMax={randMax}
|
||||
onSetQty={store.setAddonQuantity}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+41
-29
@@ -1,59 +1,71 @@
|
||||
import { DB_BASE_URL } from '../../lib/db';
|
||||
|
||||
export type Item = {
|
||||
id: string;
|
||||
name: string;
|
||||
iconFilePath: string;
|
||||
id: string;
|
||||
name: string;
|
||||
iconFilePath: string;
|
||||
};
|
||||
|
||||
export type Offering = {
|
||||
id: string;
|
||||
name: string;
|
||||
iconFilePath: string;
|
||||
role: number;
|
||||
id: string;
|
||||
name: string;
|
||||
iconFilePath: string;
|
||||
role: number;
|
||||
};
|
||||
|
||||
export type Addon = {
|
||||
id: string;
|
||||
name: string;
|
||||
iconFilePath: string;
|
||||
role: number;
|
||||
id: string;
|
||||
name: string;
|
||||
iconFilePath: string;
|
||||
role: number;
|
||||
};
|
||||
|
||||
export type ItemType = 'all' | 'toolbox' | 'flashlight' | 'medkit' | 'key' | 'map' | 'other';
|
||||
export type ItemType =
|
||||
| 'all'
|
||||
| 'toolbox'
|
||||
| 'flashlight'
|
||||
| 'medkit'
|
||||
| 'key'
|
||||
| 'map'
|
||||
| 'other';
|
||||
export type OfferingRole = 'all' | 'shared' | 'killer' | 'survivor';
|
||||
|
||||
export const ITEM_TYPE_LABELS: Record<ItemType, string> = {
|
||||
all: 'All', toolbox: 'Toolbox', flashlight: 'Flashlight',
|
||||
medkit: 'Med-Kit', key: 'Key', map: 'Map', other: 'Other',
|
||||
all: 'All',
|
||||
toolbox: 'Toolbox',
|
||||
flashlight: 'Flashlight',
|
||||
medkit: 'Med-Kit',
|
||||
key: 'Key',
|
||||
map: 'Map',
|
||||
other: 'Other'
|
||||
};
|
||||
|
||||
export const getItemType = (id: string): ItemType => {
|
||||
if (/toolbox/i.test(id)) return 'toolbox';
|
||||
if (/flashlight/i.test(id)) return 'flashlight';
|
||||
if (/medkit|med_?kit/i.test(id)) return 'medkit';
|
||||
if (/key/i.test(id)) return 'key';
|
||||
if (/map/i.test(id)) return 'map';
|
||||
return 'other';
|
||||
if (/toolbox/i.test(id)) return 'toolbox';
|
||||
if (/flashlight/i.test(id)) return 'flashlight';
|
||||
if (/medkit|med_?kit/i.test(id)) return 'medkit';
|
||||
if (/key/i.test(id)) return 'key';
|
||||
if (/map/i.test(id)) return 'map';
|
||||
return 'other';
|
||||
};
|
||||
|
||||
export const getItemIconUrl = (iconFilePath: string) => {
|
||||
const file = (iconFilePath.split('/').pop() ?? '').split('.')[0];
|
||||
return `${DB_BASE_URL}/icons/item-icons/${file}.png`;
|
||||
const file = (iconFilePath.split('/').pop() ?? '').split('.')[0];
|
||||
return `${DB_BASE_URL}/icons/item-icons/${file}.png`;
|
||||
};
|
||||
|
||||
export const getOfferingIconUrl = (iconFilePath: string) => {
|
||||
const file = (iconFilePath.split('/').pop() ?? '').split('.')[0];
|
||||
return `${DB_BASE_URL}/icons/offering-icons/${file}.png`;
|
||||
const file = (iconFilePath.split('/').pop() ?? '').split('.')[0];
|
||||
return `${DB_BASE_URL}/icons/offering-icons/${file}.png`;
|
||||
};
|
||||
|
||||
export const getAddonIconUrl = (iconFilePath: string) => {
|
||||
const file = (iconFilePath.split('/').pop() ?? '').split('.')[0];
|
||||
return `${DB_BASE_URL}/icons/addon-icons/${file}.png`;
|
||||
const file = (iconFilePath.split('/').pop() ?? '').split('.')[0];
|
||||
return `${DB_BASE_URL}/icons/addon-icons/${file}.png`;
|
||||
};
|
||||
|
||||
export const randInRange = (min: number, max: number) => {
|
||||
const lo = Math.min(min, max);
|
||||
const hi = Math.max(min, max);
|
||||
return Math.floor(Math.random() * (hi - lo + 1)) + lo;
|
||||
const lo = Math.min(min, max);
|
||||
const hi = Math.max(min, max);
|
||||
return Math.floor(Math.random() * (hi - lo + 1)) + lo;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user