From 20a73d3c809e79e8f5a8d342fc261fd387710500 Mon Sep 17 00:00:00 2001 From: Yong Sheng Tan Date: Sat, 13 Apr 2024 18:40:26 +0800 Subject: [PATCH] Improved CPF code support and tx ux --- src/core/api.jsx | 1 - src/settings/cpf-slider.jsx | 2 +- src/transactions/add-transaction-dialog.jsx | 36 +++++++++++++------ src/transactions/auto-fill.jsx | 2 +- src/transactions/transactions-grid.jsx | 9 ++++- src/util/cpf-codes.js | 39 +++++++++++++++++++++ 6 files changed, 74 insertions(+), 15 deletions(-) create mode 100644 src/util/cpf-codes.js diff --git a/src/core/api.jsx b/src/core/api.jsx index 4ce3dc2..00722ea 100644 --- a/src/core/api.jsx +++ b/src/core/api.jsx @@ -88,7 +88,6 @@ const api = () => { bulkEditTransactions: (payload, callback) => apiCall(PUT, 'transaction/bulk', callback, payload), deleteTransaction: (id, callback) => apiCall(DELETE, `transaction/${id}`, callback), suggestRemarks: (input, callback) => apiCall(GET, `suggest/remarks?q=${clean(input)}`, callback), - suggestCode: (input, callback) => apiCall(GET, `suggest/code?q=${clean(input)}`, callback), suggestCompany: (input, callback) => apiCall(GET, `suggest/company?q=${clean(input)}`, callback), getCategories: (callback) => apiCall(GET, 'suggest/categories', callback), listTemplates: (callback) => apiCall(GET, 'template', callback), diff --git a/src/settings/cpf-slider.jsx b/src/settings/cpf-slider.jsx index 5075dd6..d4c99ef 100644 --- a/src/settings/cpf-slider.jsx +++ b/src/settings/cpf-slider.jsx @@ -8,7 +8,7 @@ const CpfSlider = ({ cpfRatio, setCpfRatio, cpfAllocationInvalid }) => { (parseFloat(cpfRatio.ordinaryRatio) + parseFloat(cpfRatio.specialRatio)) ]; - const updateCpfSlider = (event, newValue) => setCpfRatio({ + const updateCpfSlider = (_, newValue) => setCpfRatio({ ordinaryRatio: newValue[0], specialRatio: parseFloat((newValue[1] - newValue[0]).toFixed(4)), medisaveRatio: parseFloat((1 - newValue[1]).toFixed(4)), diff --git a/src/transactions/add-transaction-dialog.jsx b/src/transactions/add-transaction-dialog.jsx index 65fdc7c..880b810 100644 --- a/src/transactions/add-transaction-dialog.jsx +++ b/src/transactions/add-transaction-dialog.jsx @@ -1,5 +1,6 @@ import 'dayjs/locale/en-sg'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { cpfCodes } from '../util/cpf-codes'; import { createFilterOptions } from '@mui/material/Autocomplete'; import { DatePicker } from '@mui/x-date-pickers/DatePicker'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; @@ -60,7 +61,7 @@ const AddTransactionDialog = ({ const setVisibleTransactionId = state.useState(state.visibleTransactionId)[1]; const { listAccounts, addTransaction, editTransaction, listTransactions, - showStatus, suggestRemarks, suggestCode, suggestCompany, getCategories, + showStatus, suggestRemarks, suggestCompany, getCategories, } = api(); useEffect(() => { @@ -100,6 +101,8 @@ const AddTransactionDialog = ({ } else { setMonth(date.startOf('month')); } + } else if (selectedAccount?.type === 'Retirement') { + setMonth(date.subtract(1, 'month').startOf('month')); } }, [ date ]); @@ -125,6 +128,9 @@ const AddTransactionDialog = ({ if (tx.originalAmount) { tx.originalAmount *= side; } + if (tx.code && tx.code.indexOf(':') > -1) { + tx.code = tx.code.split(':').shift(); + } tx['@type'] = (selectedAccount?.multiCurrency ? 'fx-' : '') + selectedAccount?.type.toLowerCase(); if (selectedAccount?.type === 'Credit') { tx.billingMonth = month.toISOString(); @@ -356,7 +362,7 @@ const AddTransactionDialog = ({ options={categories} filterOptions={createFilterOptions({ limit: 5 })} value={category} - onChange={(e, v) => setCategory(v)} + onChange={(_, v) => setCategory(v)} renderInput={(params) => ( ), code: ( - c.code)} + filterOptions={createFilterOptions({ limit: 5 })} + getOptionLabel={(o) => cpfCodes.find(({ code }) => code === o) ? (o + ': ' + cpfCodes.find(({ code }) => code === o).description) : o} + renderInput={(params) => ( + + )} value={code} - onChange={(e, v) => setCode(v.toUpperCase())} - fieldProps={{ - required: true, - inputProps: { minLength: 2 }, - name: 'code', - label: 'Code' - }} + onChange={(_, v) => setCode(v?.toUpperCase() || '')} /> ), company: ( @@ -391,6 +404,7 @@ const AddTransactionDialog = ({ name: 'company', label: 'Company' }} + sx={{ display: (code === 'CON') ? 'flex' : 'none' }} /> ), ordinaryAmount: ( diff --git a/src/transactions/auto-fill.jsx b/src/transactions/auto-fill.jsx index 16b1323..e4a5b6e 100644 --- a/src/transactions/auto-fill.jsx +++ b/src/transactions/auto-fill.jsx @@ -13,7 +13,7 @@ const AutoFill = ({ initValue, fieldProps, promise, ...props }) => { promise(request, callback), 200), []); useEffect(() => { - if (inputValue.length < 3) { + if (inputValue.length < 1) { return; } setOptions([]); diff --git a/src/transactions/transactions-grid.jsx b/src/transactions/transactions-grid.jsx index 08bda06..de98cd0 100644 --- a/src/transactions/transactions-grid.jsx +++ b/src/transactions/transactions-grid.jsx @@ -10,6 +10,7 @@ import { import { formatNumber, formatDecimal, formatDecimalHideZero, formatDecimalAbs, formatDate, formatMonth, } from '../util/formatters'; +import { cpfCodes } from '../util/cpf-codes'; import { GridPagination } from '@mui/x-data-grid'; import { HorizontalLoader } from '../core/loader'; import { pink, lightGreen } from '@mui/material/colors'; @@ -19,6 +20,7 @@ import api from '../core/api'; import Box from '@mui/system/Box'; import state from '../core/state'; import styled from 'styled-components'; +import Tooltip from '@mui/material/Tooltip'; import useMediaQuery from '@mui/material/useMediaQuery'; const GridBox = styled.div` @@ -102,6 +104,11 @@ const TransactionsGrid = ({ accounts, setShowAddDialog, setTransactionToEdit, ap const getColourClassForValue = ({ value }) => !value ? '' : value > 0 ? 'green' : 'red'; + const CpfCode = ({ value }) => { + const title = cpfCodes.find((c) => c.code === value)?.description || 'No description available'; + return {value}; + }; + const columns = { account: { flex : 2, field: 'accountId', headerName: 'Account', valueGetter: getAccountName }, date: { flex: 2.5, field: 'date', headerName: 'Date', type: 'date', valueFormatter: formatDate }, @@ -115,7 +122,7 @@ const TransactionsGrid = ({ accounts, setShowAddDialog, setTransactionToEdit, ap balance: { flex: 2, field: 'balance', headerName: 'Balance', type: 'number', valueFormatter: formatDecimal }, remarks: { flex: 4, field: 'remarks', headerName: 'Remarks' }, category: { flex: 2, field: 'category', headerName: 'Category' }, - code: { flex: 2, field: 'code', headerName: 'Code' }, + code: { flex: 2, field: 'code', headerName: 'Code', renderCell: CpfCode }, company: { flex: 2, field: 'company', headerName: 'Company' }, ordinaryAmount: { flex: 2, field: 'ordinaryAmount', headerName: 'Ordinary', type: 'number', valueFormatter: formatDecimal, cellClassName: getColourClassForValue }, specialAmount: { flex: 2, field: 'specialAmount', headerName: 'Special', type: 'number', valueFormatter: formatDecimal, cellClassName: getColourClassForValue }, diff --git a/src/util/cpf-codes.js b/src/util/cpf-codes.js new file mode 100644 index 0000000..8932b3c --- /dev/null +++ b/src/util/cpf-codes.js @@ -0,0 +1,39 @@ +export const cpfCodes = [ + { code: 'CON', description: 'Contributions' }, + { code: 'HSE', description: 'HDB flats and other residential properties' }, + { code: 'PMI', description: 'Private Medical Insurance' }, + { code: 'DPS', description: 'Dependants\' Protection Scheme' }, + { code: 'CSL', description: 'CareShield Life' }, + { code: 'ADJ', description: 'Adjustment / Interest on recovered CPF contributions' }, + { code: 'AMP', description: 'Additional Monthly Payout' }, + { code: 'BAL', description: 'Balance' }, + { code: 'CLA', description: 'CPF LIFE Annuity Premium' }, + { code: 'CLB', description: 'CPF LIFE Bonus (L-Bonus)' }, + { code: 'CLC', description: 'CPF LIFE Change of plan' }, + { code: 'CLI', description: 'CPF LIFE Payout' }, + { code: 'CLR', description: 'Refund from CPF LIFE' }, + { code: 'CLS', description: 'CPF LIFE Switch of plan' }, + { code: 'DPC', description: 'Dependants\' Protection Scheme return of premium refund' }, + { code: 'DPR', description: 'Dependants\' Protection Scheme premium refund' }, + { code: 'DPP', description: 'DPS Fund privatisation residual distribution' }, + { code: 'DVB', description: 'Deferment and/or Voluntary Deferment Bonus' }, + { code: 'EDN', description: 'Education Scheme' }, + { code: 'ESH', description: 'ElderShield' }, + { code: 'HPR', description: 'Home Protection Scheme Rebate' }, + { code: 'HPS', description: 'Home Protection Scheme' }, + { code: 'HRF', description: 'Withdrawal from balance of housing refund' }, + { code: 'INT', description: 'Interest for the whole year' }, + { code: 'INV', description: 'CPF Investment Scheme' }, + { code: 'MED', description: 'Medisave' }, + { code: 'MSH', description: 'MediShield' }, + { code: 'MSL', description: 'MediShield Life' }, + { code: 'MGS', description: 'Medical Grounds Scheme' }, + { code: 'PTY', description: 'Non-residential property' }, + { code: 'RFD', description: 'Refund of overpaid contributions' }, + { code: 'RSS', description: 'Retirement Sum Scheme' }, + { code: 'RST', description: 'Top-ups under CPF Retirement Sum Topping-Up Scheme' }, + { code: 'SNS', description: 'Special Needs Savings Scheme' }, + { code: 'SE:', description:'Payment by self-employed person' }, + { code: 'TFR', description: 'Transfer between accounts' }, + { code: 'WDL', description: 'Withdrawal of CPF savings' }, +];