Jaspers Hub

jaspers / screener-mcp

pluginOfficialReviewed★ 0↓ 33

Jaspers Screener: Screens US SEC registrants by fundamentals, holders and insiders, then reads filings against written criteria with cited quotes.

2.9 KB
1import { useState, type ReactElement } from 'react'
2import { fieldLabel, groupFields, MAX_COLUMNS, type Field } from './screener'
3
4// Which fields the table shows besides the ones filtered and sorted on, which are shown whatever is
5// picked here. The list is the server's guide, group by group; what is picked goes into the panel's
6// state, and from there into the screen's arguments, since the server sends only the columns asked for.
7
8interface Props {
9 columns: string[]
10 fields: Field[]
11 fieldsError: string | null
12 onApply: (columns: string[]) => void
13 onClose: () => void
14}
15
16export function ColumnsDialog({ columns, fields, fieldsError, onApply, onClose }: Props): ReactElement {
17 const [picked, setPicked] = useState<string[]>(columns)
18 // The identity columns are the company cell; the table draws them whatever is asked for.
19 const groups = groupFields(fields.filter((field) => !['cik', 'name', 'sic_description'].includes(field.field)))
20
21 function toggle(name: string, on: boolean): void {
22 setPicked((previous) => (on ? [...previous, name] : previous.filter((item) => item !== name)))
23 }
24
25 return (
26 <div className="sc-dialog" onMouseDown={onClose}>
27 <div className="sc-dialog-panel" onMouseDown={(event) => event.stopPropagation()}>
28 <div className="sc-dialog-head">
29 <span>Columns</span>
30 <button type="button" className="sc-btn" style={{ marginLeft: 'auto' }} onClick={onClose} aria-label="Close">
31 ×
32 </button>
33 </div>
34
35 <div className="sc-dialog-body">
36 {fields.length === 0 && <p className="sc-error">{fieldsError ?? 'The server sent no list of fields.'}</p>}
37 {groups.map(([group, members]) => (
38 <div key={group} className="sc-section">
39 <span className="sc-label">{group}</span>
40 <div className="sc-columns">
41 {members.map((field) => (
42 <label key={field.field} className="sc-check" title={field.description}>
43 <input type="checkbox" checked={picked.includes(field.field)} onChange={(event) => toggle(field.field, event.target.checked)} />
44 <span>{fieldLabel(field.field)}</span>
45 </label>
46 ))}
47 </div>
48 </div>
49 ))}
50 </div>
51
52 <div className="sc-dialog-foot">
53 <span className="sc-status" style={{ marginRight: 'auto' }}>
54 {picked.length} picked{picked.length > MAX_COLUMNS ? `, ${MAX_COLUMNS} at most` : ''}
55 </span>
56 <button type="button" className="sc-btn" onClick={() => setPicked([])}>
57 None
58 </button>
59 <button type="button" className="sc-btn" onClick={onClose}>
60 Cancel
61 </button>
62 <button type="button" className="sc-btn" disabled={picked.length > MAX_COLUMNS} onClick={() => onApply(picked)}>
63 Apply
64 </button>
65 </div>
66 </div>
67 </div>
68 )
69}