diff --git a/apps/web/src/components/ShareSelect.tsx b/apps/web/src/components/ShareSelect.tsx index 24282c6a..b16443cf 100644 --- a/apps/web/src/components/ShareSelect.tsx +++ b/apps/web/src/components/ShareSelect.tsx @@ -168,9 +168,11 @@ interface ShareSelectProps { shouldDisplayAccount?: (accountId: number) => boolean | undefined; additionalShareInfoHeader?: React.ReactNode | undefined; AdditionalShareInfo?: React.FC<{ account: Account }> | undefined; - excludeAccounts?: number[] | undefined; + communistShares?: number; + onChangeCommunistShares?: (value: number) => void; currencyIdentifier?: string; editable?: boolean | undefined; + hideShowEventsFilter?: boolean; } export const ShareSelect: React.FC = ({ @@ -184,11 +186,13 @@ export const ShareSelect: React.FC = ({ shouldDisplayAccount, additionalShareInfoHeader, AdditionalShareInfo, - excludeAccounts, + communistShares, + onChangeCommunistShares, currencyIdentifier, error, helperText, editable = false, + hideShowEventsFilter = false, }) => { const { t } = useTranslation(); const theme = useTheme(); @@ -213,17 +217,16 @@ export const ShareSelect: React.FC = ({ return true; } - if (editable) { - return !(!showEvents && a.type === "clearing"); + if (a.type === "clearing" && !showEvents && !hideShowEventsFilter) { + return false; } + if (shouldDisplayAccount) { return shouldDisplayAccount(accountId); } - return false; + + return editable; }; - if (excludeAccounts && excludeAccounts.includes(a.id)) { - return false; - } if (!isAccountShown(a.id)) { return false; } @@ -237,13 +240,14 @@ export const ShareSelect: React.FC = ({ return true; }) .sort(sortFn); - }, [value, showEvents, editable, searchValue, unfilteredAccounts, excludeAccounts, shouldDisplayAccount]); + }, [value, showEvents, editable, searchValue, unfilteredAccounts, shouldDisplayAccount]); React.useEffect(() => { // set displayed split mode to evenly if we have a "shares" split with non-even shares if ( splitMode === "shares" && - Object.values(value).reduce((onlyDefaultShares, value) => onlyDefaultShares && value === 1, true) + Object.values(value).reduce((onlyDefaultShares, value) => onlyDefaultShares && value === 1, true) && + (communistShares == null || communistShares === 1 || communistShares === 0) ) { setFrontendSplitMode("evenly"); } @@ -323,22 +327,25 @@ export const ShareSelect: React.FC = ({ {editable && ( - ) => - setShowEvents(event.target.checked) - } - /> - } - checked={showEvents} - label={t("shareSelect.showEvents")} - /> + {!hideShowEventsFilter && ( + ) => + setShowEvents(event.target.checked) + } + /> + } + checked={showEvents} + label={t("shareSelect.showEvents")} + /> + )} handleSplitModeChange(e.target.value as FrontendSplitMode)} label={t("shareSelect.splitMode")} select @@ -350,6 +357,29 @@ export const ShareSelect: React.FC = ({ )} + {communistShares != null && + (frontendSplitMode === "evenly" ? ( + ) => + onChangeCommunistShares?.(event.target.checked ? 1.0 : 0.0) + } + /> + } + checked={communistShares != null && communistShares > 0} + label={t("transactions.positions.shared")} + /> + ) : ( + + ))} = ({ /> )} - + {editable ? ( } diff --git a/apps/web/src/pages/accounts/AccountDetail/AccountClearingListEntry.tsx b/apps/web/src/pages/accounts/AccountDetail/AccountClearingListEntry.tsx index d86e2008..579566f3 100644 --- a/apps/web/src/pages/accounts/AccountDetail/AccountClearingListEntry.tsx +++ b/apps/web/src/pages/accounts/AccountDetail/AccountClearingListEntry.tsx @@ -37,6 +37,7 @@ export const AccountClearingListEntry: React.FC = ({ groupId, accountId, {clearingAccount.name} } + slots={{ secondary: "div" }} secondary={
diff --git a/apps/web/src/pages/accounts/AccountDetail/AccountInfo.tsx b/apps/web/src/pages/accounts/AccountDetail/AccountInfo.tsx index ae290970..945ecb56 100644 --- a/apps/web/src/pages/accounts/AccountDetail/AccountInfo.tsx +++ b/apps/web/src/pages/accounts/AccountDetail/AccountInfo.tsx @@ -121,6 +121,10 @@ export const AccountInfo: React.FC = ({ groupId, account }) => { return ; } + const shouldDisplayAccount = (accountId: number) => { + return account.is_wip && accountId !== account.id; + }; + return ( <> @@ -251,7 +255,7 @@ export const AccountInfo: React.FC = ({ groupId, account }) => { } error={!!validationErrors.fieldErrors.clearing_shares} helperText={validationErrors.fieldErrors.clearing_shares} - excludeAccounts={[account.id]} + shouldDisplayAccount={shouldDisplayAccount} AdditionalShareInfo={({ account: participatingAccount }) => ( = ({ groupId, account }) => { )} onChange={(value) => pushChanges({ clearing_shares: value })} - editable={account.is_wip} + editable={true} /> ) : null} diff --git a/apps/web/src/pages/accounts/AccountDetail/AccountTransactionListEntry.tsx b/apps/web/src/pages/accounts/AccountDetail/AccountTransactionListEntry.tsx index 55a0f534..d3b3f907 100644 --- a/apps/web/src/pages/accounts/AccountDetail/AccountTransactionListEntry.tsx +++ b/apps/web/src/pages/accounts/AccountDetail/AccountTransactionListEntry.tsx @@ -36,6 +36,7 @@ export const AccountTransactionListEntry: React.FC = ({ groupId, transact } + slots={{ secondary: "div" }} secondary={
diff --git a/apps/web/src/pages/accounts/AccountDetail/ClearingAccountDetail.tsx b/apps/web/src/pages/accounts/AccountDetail/ClearingAccountDetail.tsx index 32c76dd0..289c4b39 100644 --- a/apps/web/src/pages/accounts/AccountDetail/ClearingAccountDetail.tsx +++ b/apps/web/src/pages/accounts/AccountDetail/ClearingAccountDetail.tsx @@ -33,7 +33,6 @@ export const ClearingAccountDetail: React.FC = ({ groupId, account }) => {t("common.shared")} } - excludeAccounts={[account.id]} AdditionalShareInfo={({ account: participatingAccount }) => ( = ({ groupId }) => { const dispatch = useAppDispatch(); const navigate = useNavigate(); const transactionId = Number(params["id"]); + const isSmallScreen = useIsSmallScreen(); const [showPositions, setShowPositions] = React.useState(false); const group = useGroup(groupId); @@ -170,11 +173,19 @@ export const TransactionDetail: React.FC = ({ groupId }) => { ) : (showPositions && transaction.is_wip) || hasPositions ? ( - + isSmallScreen ? ( + + ) : ( + + ) ) : null} ); diff --git a/apps/web/src/pages/transactions/TransactionDetail/TransactionMetadata.tsx b/apps/web/src/pages/transactions/TransactionDetail/TransactionMetadata.tsx index 8789e7f1..b656ddea 100644 --- a/apps/web/src/pages/transactions/TransactionDetail/TransactionMetadata.tsx +++ b/apps/web/src/pages/transactions/TransactionDetail/TransactionMetadata.tsx @@ -76,27 +76,28 @@ export const TransactionMetadata: React.FC = ({ /> - - {total} - + {total} ); } - return ( - - {total} - - ); + return {total}; }, [showPositions, hasPositions, transaction, balanceEffect, groupCurrencyIdentifier, isSmallScreen] ); const shouldDisplayAccount = React.useCallback( - (accountId: number) => - balanceEffect[accountId] !== undefined && - (balanceEffect[accountId].commonDebitors !== 0 || balanceEffect[accountId].positions !== 0), - [balanceEffect] + (accountId: number) => { + if (transaction.is_wip) { + return true; + } + + return ( + balanceEffect[accountId] !== undefined && + (balanceEffect[accountId].commonDebitors !== 0 || balanceEffect[accountId].positions !== 0) + ); + }, + [balanceEffect, transaction.is_wip] ); const pushChanges = React.useCallback( @@ -367,14 +368,10 @@ export const TransactionMetadata: React.FC = ({ )} - - {t("common.total")} - + {t("common.total")} ) : ( - - {t("common.shared")} - + {t("common.shared")} ) } AdditionalShareInfo={ShareInfo} diff --git a/apps/web/src/pages/transactions/TransactionDetail/purchase/PositionEditDialog.tsx b/apps/web/src/pages/transactions/TransactionDetail/purchase/PositionEditDialog.tsx new file mode 100644 index 00000000..01288755 --- /dev/null +++ b/apps/web/src/pages/transactions/TransactionDetail/purchase/PositionEditDialog.tsx @@ -0,0 +1,209 @@ +import { useAppDispatch, useAppSelector } from "@/store"; +import { selectTransactionPositionById, wipPositionUpdated } from "@abrechnung/redux"; +import { Account, PositionValidator, Transaction, TransactionShare } from "@abrechnung/types"; +import { + AppBar, + Button, + Dialog, + DialogContent, + DialogTitle, + FormHelperText, + InputAdornment, + Slide, + Stack, + Toolbar, + Typography, +} from "@mui/material"; +import * as React from "react"; +import { useTranslation } from "react-i18next"; +import { PositionValidationError } from "./types"; +import { AccountSelect, ShareSelect, TextInput } from "@/components"; +import { NumericInput } from "@abrechnung/components"; +import { getCurrencySymbolForIdentifier } from "@abrechnung/core"; +import { TransitionProps } from "@mui/material/transitions"; +import { useState } from "react"; +import z from "zod"; + +export type PositionEditDialogProps = { + transaction: Transaction; + open: boolean; + onClose: () => void; + positionId?: number; + validationError?: PositionValidationError; + shownAccountIds: number[]; + updateShownAccountIds: (value: number[]) => void; +}; + +const Transition = React.forwardRef(function Transition( + props: TransitionProps & { + children: React.ReactElement; + }, + ref: React.Ref +) { + return ; +}); + +export const PositionEditDialog: React.FC = ({ + transaction, + open, + onClose, + positionId, + validationError: outerValidationError, + shownAccountIds, + updateShownAccountIds, +}) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const position = useAppSelector((state) => + selectTransactionPositionById(state, transaction.group_id, transaction.id, positionId) + ); + const [localValidationError, setLocalValidationError] = useState( + outerValidationError + ); + + const validationError = localValidationError ?? outerValidationError; + + const [showAddAccountModal, setShowAddAccountModal] = useState(false); + + React.useEffect(() => { + setLocalValidationError(outerValidationError); + }, [outerValidationError]); + + const updatePositionUsage = (shares: TransactionShare) => { + if (!position) { + return; + } + dispatch( + wipPositionUpdated({ + groupId: transaction.group_id, + transactionId: transaction.id, + position: { ...position, usages: shares }, + }) + ); + }; + + const updatePosition = (update: { name?: string; price?: number; communist_shares?: number }) => { + if (!position) { + return; + } + dispatch( + wipPositionUpdated({ + groupId: transaction.group_id, + transactionId: transaction.id, + position: { ...position, ...update }, + }) + ); + }; + + const addShownAccount = (account: Account) => { + setShowAddAccountModal(false); + updateShownAccountIds(Array.from(new Set([...shownAccountIds, account.id]))); + }; + + React.useEffect(() => { + if (!position) { + setLocalValidationError(undefined); + } + const validated = PositionValidator.safeParse(position); + if (!validated.success) { + setLocalValidationError(z.flattenError(validated.error)); + return; + } + setLocalValidationError(undefined); + }, [position, setLocalValidationError]); + + const handleClose = () => { + const validated = PositionValidator.safeParse(position); + if (!validated.success) { + setLocalValidationError(z.flattenError(validated.error)); + return; + } + setLocalValidationError(undefined); + onClose(); + }; + + return ( + <> + + + + + {t("transactions.positions.editPosition")} + + + + + + + updatePosition({ name: value })} + /> + updatePosition({ price: value })} + slotProps={{ + input: { + endAdornment: ( + + {getCurrencySymbolForIdentifier(transaction.currency_identifier)} + + ), + }, + }} + /> + {validationError?.formErrors && ( + + {validationError.formErrors} + + )} + shownAccountIds.includes(accountId)} + hideShowEventsFilter + communistShares={position?.communist_shares ?? 0} + onChangeCommunistShares={(shares) => updatePosition({ communist_shares: shares })} + /> + + + + + setShowAddAccountModal(false)} fullWidth> + {t("transactions.positions.addAccount")} + + + + + + ); +}; diff --git a/apps/web/src/pages/transactions/TransactionDetail/purchase/PositionTableRowMobile.tsx b/apps/web/src/pages/transactions/TransactionDetail/purchase/PositionTableRowMobile.tsx new file mode 100644 index 00000000..fe1f6b5c --- /dev/null +++ b/apps/web/src/pages/transactions/TransactionDetail/purchase/PositionTableRowMobile.tsx @@ -0,0 +1,122 @@ +import { Transaction, TransactionPosition } from "@abrechnung/types"; +import { ContentCopy, Delete } from "@mui/icons-material"; +import { FormHelperText, IconButton, TableCell, TableRow, useTheme } from "@mui/material"; +import React from "react"; +import { PositionValidationError } from "./types"; +import { CurrencyDisplay } from "@/components"; +import { useAppDispatch, useAppSelector } from "@/store"; +import { positionDeleted, selectAccountIdToAccountMap, wipPositionAdded } from "@abrechnung/redux"; +import { Trans, useTranslation } from "react-i18next"; + +interface PositionTableRowMobileProps { + transaction: Transaction; + position: TransactionPosition; + onEdit: (position: TransactionPosition) => void; + validationError?: PositionValidationError; +} + +export const PositionTableRowMobile: React.FC = ({ + transaction, + position, + onEdit, + validationError, +}) => { + const { t } = useTranslation(); + const theme = useTheme(); + const dispatch = useAppDispatch(); + const accounts = useAppSelector((state) => selectAccountIdToAccountMap(state, transaction.group_id)); + + const usageNames = Object.keys(position.usages) + .map((accountId) => accounts[Number(accountId)]?.name) + .concat(position.communist_shares > 0 ? [t("common.shared")] : []) + .join(", "); + + const error = validationError !== undefined; + + const deletePosition = (position: TransactionPosition) => { + if (!transaction.is_wip) { + return; + } + dispatch( + positionDeleted({ groupId: transaction.group_id, transactionId: transaction.id, positionId: position.id }) + ); + }; + + const copyPosition = (position: TransactionPosition) => { + if (!transaction.is_wip) { + return; + } + dispatch( + wipPositionAdded({ + groupId: transaction.group_id, + transactionId: transaction.id, + position: { ...position, name: `${position.name} (${t("common.copy")})` }, + }) + ); + }; + const editPosition = () => { + if (transaction.is_wip) { + onEdit(position); + } + }; + + return ( + + + {validationError && validationError.formErrors && ( + + {validationError.formErrors} + + )} + {validationError && validationError.fieldErrors["communist_shares"] && ( + + {validationError.fieldErrors["communist_shares"]} + + )} + {validationError && validationError.fieldErrors["usages"] && ( + + {validationError.fieldErrors["usages"]} + + )} + {position.name} +
+ +
+ + + + {transaction.is_wip && ( + + { + e.preventDefault(); + e.stopPropagation(); + copyPosition(position); + }} + > + + + { + e.preventDefault(); + e.stopPropagation(); + deletePosition(position); + }} + > + + + + )} +
+ ); +}; diff --git a/apps/web/src/pages/transactions/TransactionDetail/purchase/TransactionPositions.tsx b/apps/web/src/pages/transactions/TransactionDetail/purchase/TransactionPositions.tsx index 4f8d7587..02038fca 100644 --- a/apps/web/src/pages/transactions/TransactionDetail/purchase/TransactionPositions.tsx +++ b/apps/web/src/pages/transactions/TransactionDetail/purchase/TransactionPositions.tsx @@ -7,6 +7,7 @@ import { positionDeleted, selectAccountIdToAccountMap, selectTransactionBalanceEffect, + selectTransactionPositionTotal, useGroupAccounts, useTransaction, useWipTransactionPositions, @@ -102,7 +103,7 @@ export const TransactionPositions: React.FC = ({ const [showAccountSelect, setShowAccountSelect] = useState(false); - const totalPositionValue = positions.reduce((acc, curr) => acc + curr.price, 0); + const totalPositionValue = useAppSelector((state) => selectTransactionPositionTotal(state, groupId, transactionId)); const totalPositionSharedValue = positions.reduce( (currTotal, position) => currTotal + position.price * position.communist_shares, 0 diff --git a/apps/web/src/pages/transactions/TransactionDetail/purchase/TransactionPositionsMobile.tsx b/apps/web/src/pages/transactions/TransactionDetail/purchase/TransactionPositionsMobile.tsx new file mode 100644 index 00000000..3a9d3a1a --- /dev/null +++ b/apps/web/src/pages/transactions/TransactionDetail/purchase/TransactionPositionsMobile.tsx @@ -0,0 +1,178 @@ +import { MobilePaper } from "@/components/style"; +import { CurrencyDisplay } from "@/components"; +import { + selectAccountIdToAccountMap, + selectTransactionPositionTotal, + useTransaction, + useTransactionPositions, + wipPositionAdded, +} from "@abrechnung/redux"; +import { + Box, + Grid, + IconButton, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, +} from "@mui/material"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { ValidationErrors } from "./types"; +import { PositionTableRowMobile } from "./PositionTableRowMobile"; +import { Add } from "@mui/icons-material"; +import { useAppDispatch, useAppSelector } from "@/store"; +import { TransactionPosition } from "@abrechnung/types"; +import { PositionEditDialog } from "./PositionEditDialog"; +import { getAccountSortFunc } from "@abrechnung/core"; + +interface TransactionPositionsMobileProps { + groupId: number; + transactionId: number; + validationErrors?: ValidationErrors; +} + +export const TransactionPositionsMobile: React.FC = ({ + groupId, + transactionId, + validationErrors, +}) => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const transaction = useTransaction(groupId, transactionId)!; + const accountIDMap = useAppSelector((state) => selectAccountIdToAccountMap(state, groupId)); + const positions = useTransactionPositions(groupId, transactionId); + const [showEditDialog, setShowEditDialog] = useState(false); + const [editedPositionId, setEditedPositionId] = useState(undefined); + + const [shownAccountIds, setShownAccountIds] = useState([]); + const shownAccountIdsFromTransaction = React.useMemo(() => { + let accountIdsFromPositions: number[] = positions + .map((item) => Object.keys(item.usages)) + .flat() + .map((id) => parseInt(id)); + + let accountIdsFromDebitorShares: number[] = []; + if (transaction.is_wip) { + accountIdsFromDebitorShares = Object.keys(transaction.debitor_shares).map((id) => parseInt(id)); + } + + return Array.from(new Set([...accountIdsFromPositions, ...accountIdsFromDebitorShares])); + }, [transaction, positions]); + + React.useEffect(() => { + setShownAccountIds((currAdditionalAccounts: number[]) => { + const sortFunc = getAccountSortFunc("name"); + const sortedShownAccounts = [...shownAccountIdsFromTransaction].sort((acc1Id: number, acc2Id: number) => + sortFunc(accountIDMap[acc1Id], accountIDMap[acc2Id]) + ); + const allAccountIds = Array.from(new Set([...currAdditionalAccounts, ...sortedShownAccounts])); + return allAccountIds; + }); + }, [shownAccountIdsFromTransaction, accountIDMap]); + + const totalPositionValue = useAppSelector((state) => selectTransactionPositionTotal(state, groupId, transactionId)); + const remainingPositionValue = transaction.value - totalPositionValue; + + const addPosition = () => { + dispatch( + wipPositionAdded({ + groupId, + transactionId, + position: { name: "", price: 0.0, communist_shares: 0, usages: {} }, + }) + ) + .unwrap() + .then(({ position: newPosition }) => { + setEditedPositionId(newPosition.id); + setShowEditDialog(true); + }); + }; + + const onEditPosition = (position: TransactionPosition) => { + setEditedPositionId(position.id); + setShowEditDialog(true); + }; + + return ( + + + {t("transactions.positions.positions")} + + + + + + {t("common.name")} + {t("common.price")} + {transaction.is_wip && } + + + + {positions.map((position) => ( + + ))} + {transaction.is_wip && ( + + + + + + + + + + )} + + + {t("common.totalWithColon")} + + + + + {transaction.is_wip && } + + + + + {t("transactions.positions.remaining")} + + + + + + {/* Next table column is the actions */} + {transaction.is_wip && } + + +
+
+ {transaction.is_wip && ( + setShowEditDialog(false)} + positionId={editedPositionId} + validationError={editedPositionId != null ? validationErrors?.[editedPositionId] : undefined} + shownAccountIds={shownAccountIds} + updateShownAccountIds={setShownAccountIds} + /> + )} +
+ ); +}; diff --git a/libs/components/src/lib/numeric-input/NumericInput.tsx b/libs/components/src/lib/numeric-input/NumericInput.tsx index 4dae706c..b58d8b48 100644 --- a/libs/components/src/lib/numeric-input/NumericInput.tsx +++ b/libs/components/src/lib/numeric-input/NumericInput.tsx @@ -5,7 +5,7 @@ import { evaluateExpression } from "./mathExpression"; import { useTranslation } from "react-i18next"; export type NumericInputProps = { - onChange: (value: number) => void; + onChange?: (value: number) => void; value?: number | undefined; isCurrency?: boolean | false; } & Omit; @@ -70,7 +70,7 @@ export const NumericInput: React.FC = ({ const finalizeInput = () => { const updateValue = (value: number) => { setInternalValue(formatValue(value)); - onChange(value); + onChange?.(value); setInternalError(false); setInternalHelperText(undefined); }; diff --git a/libs/redux/src/lib/transactions/transactionSlice.ts b/libs/redux/src/lib/transactions/transactionSlice.ts index cb77f1dd..b6f9146e 100644 --- a/libs/redux/src/lib/transactions/transactionSlice.ts +++ b/libs/redux/src/lib/transactions/transactionSlice.ts @@ -97,6 +97,17 @@ export const useTransaction = (groupId: number, transactionId: number): Transact export const selectNextLocalPositionId = (state: TransactionSliceState): number => { return state.nextLocalPositionId; }; +const selectTransactionPositions = createSelector(selectTransactionById, (transaction: Transaction | undefined) => { + if (!transaction) { + return []; + } + const positions = transaction?.position_ids.map((id) => transaction.positions[id]).filter((p) => !p.deleted) ?? []; + return positions; +}); + +export const useTransactionPositions = (groupId: number, transactionId: number): TransactionPosition[] => { + return useSelector((state: IRootState) => selectTransactionPositions(state, groupId, transactionId)); +}; const selectWipTransactionPositions = createSelector( selectTransactionById, @@ -131,6 +142,27 @@ export const useWipTransactionPositions = (groupId: number, transactionId: numbe return useSelector((state: IRootState) => selectWipTransactionPositions(state, groupId, transactionId)); }; +export const selectTransactionPositionById = createSelector( + selectTransactionById, + (state: IRootState, groupId: number, transactionId: number, positionId: number | undefined) => positionId, + (transaction: Transaction | undefined, positionId: number | undefined): TransactionPosition | undefined => { + if (!transaction || positionId == null) { + return undefined; + } + return transaction.positions[positionId]; + } +); + +export const selectTransactionPositionTotal = createSelector( + selectTransactionById, + (transaction: Transaction | undefined): number => { + if (!transaction) { + return 0; + } + return Object.values(transaction.positions).reduce((acc, curr) => acc + curr.price, 0); + } +); + export const selectTransactionHasPositions = createSelector( selectTransactionById, (transaction: Transaction | undefined): boolean => { @@ -426,6 +458,27 @@ export const deleteTransaction = createAsyncThunkWithErrorHandling< return { transaction: undefined }; }); +export const wipPositionAdded = createAsyncThunkWithErrorHandling< + { position: TransactionPosition }, + { + groupId: number; + transactionId: number; + position: Omit; + }, + { state: IRootState } +>("wipPositionAdded", async ({ groupId, transactionId, position: data }, { getState, dispatch }) => { + const state = getState(); + const position = { + ...data, + id: state.transactions.nextLocalPositionId, + deleted: false, + only_local: true, + is_changed: true, + }; + dispatch(wipPositionAddedInternal({ groupId, position, transactionId })); + return { position }; +}); + const initialState: TransactionSliceState = { byGroupId: {}, nextLocalTransactionId: -1, @@ -609,12 +662,12 @@ const transactionSlice = createSlice({ }; updateTransactionBalanceEffect(s.wipTransactions, transaction.id); }, - wipPositionAdded: ( + wipPositionAddedInternal: ( state, action: PayloadAction<{ groupId: number; transactionId: number; - position: Omit; + position: TransactionPosition; }> ) => { const { groupId, transactionId, position } = action.payload; @@ -624,12 +677,10 @@ const transactionSlice = createSlice({ return; } - const positionId = state.nextLocalPositionId; - state.nextLocalPositionId = positionId - 1; - wipTransaction.position_ids.push(positionId); - wipTransaction.positions[positionId] = { + state.nextLocalPositionId = state.nextLocalPositionId - 1; + wipTransaction.position_ids.push(position.id); + wipTransaction.positions[position.id] = { ...position, - id: positionId, deleted: false, only_local: true, is_changed: true, @@ -797,10 +848,11 @@ export const { wipFileDeleted, wipFileUpdated, wipPositionUpdated, - wipPositionAdded, positionDeleted, discardTransactionChange, setTransactionStatus, } = transactionSlice.actions; +const { wipPositionAddedInternal } = transactionSlice.actions; + export const { reducer: transactionReducer } = transactionSlice; diff --git a/libs/translations/src/lib/en.json b/libs/translations/src/lib/en.json index 4fdf0d16..ad66a8e8 100644 --- a/libs/translations/src/lib/en.json +++ b/libs/translations/src/lib/en.json @@ -46,6 +46,7 @@ "linkCopiedToClipboard": "Link copied to clipboard!", "qrCode": "Qr Code", "createdBy": "created by {{username}}", + "copy": "Copy", "changedBy": "changed by {{username}}" }, "shareSelect": { @@ -195,7 +196,11 @@ "positions": "Positions", "sharedPlusRest": "Shared + Rest", "addPositions": "Add Positions", - "remaining": "Remaining:" + "editPosition": "Edit Position", + "addAccount": "Add Account", + "remaining": "Remaining:", + "shared": "Shared with all Purchase Participants", + "usagesFor": "For {{for}}" }, "history": { "linkTo": "History"