Skip to content

Commit 1625fba

Browse files
marcodejonghclaude
andauthored
Add tall climbs only filter for Kilter Homewall 10x12 (#304)
This filter allows users to show only climbs that use holds in the bottom 8 rows that are exclusive to the 10x12 board size. The filter: - Only appears on Kilter Homewall (layout_id=8) when on the largest size - Uses a SQL subquery to find climbs with edge_bottom below the second-largest size's threshold - Adds tooltip explaining the filter's purpose - Properly integrates with existing URL params and analytics tracking Co-authored-by: Claude <[email protected]>
1 parent 8844bb3 commit 1625fba

File tree

9 files changed

+73
-6
lines changed

9 files changed

+73
-6
lines changed

app/[board_name]/[layout_id]/[size_id]/[set_ids]/[angle]/list/layout-client.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ const TabsWrapper: React.FC<{ boardDetails: BoardDetails }> = ({ boardDetails })
4444
children: (
4545
<div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
4646
<div style={{ flex: 1, overflow: 'auto' }}>
47-
<BasicSearchForm />
47+
<BasicSearchForm boardDetails={boardDetails} />
4848
</div>
4949
<SearchResultsFooter />
5050
</div>

app/components/queue-control/__tests__/hooks/use-queue-data-fetching.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ const mockSearchParams: SearchRequestPagination = {
7575
sortOrder: 'desc',
7676
name: '',
7777
onlyClassics: false,
78+
onlyTallClimbs: false,
7879
settername: [],
7980
setternameSuggestion: '',
8081
holdsFilter: {},

app/components/queue-control/__tests__/reducer.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const mockSearchParams: SearchRequestPagination = {
4141
sortOrder: 'desc',
4242
name: '',
4343
onlyClassics: false,
44+
onlyTallClimbs: false,
4445
settername: [],
4546
setternameSuggestion: '',
4647
holdsFilter: {},
@@ -286,6 +287,7 @@ describe('queueReducer', () => {
286287
sortOrder: 'asc',
287288
name: '',
288289
onlyClassics: false,
290+
onlyTallClimbs: false,
289291
settername: [],
290292
setternameSuggestion: '',
291293
holdsFilter: {},

app/components/queue-control/ui-searchparams-provider.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export const UISearchParamsProvider: React.FC<{ children: React.ReactNode }> = (
3737
if (uiSearchParams.settername.length > 0) activeFilters.push('setter');
3838
if (uiSearchParams.holdsFilter && Object.entries(uiSearchParams.holdsFilter).length > 0)
3939
activeFilters.push('holds');
40+
if (uiSearchParams.onlyTallClimbs) activeFilters.push('onlyTallClimbs');
4041
if (uiSearchParams.hideAttempted) activeFilters.push('hideAttempted');
4142
if (uiSearchParams.hideCompleted) activeFilters.push('hideCompleted');
4243
if (uiSearchParams.showOnlyAttempted) activeFilters.push('showOnlyAttempted');

app/components/search-drawer/basic-search-form.tsx

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,36 @@
11
'use client';
22

33
import React from 'react';
4-
import { Form, InputNumber, Row, Col, Select, Switch, Alert, Typography } from 'antd';
4+
import { Form, InputNumber, Row, Col, Select, Switch, Alert, Typography, Tooltip } from 'antd';
55
import { TENSION_KILTER_GRADES } from '@/app/lib/board-data';
66
import { useUISearchParams } from '@/app/components/queue-control/ui-searchparams-provider';
77
import { useBoardProvider } from '@/app/components/board-provider/board-provider-context';
88
import SearchClimbNameInput from './search-climb-name-input';
99
import SetterNameSelect from './setter-name-select';
10+
import { BoardDetails } from '@/app/lib/types';
1011

1112
const { Title } = Typography;
1213

13-
const BasicSearchForm: React.FC = () => {
14+
// Kilter Homewall layout ID
15+
const KILTER_HOMEWALL_LAYOUT_ID = 8;
16+
17+
interface BasicSearchFormProps {
18+
boardDetails: BoardDetails;
19+
}
20+
21+
const BasicSearchForm: React.FC<BasicSearchFormProps> = ({ boardDetails }) => {
1422
const { uiSearchParams, updateFilters } = useUISearchParams();
1523
const { token, user_id } = useBoardProvider();
1624
const grades = TENSION_KILTER_GRADES;
17-
25+
1826
const isLoggedIn = token && user_id;
1927

28+
// Check if we should show the tall climbs filter
29+
// Only show for Kilter Homewall on the largest size (10x12)
30+
const isKilterHomewall = boardDetails.board_name === 'kilter' && boardDetails.layout_id === KILTER_HOMEWALL_LAYOUT_ID;
31+
const isLargestSize = boardDetails.size_name?.toLowerCase().includes('12');
32+
const showTallClimbsFilter = isKilterHomewall && isLargestSize;
33+
2034
const handleGradeChange = (type: 'min' | 'max', value: number | undefined) => {
2135
if (type === 'min') {
2236
updateFilters({ minGrade: value });
@@ -178,6 +192,23 @@ const BasicSearchForm: React.FC = () => {
178192
/>
179193
</Form.Item>
180194

195+
{showTallClimbsFilter && (
196+
<Form.Item
197+
label={
198+
<Tooltip title="Show only climbs that use holds in the bottom 8 rows (only available on 10x12 boards)">
199+
Tall Climbs Only
200+
</Tooltip>
201+
}
202+
valuePropName="checked"
203+
>
204+
<Switch
205+
style={{ float: 'right' }}
206+
checked={uiSearchParams.onlyTallClimbs}
207+
onChange={(checked) => updateFilters({ onlyTallClimbs: checked })}
208+
/>
209+
</Form.Item>
210+
)}
211+
181212
<Form.Item label="Grade Accuracy">
182213
<Select
183214
value={uiSearchParams.gradeAccuracy}

app/components/search-drawer/search-form.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const SearchForm: React.FC<SearchFormProps> = ({ boardDetails }) => {
1414
{
1515
key: 'filters',
1616
label: 'Search',
17-
children: <BasicSearchForm />,
17+
children: <BasicSearchForm boardDetails={boardDetails} />,
1818
},
1919
{
2020
key: 'holds',

app/lib/db/queries/climbs/create-climb-filters.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,30 @@ export const createClimbFilters = (
9494
...notHolds.map((holdId) => notLike(tables.climbs.frames, `%${holdId}r%`)),
9595
];
9696

97+
// Tall climbs filter condition
98+
// Only applies for Kilter Homewall (layout_id = 8) on the largest size
99+
// A "tall climb" is one that uses holds in the bottom rows that are only available on the largest size
100+
const tallClimbsConditions: SQL[] = [];
101+
const KILTER_HOMEWALL_LAYOUT_ID = 8;
102+
103+
if (searchParams.onlyTallClimbs && params.board_name === 'kilter' && params.layout_id === KILTER_HOMEWALL_LAYOUT_ID) {
104+
// Find the maximum edge_bottom of all sizes smaller than the current size
105+
// Climbs with edge_bottom below this threshold use "tall only" holds
106+
const productSizesTable = getTableName(params.board_name, 'product_sizes');
107+
const layoutsTable = getTableName(params.board_name, 'layouts');
108+
109+
tallClimbsConditions.push(
110+
sql`${tables.climbs.edgeBottom} < (
111+
SELECT MAX(other_sizes.edge_bottom)
112+
FROM ${sql.identifier(productSizesTable)} other_sizes
113+
INNER JOIN ${sql.identifier(layoutsTable)} layouts ON other_sizes.product_id = layouts.product_id
114+
WHERE layouts.id = ${params.layout_id}
115+
AND other_sizes.id != ${params.size_id}
116+
AND other_sizes.edge_bottom < ${sizeTable.edgeTop}
117+
)`
118+
);
119+
}
120+
97121
// Personal progress filter conditions (only apply if userId is provided)
98122
const personalProgressConditions: SQL[] = [];
99123
if (userId) {
@@ -193,7 +217,7 @@ export const createClimbFilters = (
193217

194218
return {
195219
// Helper function to get all climb filtering conditions
196-
getClimbWhereConditions: () => [...baseConditions, ...nameCondition, ...setterNameCondition, ...holdConditions, ...personalProgressConditions],
220+
getClimbWhereConditions: () => [...baseConditions, ...nameCondition, ...setterNameCondition, ...holdConditions, ...tallClimbsConditions, ...personalProgressConditions],
197221

198222
// Size-specific conditions
199223
getSizeConditions: () => sizeConditions,
@@ -225,6 +249,7 @@ export const createClimbFilters = (
225249
nameCondition,
226250
setterNameCondition,
227251
holdConditions,
252+
tallClimbsConditions,
228253
sizeConditions,
229254
personalProgressConditions,
230255
anyHolds,

app/lib/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export type SearchRequest = {
8686
sortOrder: 'asc' | 'desc';
8787
name: string;
8888
onlyClassics: boolean;
89+
onlyTallClimbs: boolean;
8990
settername: string[];
9091
setternameSuggestion: string;
9192
holdsFilter: LitUpHoldsMap;

app/lib/url-utils.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export const searchParamsToUrlParams = ({
4747
sortOrder,
4848
name,
4949
onlyClassics,
50+
onlyTallClimbs,
5051
settername,
5152
setternameSuggestion,
5253
holdsFilter,
@@ -87,6 +88,9 @@ export const searchParamsToUrlParams = ({
8788
if (onlyClassics !== DEFAULT_SEARCH_PARAMS.onlyClassics) {
8889
params.onlyClassics = onlyClassics.toString();
8990
}
91+
if (onlyTallClimbs !== DEFAULT_SEARCH_PARAMS.onlyTallClimbs) {
92+
params.onlyTallClimbs = onlyTallClimbs.toString();
93+
}
9094
if (settername && settername.length > 0) {
9195
params.settername = settername.join(',');
9296
}
@@ -131,6 +135,7 @@ export const DEFAULT_SEARCH_PARAMS: SearchRequestPagination = {
131135
sortOrder: 'desc',
132136
name: '',
133137
onlyClassics: false,
138+
onlyTallClimbs: false,
134139
settername: [],
135140
setternameSuggestion: '',
136141
holdsFilter: {},
@@ -160,6 +165,7 @@ export const urlParamsToSearchParams = (urlParams: URLSearchParams): SearchReque
160165
sortOrder: (urlParams.get('sortOrder') ?? DEFAULT_SEARCH_PARAMS.sortOrder) as 'asc' | 'desc',
161166
name: urlParams.get('name') ?? DEFAULT_SEARCH_PARAMS.name,
162167
onlyClassics: urlParams.get('onlyClassics') === 'true',
168+
onlyTallClimbs: urlParams.get('onlyTallClimbs') === 'true',
163169
settername: urlParams.get('settername')?.split(',').filter(s => s.length > 0) ?? DEFAULT_SEARCH_PARAMS.settername,
164170
setternameSuggestion: urlParams.get('setternameSuggestion') ?? DEFAULT_SEARCH_PARAMS.setternameSuggestion,
165171
//@ts-expect-error fix later

0 commit comments

Comments
 (0)