diff --git a/packages/desktop-client/globals.d.ts b/packages/desktop-client/globals.d.ts new file mode 100644 index 00000000000..ce1ae7b7547 --- /dev/null +++ b/packages/desktop-client/globals.d.ts @@ -0,0 +1,2 @@ +// Allow images to be imported +declare module '*.png'; diff --git a/packages/desktop-client/src/components/common/Input.tsx b/packages/desktop-client/src/components/common/Input.tsx index c02b9bd86c0..d5284251426 100644 --- a/packages/desktop-client/src/components/common/Input.tsx +++ b/packages/desktop-client/src/components/common/Input.tsx @@ -21,7 +21,7 @@ export const defaultInputStyle = { border: '1px solid ' + theme.formInputBorder, }; -type InputProps = InputHTMLAttributes & { +export type InputProps = InputHTMLAttributes & { style?: CSSProperties; inputRef?: Ref; onEnter?: (event: KeyboardEvent) => void; diff --git a/packages/desktop-client/src/components/common/View.tsx b/packages/desktop-client/src/components/common/View.tsx index 5212bb7758d..3b84c962c78 100644 --- a/packages/desktop-client/src/components/common/View.tsx +++ b/packages/desktop-client/src/components/common/View.tsx @@ -8,7 +8,7 @@ import { css } from 'glamor'; import { type CSSProperties } from '../../style'; -type ViewProps = HTMLProps & { +export type ViewProps = HTMLProps & { className?: string; style?: CSSProperties; nativeStyle?: StyleHTMLAttributes; diff --git a/packages/desktop-client/src/components/select/DateSelect.js b/packages/desktop-client/src/components/select/DateSelect.tsx similarity index 74% rename from packages/desktop-client/src/components/select/DateSelect.js rename to packages/desktop-client/src/components/select/DateSelect.tsx index 4d372e79569..e3ab976d505 100644 --- a/packages/desktop-client/src/components/select/DateSelect.js +++ b/packages/desktop-client/src/components/select/DateSelect.tsx @@ -6,10 +6,12 @@ import React, { useLayoutEffect, useImperativeHandle, useMemo, + type MutableRefObject, + type KeyboardEvent, } from 'react'; import { useSelector } from 'react-redux'; -import * as d from 'date-fns'; +import { parse, parseISO, format, subDays, addDays, isValid } from 'date-fns'; import Pikaday from 'pikaday'; import 'pikaday/css/pikaday.css'; @@ -23,9 +25,9 @@ import { } from 'loot-core/src/shared/months'; import { stringToInteger } from 'loot-core/src/shared/util'; -import { theme } from '../../style'; -import Input from '../common/Input'; -import View from '../common/View'; +import { type CSSProperties, theme } from '../../style'; +import Input, { type InputProps } from '../common/Input'; +import View, { type ViewProps } from '../common/View'; import { Tooltip } from '../tooltips'; import DateSelectLeft from './DateSelect.left.png'; @@ -76,7 +78,18 @@ let pickerStyles = { }, }; -let DatePicker = forwardRef( +type DatePickerProps = { + value: string; + firstDayOfWeekIdx: string; + dateFormat: string; + onUpdate?: (selectedDate: Date) => void; + onSelect: (selectedDate: Date | null) => void; +}; + +type DatePickerForwardedRef = { + handleInputKeyDown: (e: KeyboardEvent) => void; +}; +let DatePicker = forwardRef( ({ value, firstDayOfWeekIdx, dateFormat, onUpdate, onSelect }, ref) => { let picker = useRef(null); let mountPoint = useRef(null); @@ -89,22 +102,23 @@ let DatePicker = forwardRef( switch (e.key) { case 'ArrowLeft': e.preventDefault(); - newDate = d.subDays(picker.current.getDate(), 1); + newDate = subDays(picker.current.getDate(), 1); break; case 'ArrowUp': e.preventDefault(); - newDate = d.subDays(picker.current.getDate(), 7); + newDate = subDays(picker.current.getDate(), 7); break; case 'ArrowRight': e.preventDefault(); - newDate = d.addDays(picker.current.getDate(), 1); + newDate = addDays(picker.current.getDate(), 1); break; case 'ArrowDown': e.preventDefault(); - newDate = d.addDays(picker.current.getDate(), 7); + newDate = addDays(picker.current.getDate(), 7); break; default: } + if (newDate) { picker.current.setDate(newDate, true); onUpdate?.(newDate); @@ -120,14 +134,14 @@ let DatePicker = forwardRef( keyboardInput: false, firstDay: stringToInteger(firstDayOfWeekIdx), defaultDate: value - ? d.parse(value, dateFormat, currentDate()) + ? parse(value, dateFormat, currentDate()) : currentDate(), setDefaultDate: true, toString(date) { - return d.format(date, dateFormat); + return format(date, dateFormat); }, parse(dateString) { - return d.parse(dateString, dateFormat, new Date()); + return parse(dateString, dateFormat, new Date()); }, onSelect, }); @@ -141,7 +155,7 @@ let DatePicker = forwardRef( useEffect(() => { if (picker.current.getDate() !== value) { - picker.current.setDate(d.parse(value, dateFormat, new Date()), true); + picker.current.setDate(parse(value, dateFormat, new Date()), true); } }, [value, dateFormat]); @@ -153,6 +167,23 @@ function defaultShouldSaveFromKey(e) { return e.key === 'Enter'; } +type DateSelectProps = { + containerProps?: ViewProps; + inputProps?: InputProps; + tooltipStyle?: CSSProperties; + value: string; + isOpen?: boolean; + embedded?: boolean; + dateFormat: string; + focused?: boolean; + openOnFocus?: boolean; + inputRef?: MutableRefObject; + shouldSaveFromKey?: (e: KeyboardEvent) => boolean; + tableBehavior?: boolean; + onUpdate?: (selectedDate: string) => void; + onSelect: (selectedDate: string) => void; +}; + export default function DateSelect({ containerProps, inputProps, @@ -168,12 +199,12 @@ export default function DateSelect({ tableBehavior, onUpdate, onSelect, -}) { +}: DateSelectProps) { let parsedDefaultValue = useMemo(() => { if (defaultValue) { - let date = d.parseISO(defaultValue); - if (d.isValid(date)) { - return d.format(date, dateFormat); + let date = parseISO(defaultValue); + if (isValid(date)) { + return format(date, dateFormat); } } return ''; @@ -216,29 +247,29 @@ export default function DateSelect({ // Support only entering the month and day (4/5). This is complex // because of the various date formats - we need to derive // the right day/month format from it - let test = d.parse(value, getDayMonthFormat(dateFormat), new Date()); - if (d.isValid(test)) { - onUpdate?.(d.format(test, 'yyyy-MM-dd')); - setSelectedValue(d.format(test, dateFormat)); + let test = parse(value, getDayMonthFormat(dateFormat), new Date()); + if (isValid(test)) { + onUpdate?.(format(test, 'yyyy-MM-dd')); + setSelectedValue(format(test, dateFormat)); } } else if (getShortYearRegex(dateFormat).test(value)) { // Support entering the year as only two digits (4/5/19) - let test = d.parse(value, getShortYearFormat(dateFormat), new Date()); - if (d.isValid(test)) { - onUpdate?.(d.format(test, 'yyyy-MM-dd')); - setSelectedValue(d.format(test, dateFormat)); + let test = parse(value, getShortYearFormat(dateFormat), new Date()); + if (isValid(test)) { + onUpdate?.(format(test, 'yyyy-MM-dd')); + setSelectedValue(format(test, dateFormat)); } } else { - let test = d.parse(value, dateFormat, new Date()); - if (d.isValid(test)) { - let date = d.format(test, 'yyyy-MM-dd'); + let test = parse(value, dateFormat, new Date()); + if (isValid(test)) { + let date = format(test, 'yyyy-MM-dd'); onUpdate?.(date); setSelectedValue(value); } } }, [value]); - function onKeyDown(e) { + function onKeyDown(e: KeyboardEvent) { if ( ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(e.key) && !e.shiftKey && @@ -267,8 +298,8 @@ export default function DateSelect({ setValue(selectedValue); setOpen(false); - let date = d.parse(selectedValue, dateFormat, new Date()); - onSelect(d.format(date, 'yyyy-MM-dd')); + let date = parse(selectedValue, dateFormat, new Date()); + onSelect(format(date, 'yyyy-MM-dd')); if (open && e.key === 'Enter') { // This stops the event from propagating up @@ -341,9 +372,9 @@ export default function DateSelect({ } else { setValue(selectedValue || ''); - let date = d.parse(selectedValue, dateFormat, new Date()); - if (date instanceof Date && !isNaN(date)) { - onSelect(d.format(date, 'yyyy-MM-dd')); + let date = parse(selectedValue, dateFormat, new Date()); + if (date instanceof Date && !isNaN(date.valueOf())) { + onSelect(format(date, 'yyyy-MM-dd')); } } } @@ -357,12 +388,12 @@ export default function DateSelect({ firstDayOfWeekIdx={firstDayOfWeekIdx} dateFormat={dateFormat} onUpdate={date => { - setSelectedValue(d.format(date, dateFormat)); - onUpdate?.(d.format(date, 'yyyy-MM-dd')); + setSelectedValue(format(date, dateFormat)); + onUpdate?.(format(date, 'yyyy-MM-dd')); }} onSelect={date => { - setValue(d.format(date, dateFormat)); - onSelect(d.format(date, 'yyyy-MM-dd')); + setValue(format(date, dateFormat)); + onSelect(format(date, 'yyyy-MM-dd')); setOpen(false); }} />, diff --git a/packages/loot-core/src/types/server-handlers.d.ts b/packages/loot-core/src/types/server-handlers.d.ts index ceaa91df30a..e7c3e7ba0ca 100644 --- a/packages/loot-core/src/types/server-handlers.d.ts +++ b/packages/loot-core/src/types/server-handlers.d.ts @@ -42,10 +42,24 @@ export interface ServerHandlers { 'transactions-export-query': (arg: { query: queryState }) => Promise; - // incomplete 'get-categories': () => Promise<{ - grouped: { id: string }[]; - list: { id: string }[]; + grouped: { + id: string; + name: string; + is_income: number; + sort_order: number; + tombstone: number; + hidden: boolean; + }[]; + list: { + id: string; + name: string; + is_income: number; + cat_group: string; + sort_order: number; + tombstone: number; + hidden: boolean; + }[]; }>; 'get-earliest-transaction': () => Promise; diff --git a/upcoming-release-notes/1776.md b/upcoming-release-notes/1776.md new file mode 100644 index 00000000000..d4c2cd9e547 --- /dev/null +++ b/upcoming-release-notes/1776.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MikesGlitch] +--- + +Convert DateSelect component to TypeScript and update category query type.