From a54b115030bf1a80447628adc36adcb326619357 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 15 Nov 2025 01:28:13 +0000 Subject: [PATCH 01/90] Convert Answer view components from JavaScript to TypeScript MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Converted 5 Answer view components to TypeScript: - AnswerAttributeSelector.jsx → .tsx - AnswerFilter.jsx → .tsx - AnswerFilterSelector.jsx → .tsx - AnswerTable.jsx → .tsx - AnswerTableCell.jsx → .tsx All conversions: - Preserve class-based vs functional structure - Use existing TypeScript types from WdkModel and Actions - Add comprehensive prop and state interfaces - Maintain backward compatibility --- ...lector.jsx => AnswerAttributeSelector.tsx} | 12 +- .../{AnswerFilter.jsx => AnswerFilter.tsx} | 58 +++++++--- ...rSelector.jsx => AnswerFilterSelector.tsx} | 50 ++++++-- .../{AnswerTable.jsx => AnswerTable.tsx} | 107 ++++++++++++------ ...nswerTableCell.jsx => AnswerTableCell.tsx} | 29 +++-- 5 files changed, 181 insertions(+), 75 deletions(-) rename packages/libs/wdk-client/src/Views/Answer/{AnswerAttributeSelector.jsx => AnswerAttributeSelector.tsx} (80%) rename packages/libs/wdk-client/src/Views/Answer/{AnswerFilter.jsx => AnswerFilter.tsx} (73%) rename packages/libs/wdk-client/src/Views/Answer/{AnswerFilterSelector.jsx => AnswerFilterSelector.tsx} (64%) rename packages/libs/wdk-client/src/Views/Answer/{AnswerTable.jsx => AnswerTable.tsx} (74%) rename packages/libs/wdk-client/src/Views/Answer/{AnswerTableCell.jsx => AnswerTableCell.tsx} (58%) diff --git a/packages/libs/wdk-client/src/Views/Answer/AnswerAttributeSelector.jsx b/packages/libs/wdk-client/src/Views/Answer/AnswerAttributeSelector.tsx similarity index 80% rename from packages/libs/wdk-client/src/Views/Answer/AnswerAttributeSelector.jsx rename to packages/libs/wdk-client/src/Views/Answer/AnswerAttributeSelector.tsx index db44933c2b..1e7d98c65b 100644 --- a/packages/libs/wdk-client/src/Views/Answer/AnswerAttributeSelector.jsx +++ b/packages/libs/wdk-client/src/Views/Answer/AnswerAttributeSelector.tsx @@ -1,11 +1,19 @@ -import React from 'react'; +import React, { FormEvent } from 'react'; +import { AttributeField } from '../../Utils/WdkModel'; + +interface Props { + onSubmit: (event: FormEvent) => void; + allAttributes: AttributeField[]; + selectedAttributes: string[]; + onChange: (attributeName: string, isChecked: boolean) => void; +} function AttributeSelector({ onSubmit, allAttributes, selectedAttributes, onChange, -}) { +}: Props) { return (
diff --git a/packages/libs/wdk-client/src/Views/Answer/AnswerFilter.jsx b/packages/libs/wdk-client/src/Views/Answer/AnswerFilter.tsx similarity index 73% rename from packages/libs/wdk-client/src/Views/Answer/AnswerFilter.jsx rename to packages/libs/wdk-client/src/Views/Answer/AnswerFilter.tsx index 75515a7dae..00f48005d9 100644 --- a/packages/libs/wdk-client/src/Views/Answer/AnswerFilter.jsx +++ b/packages/libs/wdk-client/src/Views/Answer/AnswerFilter.tsx @@ -4,23 +4,42 @@ import ReactDOM from 'react-dom'; import { HelpTrigger } from '@veupathdb/coreui/lib/components/Mesa'; import { Tooltip } from '@veupathdb/coreui'; import { wrappable } from '../../Utils/ComponentUtils'; +import { RecordClass, Question, AttributeField, TableField } from '../../Utils/WdkModel'; +import { DisplayInfo } from '../../Actions/AnswerActions'; import AnswerFilterSelector from '../../Views/Answer/AnswerFilterSelector'; // concatenate each item in items with arr -function addToArray(arr, item) { +function addToArray(arr: string[], item: string): string[] { return arr.concat(item); } -function removeFromArray(arr, item) { +function removeFromArray(arr: string[], item: string): string[] { return arr.filter(function (a) { return a !== item; }); } -class AnswerFilter extends React.Component { - constructor(props) { +interface AnswerFilterProps { + recordClass: RecordClass; + question: Question; + filterTerm: string; + displayInfo: DisplayInfo; + onFilter: (filterTerm: string, filterAttributes: string[], filterTables: string[]) => void; +} + +interface AnswerFilterState { + showFilterFieldSelector: boolean; + filterAttributes: string[]; + filterTables: string[]; +} + +class AnswerFilter extends React.Component { + filterInputRef: React.RefObject; + + constructor(props: AnswerFilterProps) { super(props); + this.filterInputRef = React.createRef(); this.toggleFilterFieldSelector = this.toggleFilterFieldSelector.bind(this); this.handleFilter = debounce(this.handleFilter.bind(this), 300); this.toggleAttribute = this.toggleAttribute.bind(this); @@ -36,7 +55,7 @@ class AnswerFilter extends React.Component { }; } - componentDidUpdate(prevProps, prevState) { + componentDidUpdate(prevProps: AnswerFilterProps, prevState: AnswerFilterState): void { let { filterAttributes, filterTables } = this.state; if ( filterAttributes !== prevState.filterAttributes || @@ -46,19 +65,22 @@ class AnswerFilter extends React.Component { } } - toggleFilterFieldSelector() { + toggleFilterFieldSelector(): void { this.setState({ showFilterFieldSelector: !this.state.showFilterFieldSelector, }); } - handleFilter() { - let value = ReactDOM.findDOMNode(this.refs.filterInput).value; + handleFilter(): void { + const inputElement = this.filterInputRef.current; + if (!inputElement) return; + + let value = inputElement.value; let { filterAttributes, filterTables } = this.state; this.props.onFilter(value, filterAttributes, filterTables); } - toggleAttribute(e) { + toggleAttribute(e: React.ChangeEvent): void { let attr = e.target.value; let op = e.target.checked ? addToArray : removeFromArray; this.setState({ @@ -66,7 +88,7 @@ class AnswerFilter extends React.Component { }); } - toggleTable(e) { + toggleTable(e: React.ChangeEvent): void { let table = e.target.value; let op = e.target.checked ? addToArray : removeFromArray; this.setState({ @@ -74,21 +96,21 @@ class AnswerFilter extends React.Component { }); } - selectAll(e) { + selectAll(e: React.MouseEvent): void { let { attributes, tables } = this.props.recordClass; this.setState({ - filterAttributes: attributes.map((a) => a.name), - filterTables: tables.map((t) => t.name), + filterAttributes: attributes.map((a: AttributeField) => a.name), + filterTables: tables.map((t: TableField) => t.name), }); e.preventDefault(); } - clearAll(e) { + clearAll(e: React.MouseEvent): void { this.setState({ filterAttributes: [], filterTables: [] }); e.preventDefault(); } - render() { + render(): React.ReactNode { let { filterAttributes, filterTables, showFilterFieldSelector } = this.state; let { recordClass, question, filterTerm, displayInfo } = this.props; @@ -138,15 +160,15 @@ class AnswerFilter extends React.Component { ); let attributes = recordClass.attributes .concat(question.dynamicAttributes) - .filter((attr) => displayInfo.attributes.includes(attr.name)); - let tables = recordClass.tables.filter((table) => + .filter((attr: AttributeField) => displayInfo.attributes.includes(attr.name)); + let tables = recordClass.tables.filter((table: TableField) => displayInfo.tables.includes(table.name) ); return (
void; + attributes: AttributeField[]; + tables: TableField[]; + filterAttributes: string[]; + filterTables: string[]; + selectAll: (e: React.MouseEvent) => void; + clearAll: (e: React.MouseEvent) => void; + toggleAttribute: (e: React.ChangeEvent) => void; + toggleTable: (e: React.ChangeEvent) => void; +} /** Filter text input */ -function renderFilterField(field, isChecked, handleChange) { +function renderFilterField( + field: FilterField, + isChecked: boolean, + handleChange: (e: React.ChangeEvent) => void +) { return (