Skip to content

Commit

Permalink
Enabled filtering fish bait tables
Browse files Browse the repository at this point in the history
  • Loading branch information
codeaid committed May 27, 2024
1 parent 7c43350 commit 5b6429e
Show file tree
Hide file tree
Showing 11 changed files with 342 additions and 119 deletions.
14 changes: 13 additions & 1 deletion src/components/FishInfoBaits/FishInfoBaits.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
display: grid;
grid-gap: 3rem;
grid-template-columns: 1fr;
margin-top: 1rem;
}

/**
Expand Down Expand Up @@ -99,6 +100,13 @@
text-align: center;
}

.FishInfoBaitsEmpty {
font-size: 0.9em;
padding: 1em;
text-align: center;
user-select: none;
}

/**
* Table rows containing bait names and chance columns
*/
Expand All @@ -120,8 +128,10 @@
}

.FishInfoBaitsValue {
align-items: center;
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
user-select: none;
}

Expand All @@ -140,6 +150,8 @@
*/
.FishInfoBaitsValues {
border-bottom: thin solid var(--color-border-dark);
display: flex;
justify-content: center;
}

/**
Expand Down
224 changes: 109 additions & 115 deletions src/components/FishInfoBaits/FishInfoBaits.tsx
Original file line number Diff line number Diff line change
@@ -1,148 +1,142 @@
'use client';

import clsx from 'clsx';
import { useCallback } from 'react';
import {
baitKindMap,
baitsBottom,
baitsLive,
baitsNatural,
lureMethods,
lures,
} from 'config/baits';
import type { Bait, Lure, LureChanceMap } from 'types/baits';
import { useMemo, useState } from 'react';
import { Option, Stepper } from 'components';
import { baitKindMap, baitsBottom, baitsLive, baitsNatural, lures } from 'config/baits';
import { getFilteredBaitValues, getFilteredLureValues } from 'lib/filter';
import type { BaitFilterType, BaitFilterValue } from 'types/baits';
import { FishInfoBaitsBaits } from './FishInfoBaitsBaits';
import { FishInfoBaitsLures } from './FishInfoBaitsLures';
import { FishInfoBaitsRating } from './FishInfoBaitsRating';
import type { FishInfoBaitsProps } from './types';
import styles from './FishInfoBaits.module.css';

export const FishInfoBaits = (props: FishInfoBaitsProps) => {
const { data } = props;

// All available bait and lure filter types
const filterTypes: BaitFilterType[] = ['all', 'available', '1', '2', '3'];

// Currently selected bait and lure filter types
const [filter, setFilter] = useState<BaitFilterType>('available');

// Bait values filtered to only include matching ones
const baitValues = useMemo<[BaitFilterValue[], string, string, string][]>(
() => [
[
getFilteredBaitValues(baitsNatural, filter, data),
baitKindMap.natural.name,
styles.FishInfoBaitsKindWrapperNatural,
styles.FishInfoBaitsKindHeaderNatural,
],
[
getFilteredBaitValues(baitsLive, filter, data),
baitKindMap.live.name,
styles.FishInfoBaitsKindWrapperLive,
styles.FishInfoBaitsKindHeaderLive,
],
[
getFilteredBaitValues(baitsBottom, filter, data),
baitKindMap.bottom.name,
styles.FishInfoBaitsKindWrapperBottom,
styles.FishInfoBaitsKindHeaderBottom,
],
],
[data, filter],
);

// Lure values filtered to only include matching ones
const lureValues = useMemo(() => getFilteredLureValues(lures, filter, data), [data, filter]);

/**
* Render an individual bait chance value
* Render all available bait category elements
*/
const renderValue = useCallback((value?: number) => {
if (typeof value !== 'number') {
const renderedBaits = useMemo(() => {
// Check if any of the bait type categories have baits included after filtering
const hasValues = baitValues.filter(([value]) => value.length > 0).length > 0;

if (!hasValues) {
return (
<div
className={clsx(styles.FishInfoBaitsValue, {
[styles.FishInfoBaitsValueEmpty]: typeof value === 'undefined',
})}
>
~
<div className={clsx(styles.FishInfoBaitsValueEmpty, styles.FishInfoBaitsEmpty)}>
No baits available
</div>
);
}

return (
<div className={styles.FishInfoBaitsValue}>
<FishInfoBaitsRating size={0.5} value={value} />
<div className={styles.FishInfoBaitsCategoryWrapperBaits}>
{baitValues.map(([values, category, wrapperClassName, headerClassName]) => (
<FishInfoBaitsBaits
caption={category}
headerClassName={headerClassName}
values={values}
wrapperClassName={wrapperClassName}
/>
))}
</div>
);
}, []);
}, [baitValues]);

/**
* Render a row in the baits table
* Render all available lure rows
*/
const renderBaitRow = useCallback(
(bait: Bait) => {
const value = data.bait ? data.bait[bait.id] : undefined;

const renderedLures = useMemo(() => {
if (!lureValues.length) {
return (
<div className={styles.FishInfoBaitsRow} key={bait.id}>
<div className={styles.FishInfoBaitsKey}>{bait.name}</div>
<div className={styles.FishInfoBaitsValues}>{renderValue(value)}</div>
<div className={clsx(styles.FishInfoBaitsValueEmpty, styles.FishInfoBaitsEmpty)}>
No lures available
</div>
);
},
[data.bait, renderValue],
);
}

/**
* Render a row in the lures table
*/
const renderLureRow = useCallback(
(lure: Lure) => {
const values: Partial<LureChanceMap> = data[lure.id] ?? {};

return (
<div className={clsx(styles.FishInfoBaitsRow, styles.FishInfoBaitsRowLures)} key={lure.id}>
<div className={styles.FishInfoBaitsKey}>{lure.name}</div>
<div className={clsx(styles.FishInfoBaitsValues, styles.FishInfoBaitsValuesLures)}>
{lureMethods.map(method => renderValue(values[method.id]))}
</div>
</div>
);
},
[data, renderValue],
);
return <FishInfoBaitsLures values={lureValues} />;
}, [lureValues]);

return (
<div className={styles.FishInfoBaits}>
<section>
<div
className={clsx(
styles.FishInfoBaitsCategoryHeader,
styles.FishInfoBaitsCategoryHeaderBaits,
)}
>
Baits
</div>
<>
<Option label="Filter">
<Stepper<BaitFilterType>
labels={{
'all': 'All',
'available': 'Available',
'1': <FishInfoBaitsRating size={0.5} value={1} />,
'2': <FishInfoBaitsRating size={0.5} value={2} />,
'3': <FishInfoBaitsRating size={0.5} value={3} />,
}}
selected={filter}
values={filterTypes}
onChange={setFilter}
/>
</Option>

<div className={styles.FishInfoBaitsCategoryWrapperBaits}>
{(
[
[
baitKindMap.natural.name,
styles.FishInfoBaitsKindWrapperNatural,
styles.FishInfoBaitsKindHeaderNatural,
baitsNatural,
],
[
baitKindMap.live.name,
styles.FishInfoBaitsKindWrapperLive,
styles.FishInfoBaitsKindHeaderLive,
baitsLive,
],
[
baitKindMap.bottom.name,
styles.FishInfoBaitsKindWrapperBottom,
styles.FishInfoBaitsKindHeaderBottom,
baitsBottom,
],
] as [string, string, string, Bait[]][]
).map(([caption, wrapperClassName, headerClassName, baits]) => (
<div className={wrapperClassName} key={caption}>
<div className={clsx(styles.FishInfoBaitsKindHeader, headerClassName)}>{caption}</div>
{baits.map(renderBaitRow)}
</div>
))}
</div>
</section>

<section>
<div
className={clsx(
styles.FishInfoBaitsCategoryHeader,
styles.FishInfoBaitsCategoryHeaderLures,
)}
>
Lures
</div>
<div className={styles.FishInfoBaits}>
<section>
<div
className={clsx(
styles.FishInfoBaitsCategoryHeader,
styles.FishInfoBaitsCategoryHeaderBaits,
)}
>
Baits
</div>

<div>
<div className={clsx(styles.FishInfoBaitsRow, styles.FishInfoBaitsRowLures)}>
<div>&#8203;</div>
<div className={clsx(styles.FishInfoBaitsValues, styles.FishInfoBaitsValuesLures)}>
{lureMethods.map(method => (
<div className={styles.FishInfoBaitsMethodHeader} key={method.id}>
{method.name}
</div>
))}
</div>
{renderedBaits}
</section>

<section>
<div
className={clsx(
styles.FishInfoBaitsCategoryHeader,
styles.FishInfoBaitsCategoryHeaderLures,
)}
>
Lures
</div>

{lures.map(renderLureRow)}
</div>
</section>
</div>
{renderedLures}
</section>
</div>
</>
);
};
22 changes: 22 additions & 0 deletions src/components/FishInfoBaits/FishInfoBaitsBaits.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import clsx from 'clsx';
import { FishInfoBaitsBaitsRow } from './FishInfoBaitsBaitsRow';
import type { FishInfoBaitsBaitsProps } from './types';
import styles from './FishInfoBaits.module.css';

export const FishInfoBaitsBaits = (props: FishInfoBaitsBaitsProps) => {
const { caption, headerClassName, values, wrapperClassName } = props;

if (!values.length) {
return null;
}

return (
<div className={wrapperClassName} key={caption}>
<div className={clsx(styles.FishInfoBaitsKindHeader, headerClassName)}>{caption}</div>

{values.map(value => (
<FishInfoBaitsBaitsRow key={value.bait.id} value={value} />
))}
</div>
);
};
16 changes: 16 additions & 0 deletions src/components/FishInfoBaits/FishInfoBaitsBaitsRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { FishInfoBaitsValue } from './FishInfoBaitsValue';
import type { FishInfoBaitsBaitsRowProps } from './types';
import styles from './FishInfoBaits.module.css';

export const FishInfoBaitsBaitsRow = (props: FishInfoBaitsBaitsRowProps) => {
const { value } = props;

return (
<div className={styles.FishInfoBaitsRow} key={value.bait.id}>
<div className={styles.FishInfoBaitsKey}>{value.bait.name}</div>
<div className={styles.FishInfoBaitsValues}>
<FishInfoBaitsValue chance={value.chance} />
</div>
</div>
);
};
28 changes: 28 additions & 0 deletions src/components/FishInfoBaits/FishInfoBaitsLures.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import clsx from 'clsx';
import { lureMethods } from 'config/baits';
import { FishInfoBaitsLuresRow } from './FishInfoBaitsLuresRow';
import type { FishInfoBaitsLuresProps } from './types';
import styles from './FishInfoBaits.module.css';

export const FishInfoBaitsLures = (props: FishInfoBaitsLuresProps) => {
const { values } = props;

return (
<>
<div className={clsx(styles.FishInfoBaitsRow, styles.FishInfoBaitsRowLures)}>
<div>&#8203;</div>
<div className={clsx(styles.FishInfoBaitsValues, styles.FishInfoBaitsValuesLures)}>
{lureMethods.map(method => (
<div className={styles.FishInfoBaitsMethodHeader} key={method.id}>
{method.name}
</div>
))}
</div>
</div>

{values.map(value => (
<FishInfoBaitsLuresRow key={value.lure.id} value={value} />
))}
</>
);
};
20 changes: 20 additions & 0 deletions src/components/FishInfoBaits/FishInfoBaitsLuresRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import clsx from 'clsx';
import { lureMethods } from 'config/baits';
import { FishInfoBaitsValue } from './FishInfoBaitsValue';
import type { FishInfoBaitsLuresRowProps } from './types';
import styles from './FishInfoBaits.module.css';

export const FishInfoBaitsLuresRow = (props: FishInfoBaitsLuresRowProps) => {
const { value } = props;

return (
<div className={clsx(styles.FishInfoBaitsRow, styles.FishInfoBaitsRowLures)}>
<div className={styles.FishInfoBaitsKey}>{value.lure.name}</div>
<div className={clsx(styles.FishInfoBaitsValues, styles.FishInfoBaitsValuesLures)}>
{lureMethods.map(method => (
<FishInfoBaitsValue chance={value.value[method.id]} key={method.id} />
))}
</div>
</div>
);
};
Loading

0 comments on commit 5b6429e

Please sign in to comment.