// @ts-strict-ignore import React, { useState } from 'react'; import { runQuery } from 'loot-core/src/client/query-helpers'; import { send } from 'loot-core/src/platform/client/fetch'; import { q } from 'loot-core/src/shared/query'; import { getRecurringDescription } from 'loot-core/src/shared/schedules'; import type { DiscoverScheduleEntity } from 'loot-core/src/types/models'; import type { BoundActions } from '../../hooks/useActions'; import { useDateFormat } from '../../hooks/useDateFormat'; import { useSelected, useSelectedDispatch, useSelectedItems, SelectedProvider, } from '../../hooks/useSelected'; import { useSendPlatformRequest } from '../../hooks/useSendPlatformRequest'; import { theme } from '../../style'; import { ButtonWithLoading } from '../common/Button'; import { Modal } from '../common/Modal'; import { Paragraph } from '../common/Paragraph'; import { Stack } from '../common/Stack'; import { View } from '../common/View'; import { type CommonModalProps } from '../Modals'; import { Table, TableHeader, Row, Field, SelectCell } from '../table'; import { DisplayId } from '../util/DisplayId'; import { ScheduleAmountCell } from './SchedulesTable'; const ROW_HEIGHT = 43; function DiscoverSchedulesTable({ schedules, loading, }: { schedules: DiscoverScheduleEntity[]; loading: boolean; }) { const selectedItems = useSelectedItems(); const dispatchSelected = useSelectedDispatch(); const dateFormat = useDateFormat() || 'MM/dd/yyyy'; function renderItem({ item }: { item: DiscoverScheduleEntity }) { const selected = selectedItems.has(item.id); const amountOp = item._conditions.find(c => c.field === 'amount').op; const recurDescription = getRecurringDescription(item.date, dateFormat); return ( <Row height={ROW_HEIGHT} inset={15} onClick={e => { dispatchSelected({ type: 'select', id: item.id, event: e }); }} style={{ borderColor: selected ? theme.tableBorderSelected : theme.tableBorder, cursor: 'pointer', color: selected ? theme.tableRowBackgroundHighlightText : theme.tableText, backgroundColor: selected ? theme.tableRowBackgroundHighlight : theme.tableBackground, ':hover': { backgroundColor: theme.tableRowBackgroundHover, color: theme.tableText, }, }} > <SelectCell exposed={true} focused={false} selected={selected} onSelect={e => { dispatchSelected({ type: 'select', id: item.id, event: e }); }} /> <Field width="flex"> <DisplayId type="payees" id={item.payee} /> </Field> <Field width="flex"> <DisplayId type="accounts" id={item.account} /> </Field> <Field width="auto" title={recurDescription} style={{ flex: 1.5 }}> {recurDescription} </Field> <ScheduleAmountCell amount={item.amount} op={amountOp} /> </Row> ); } return ( <View style={{ flex: 1 }}> <TableHeader height={ROW_HEIGHT} inset={15}> <SelectCell exposed={!loading} focused={false} selected={selectedItems.size > 0} onSelect={e => dispatchSelected({ type: 'select-all', event: e })} /> <Field width="flex">Payee</Field> <Field width="flex">Account</Field> <Field width="auto" style={{ flex: 1.5 }}> When </Field> <Field width={100} style={{ textAlign: 'right' }}> Amount </Field> </TableHeader> <Table rowHeight={ROW_HEIGHT} style={{ flex: 1, backgroundColor: 'transparent', }} items={schedules} loading={loading} isSelected={id => selectedItems.has(id)} renderItem={renderItem} renderEmpty="No schedules found" /> </View> ); } export function DiscoverSchedules({ modalProps, actions, }: { modalProps: CommonModalProps; actions: BoundActions; }) { const { data, isLoading } = useSendPlatformRequest('schedule/discover'); const schedules = data || []; const [creating, setCreating] = useState(false); const selectedInst = useSelected<DiscoverScheduleEntity>( 'discover-schedules', schedules, [], ); async function onCreate() { const selected = schedules.filter(s => selectedInst.items.has(s.id)); setCreating(true); for (const schedule of selected) { const scheduleId = await send('schedule/create', { conditions: schedule._conditions, schedule: {}, }); // Now query for matching transactions and link them automatically const { filters } = await send('make-filters-from-conditions', { conditions: schedule._conditions, }); if (filters.length > 0) { const { data: transactions } = await runQuery( q('transactions').filter({ $and: filters }).select('id'), ); await send('transactions-batch-update', { updated: transactions.map(t => ({ id: t.id, schedule: scheduleId, })), }); } } setCreating(false); actions.popModal(); } return ( <Modal title="Found schedules" size={{ width: 850, height: 650 }} {...modalProps} > <Paragraph> We found some possible schedules in your current transactions. Select the ones you want to create. </Paragraph> <Paragraph> If you expected a schedule here and don’t see it, it might be because the payees of the transactions don’t match. Make sure you rename payees on all transactions for a schedule to be the same payee. </Paragraph> <SelectedProvider instance={selectedInst}> <DiscoverSchedulesTable loading={isLoading} schedules={schedules} /> </SelectedProvider> <Stack direction="row" align="center" justify="flex-end" style={{ paddingTop: 20, paddingBottom: 0, }} > <ButtonWithLoading type="primary" loading={creating} disabled={selectedInst.items.size === 0} onClick={onCreate} > Create schedules </ButtonWithLoading> </Stack> </Modal> ); }