From 05a860614cba5a68e5da7b3a3e3e81e9497bb2d5 Mon Sep 17 00:00:00 2001 From: Julian Dominguez-Schatz Date: Sat, 7 Sep 2024 18:13:42 -0400 Subject: [PATCH 1/3] Supporting types --- .../src/components/common/Select.tsx | 3 ++ .../desktop-client/src/components/forms.tsx | 6 ++-- .../src/components/select/DateSelect.tsx | 3 ++ .../loot-core/src/types/models/schedule.d.ts | 32 +++++++++++-------- 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/packages/desktop-client/src/components/common/Select.tsx b/packages/desktop-client/src/components/common/Select.tsx index c2fae346eb0..09db1321683 100644 --- a/packages/desktop-client/src/components/common/Select.tsx +++ b/packages/desktop-client/src/components/common/Select.tsx @@ -17,6 +17,7 @@ function isValueOption( export type SelectOption = [Value, string] | typeof Menu.line; type SelectProps = { + id?: string; bare?: boolean; options: Array; value: Value; @@ -41,6 +42,7 @@ type SelectProps = { * // Date: Sat, 7 Sep 2024 18:13:31 -0400 Subject: [PATCH 2/3] Move `RecurringSchedulePicker.jsx` -> `RecurringSchedulePicker.tsx` --- ...Picker.jsx => RecurringSchedulePicker.tsx} | 151 ++++++++++++++---- 1 file changed, 121 insertions(+), 30 deletions(-) rename packages/desktop-client/src/components/select/{RecurringSchedulePicker.jsx => RecurringSchedulePicker.tsx} (79%) diff --git a/packages/desktop-client/src/components/select/RecurringSchedulePicker.jsx b/packages/desktop-client/src/components/select/RecurringSchedulePicker.tsx similarity index 79% rename from packages/desktop-client/src/components/select/RecurringSchedulePicker.jsx rename to packages/desktop-client/src/components/select/RecurringSchedulePicker.tsx index 036b875c887..eb7c282289a 100644 --- a/packages/desktop-client/src/components/select/RecurringSchedulePicker.jsx +++ b/packages/desktop-client/src/components/select/RecurringSchedulePicker.tsx @@ -1,8 +1,17 @@ -import React, { useEffect, useReducer, useRef, useState } from 'react'; +import { + type CSSProperties, + type Dispatch, + useEffect, + useReducer, + useRef, + useState, +} from 'react'; import { sendCatch } from 'loot-core/src/platform/client/fetch'; import * as monthUtils from 'loot-core/src/shared/months'; import { getRecurringDescription } from 'loot-core/src/shared/schedules'; +import { type RecurConfig, type RecurPattern } from 'loot-core/types/models'; +import { type WithRequired } from 'loot-core/types/util'; import { useDateFormat } from '../../hooks/useDateFormat'; import { SvgAdd, SvgSubtract } from '../../icons/v0'; @@ -28,7 +37,7 @@ const FREQUENCY_OPTIONS = [ { id: 'weekly', name: 'Weeks' }, { id: 'monthly', name: 'Months' }, { id: 'yearly', name: 'Years' }, -]; +] as const; const DAY_OF_MONTH_OPTIONS = [...Array(31).keys()].map(day => day + 1); @@ -40,16 +49,16 @@ const DAY_OF_WEEK_OPTIONS = [ { id: 'TH', name: 'Thursday' }, { id: 'FR', name: 'Friday' }, { id: 'SA', name: 'Saturday' }, -]; +] as const; -function parsePatternValue(value) { +function parsePatternValue(value: string | number) { if (value === 'last') { return -1; } return Number(value); } -function parseConfig(config) { +function parseConfig(config: Partial): StateConfig { return { start: monthUtils.currentDay(), interval: 1, @@ -58,13 +67,13 @@ function parseConfig(config) { skipWeekend: false, weekendSolveMode: 'before', endMode: 'never', - endOccurrences: '1', + endOccurrences: 1, endDate: monthUtils.currentDay(), ...config, }; } -function unparseConfig(parsed) { +function unparseConfig(parsed: StateConfig): RecurConfig { return { ...parsed, interval: validInterval(parsed.interval), @@ -72,14 +81,23 @@ function unparseConfig(parsed) { }; } -function createMonthlyRecurrence(startDate) { +function createMonthlyRecurrence(startDate: string) { return { value: parseInt(monthUtils.format(startDate, 'd')), - type: 'day', + type: 'day' as const, }; } -function boundedRecurrence({ field, value, recurrence }) { +function boundedRecurrence({ + field, + value, + recurrence, +}: { + recurrence: RecurPattern; +} & ( + | { field: 'type'; value: RecurPattern['type'] } + | { field: 'value'; value: RecurPattern['value'] } +)) { if ( (field === 'value' && recurrence.type !== 'day' && @@ -93,7 +111,48 @@ function boundedRecurrence({ field, value, recurrence }) { return { [field]: value }; } -function reducer(state, action) { +type StateConfig = Omit< + WithRequired, + 'interval' | 'endOccurrences' +> & { + interval: number | string; + endOccurrences: number | string; +}; + +type ReducerState = { + config: StateConfig; +}; + +type UpdateRecurrenceAction = + | { + type: 'update-recurrence'; + recurrence: RecurPattern; + field: 'type'; + value: RecurPattern['type']; + } + | { + type: 'update-recurrence'; + recurrence: RecurPattern; + field: 'value'; + value: RecurPattern['value']; + }; + +type ChangeFieldAction = { + type: 'change-field'; + field: T; + value: StateConfig[T]; +}; + +type ReducerAction = + | { type: 'replace-config'; config: StateConfig } + | ChangeFieldAction + | UpdateRecurrenceAction + | { type: 'add-recurrence' } + | { type: 'remove-recurrence'; recurrence: RecurPattern } + | { type: 'set-skip-weekend'; skipWeekend: boolean } + | { type: 'set-weekend-solve'; value: StateConfig['weekendSolveMode'] }; + +function reducer(state: ReducerState, action: ReducerAction): ReducerState { switch (action.type) { case 'replace-config': return { ...state, config: action.config }; @@ -159,7 +218,7 @@ function reducer(state, action) { } } -function SchedulePreview({ previewDates }) { +function SchedulePreview({ previewDates }: { previewDates: Date[] }) { const dateFormat = (useDateFormat() || 'MM/dd/yyyy') .replace('MM', 'M') .replace('dd', 'd'); @@ -198,15 +257,18 @@ function SchedulePreview({ previewDates }) { ); } -function validInterval(interval) { - const intInterval = parseInt(interval); +function validInterval(interval: string | number) { + const intInterval = Number(interval); return Number.isInteger(intInterval) && intInterval > 0 ? intInterval : 1; } -function MonthlyPatterns({ config, dispatch }) { - const updateRecurrence = (recurrence, field, value) => - dispatch({ type: 'update-recurrence', recurrence, field, value }); - +function MonthlyPatterns({ + config, + dispatch, +}: { + config: StateConfig; + dispatch: Dispatch; +}) { return ( {config.patterns.map((recurrence, idx) => ( @@ -221,24 +283,34 @@ function MonthlyPatterns({ config, dispatch }) { options={[ [-1, 'Last'], Menu.line, - ...DAY_OF_MONTH_OPTIONS.map(opt => [opt, opt]), + ...DAY_OF_MONTH_OPTIONS.map(opt => [opt, String(opt)] as const), ]} value={recurrence.value} onChange={value => - updateRecurrence(recurrence, 'value', parsePatternValue(value)) + dispatch({ + type: 'update-recurrence', + recurrence, + field: 'value', + value: parsePatternValue(value), + }) } - disabledKeys={['-']} buttonStyle={{ flex: 1, marginRight: 10 }} />