diff --git a/packages/desktop-client/package.json b/packages/desktop-client/package.json index 5fc2b635c4..bfc80f7a87 100644 --- a/packages/desktop-client/package.json +++ b/packages/desktop-client/package.json @@ -39,6 +39,7 @@ "@testing-library/react": "16.3.0", "@testing-library/user-event": "14.6.1", "@types/lodash": "^4", + "@types/pikaday": "^1.7.10", "@types/promise-retry": "^1.1.6", "@types/react": "^19.2.5", "@types/react-dom": "^19.2.3", diff --git a/packages/desktop-client/src/components/select/DateSelect.tsx b/packages/desktop-client/src/components/select/DateSelect.tsx index 449ac3d4d4..7120b05ed7 100644 --- a/packages/desktop-client/src/components/select/DateSelect.tsx +++ b/packages/desktop-client/src/components/select/DateSelect.tsx @@ -1,5 +1,4 @@ -// @ts-strict-ignore -import React, { +import { forwardRef, useEffect, useEffectEvent, @@ -9,7 +8,13 @@ import React, { useRef, useState, } from 'react'; -import type { ComponentProps, KeyboardEvent, Ref } from 'react'; +import type { + ChangeEvent, + ComponentProps, + JSX, + KeyboardEvent, + Ref, +} from 'react'; import { useResponsive } from '@actual-app/components/hooks/useResponsive'; import { Input } from '@actual-app/components/input'; @@ -122,8 +127,8 @@ type DatePickerProps = { value: string; firstDayOfWeekIdx: string; dateFormat: string; - onUpdate?: (selectedDate: Date) => void; - onSelect: (selectedDate: Date | null) => void; + onUpdate: (selectedDate: Date) => void; + onSelect: (selectedDate: Date) => void; }; type DatePickerForwardedRef = { @@ -132,8 +137,8 @@ type DatePickerForwardedRef = { const DatePicker = forwardRef( ({ value, firstDayOfWeekIdx, dateFormat, onUpdate, onSelect }, ref) => { const locale = useLocale(); - const picker = useRef(null); - const mountPoint = useRef(null); + const picker = useRef(null); + const mountPoint = useRef(null); const onUpdateEffect = useEffectEvent(onUpdate); @@ -141,29 +146,32 @@ const DatePicker = forwardRef( ref, () => ({ handleInputKeyDown(e) { + const currentDate = picker.current?.getDate(); + if (!currentDate) return; + let newDate = null; switch (e.key) { case 'ArrowLeft': e.preventDefault(); - newDate = subDays(picker.current.getDate(), 1); + newDate = subDays(currentDate, 1); break; case 'ArrowUp': e.preventDefault(); - newDate = subDays(picker.current.getDate(), 7); + newDate = subDays(currentDate, 7); break; case 'ArrowRight': e.preventDefault(); - newDate = addDays(picker.current.getDate(), 1); + newDate = addDays(currentDate, 1); break; case 'ArrowDown': e.preventDefault(); - newDate = addDays(picker.current.getDate(), 7); + newDate = addDays(currentDate, 7); break; default: } if (newDate) { - picker.current.setDate(newDate, true); + picker.current?.setDate(newDate, true); onUpdateEffect?.(newDate); } }, @@ -194,16 +202,16 @@ const DatePicker = forwardRef( useLayoutEffect(() => { picker.current = initPikaday(); - mountPoint.current.appendChild(picker.current.el); + mountPoint.current?.appendChild(picker.current.el); return () => { - picker.current.destroy(); + picker.current?.destroy(); }; }, []); useEffect(() => { - if (value && picker.current.getDate() !== value) { - picker.current.setDate(parse(value, dateFormat, new Date()), true); + if (value) { + picker.current?.setDate(parse(value, dateFormat, new Date()), true); } }, [value, dateFormat]); @@ -218,7 +226,7 @@ const DatePicker = forwardRef( DatePicker.displayName = 'DatePicker'; -function defaultShouldSaveFromKey(e) { +function defaultShouldSaveFromKey(e: KeyboardEvent) { return e.key === 'Enter'; } @@ -263,7 +271,7 @@ function DateSelectDesktop({ return ''; }, [defaultValue, dateFormat]); - const picker = useRef(null); + const picker = useRef(null); const [value, setValue] = useState(parsedDefaultValue); const [open, setOpen] = useState(embedded || isOpen || false); const innerRef = useRef(null); @@ -315,7 +323,7 @@ function DateSelectDesktop({ !e.altKey && open ) { - picker.current.handleInputKeyDown(e); + picker.current?.handleInputKeyDown(e); } else if (e.key === 'Escape') { setValue(parsedDefaultValue); setSelectedValue(parsedDefaultValue); @@ -357,11 +365,11 @@ function DateSelectDesktop({ } } - function onChange(e) { + function onChange(e: ChangeEvent) { setValue(e.target.value); } - const maybeWrapTooltip = content => { + const maybeWrapTooltip = (content: JSX.Element) => { if (embedded) { return open ? content : null; } @@ -413,8 +421,8 @@ function DateSelectDesktop({ // Otherwise the input is reset to whatever is already // selected if (value === '') { - setSelectedValue(null); - onSelect(null); + setSelectedValue(''); + onSelect(''); } else { setValue(selectedValue || ''); diff --git a/upcoming-release-notes/6905.md b/upcoming-release-notes/6905.md new file mode 100644 index 0000000000..01ce3ff6f0 --- /dev/null +++ b/upcoming-release-notes/6905.md @@ -0,0 +1,6 @@ +--- +category: Bugfixes +authors: [rznn7] +--- + +Fix crash when setting a rule date field and leaving it empty diff --git a/yarn.lock b/yarn.lock index c740fbc2a2..1ec4be62a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -161,6 +161,7 @@ __metadata: "@testing-library/react": "npm:16.3.0" "@testing-library/user-event": "npm:14.6.1" "@types/lodash": "npm:^4" + "@types/pikaday": "npm:^1.7.10" "@types/promise-retry": "npm:^1.1.6" "@types/react": "npm:^19.2.5" "@types/react-dom": "npm:^19.2.3" @@ -9431,6 +9432,15 @@ __metadata: languageName: node linkType: hard +"@types/pikaday@npm:^1.7.10": + version: 1.7.10 + resolution: "@types/pikaday@npm:1.7.10" + dependencies: + moment: "npm:>=2.29.2" + checksum: 10/638bc31725f4663c6266bb527d34d08740d8dc98c1c71efe53ef5bc63e177718e960f96f2719ee97f18ec7caf15e9f70d225e07f4d9ae1235384bb6da31a34a7 + languageName: node + linkType: hard + "@types/plist@npm:^3.0.1": version: 3.0.5 resolution: "@types/plist@npm:3.0.5" @@ -20768,6 +20778,13 @@ __metadata: languageName: node linkType: hard +"moment@npm:>=2.29.2": + version: 2.30.1 + resolution: "moment@npm:2.30.1" + checksum: 10/ae42d876d4ec831ef66110bdc302c0657c664991e45cf2afffc4b0f6cd6d251dde11375c982a5c0564ccc0fa593fc564576ddceb8c8845e87c15f58aa6baca69 + languageName: node + linkType: hard + "mrmime@npm:^2.0.0": version: 2.0.1 resolution: "mrmime@npm:2.0.1"