Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Maintenance: DateSelect component to tsx & server-handlers get-categories type #1776

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/desktop-client/globals.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Allow images to be imported
declare module '*.png';
Copy link
Contributor Author

@MikesGlitch MikesGlitch Oct 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needed to allow us to import png's - typescript needs to know it's allowed. Used in DateSelect where we're importing the back and forward arrow images.

image

2 changes: 1 addition & 1 deletion packages/desktop-client/src/components/common/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const defaultInputStyle = {
border: '1px solid ' + theme.formInputBorder,
};

type InputProps = InputHTMLAttributes<HTMLInputElement> & {
export type InputProps = InputHTMLAttributes<HTMLInputElement> & {
style?: CSSProperties;
inputRef?: Ref<HTMLInputElement>;
onEnter?: (event: KeyboardEvent<HTMLInputElement>) => void;
Expand Down
2 changes: 1 addition & 1 deletion packages/desktop-client/src/components/common/View.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { css } from 'glamor';

import { type CSSProperties } from '../../style';

type ViewProps = HTMLProps<HTMLDivElement> & {
export type ViewProps = HTMLProps<HTMLDivElement> & {
className?: string;
style?: CSSProperties;
nativeStyle?: StyleHTMLAttributes<HTMLDivElement>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Copy link
Contributor Author

@MikesGlitch MikesGlitch Oct 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reducing import cost from
image
To
image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing

import Pikaday from 'pikaday';

import 'pikaday/css/pikaday.css';
Expand All @@ -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';
Expand Down Expand Up @@ -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<HTMLInputElement>) => void;
};
let DatePicker = forwardRef<DatePickerForwardedRef, DatePickerProps>(
({ value, firstDayOfWeekIdx, dateFormat, onUpdate, onSelect }, ref) => {
let picker = useRef(null);
let mountPoint = useRef(null);
Expand All @@ -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);
Expand All @@ -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,
});
Expand All @@ -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]);

Expand All @@ -153,6 +167,23 @@ function defaultShouldSaveFromKey(e) {
return e.key === 'Enter';
}

type DateSelectProps = {
containerProps?: ViewProps;
inputProps?: InputProps;
Comment on lines +171 to +172
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🥜 nitpick: ‏(for future reference) you can also do this:

Suggested change
containerProps?: ViewProps;
inputProps?: InputProps;
containerProps?: React.ComponentProps<typeof View>;
inputProps?: React.ComponentProps<typeof Input>;

tooltipStyle?: CSSProperties;
value: string;
isOpen?: boolean;
embedded?: boolean;
dateFormat: string;
focused?: boolean;
openOnFocus?: boolean;
inputRef?: MutableRefObject<HTMLInputElement>;
shouldSaveFromKey?: (e: KeyboardEvent<HTMLInputElement>) => boolean;
tableBehavior?: boolean;
onUpdate?: (selectedDate: string) => void;
onSelect: (selectedDate: string) => void;
};

export default function DateSelect({
containerProps,
inputProps,
Expand All @@ -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 '';
Expand Down Expand Up @@ -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<HTMLInputElement>) {
if (
['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(e.key) &&
!e.shiftKey &&
Expand Down Expand Up @@ -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
Expand Down Expand 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())) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typescript was complaining about isNaN(date) because date is typed as a Date (previous check ensures that) - isNaN only accepts a number.

under the hood isNaN would convert the date object to a timestamp number - to satisfy typescript we now have to call that manually.

onSelect(format(date, 'yyyy-MM-dd'));
}
}
}
Expand All @@ -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);
}}
/>,
Expand Down
20 changes: 17 additions & 3 deletions packages/loot-core/src/types/server-handlers.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,24 @@ export interface ServerHandlers {

'transactions-export-query': (arg: { query: queryState }) => Promise<unknown>;

// 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<unknown>;
Expand Down
6 changes: 6 additions & 0 deletions upcoming-release-notes/1776.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [MikesGlitch]
---

Convert DateSelect component to TypeScript and update category query type.