Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/feat/massCategorization'
Browse files Browse the repository at this point in the history
  • Loading branch information
Florian Pires committed Jun 25, 2021
2 parents d051563 + bc4e8d3 commit a20dfcc
Show file tree
Hide file tree
Showing 18 changed files with 591 additions and 175 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@
"react-router": "3.2.4",
"react-side-effect": "2.1.0",
"react-swipeable-views": "0.13.9",
"react-tappable": "1.0.4",
"redux": "4",
"redux-logger": "3.0.6",
"redux-raven-middleware": "1.2.0",
Expand Down
9 changes: 6 additions & 3 deletions src/AppContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import flag from 'cozy-flags'
import { TrackerProvider } from 'ducks/tracking/browser'
import JobsProvider from 'ducks/context/JobsContext'
import BanksProvider from 'ducks/context/BanksContext'
import SelectionProvider from 'ducks/context/SelectionContext'
import Alerter from 'cozy-ui/transpiled/react/Alerter'
import { initTranslation } from 'cozy-ui/transpiled/react/I18n'

Expand Down Expand Up @@ -68,9 +69,11 @@ const AppContainer = ({ store, lang, history, client }) => {
>
<JobsProvider client={client} options={jobsProviderOptions(t)}>
<BanksProvider client={client}>
<MuiCozyTheme>
<Router history={history} routes={AppRoute()} />
</MuiCozyTheme>
<SelectionProvider>
<MuiCozyTheme>
<Router history={history} routes={AppRoute()} />
</MuiCozyTheme>
</SelectionProvider>
</BanksProvider>
</JobsProvider>
</I18n>
Expand Down
85 changes: 85 additions & 0 deletions src/ducks/context/SelectionContext.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React, {
createContext,
useState,
useCallback,
useEffect,
useContext,
useMemo
} from 'react'

import flag from 'cozy-flags'

export const SelectionContext = createContext()

export const useSelectionContext = () => {
return useContext(SelectionContext)
}

const SelectionProvider = ({ children }) => {
const [isSelectionModeActive, setIsSelectionModeActive] = useState(false)
const [selected, setSelected] = useState([])
const isSelectionModeEnabled = flag('banks.selectionMode.enabled')

const activateSelectionMode = useCallback(
() => isSelectionModeEnabled && setIsSelectionModeActive(true),
[isSelectionModeEnabled]
)

const deactivateSelectionMode = () => setIsSelectionModeActive(false)

const addToSelection = useCallback(
item => {
setSelected(v => [...v, item])
},
[setSelected]
)

const removeFromSelection = useCallback(
item => {
setSelected(selected.filter(e => e._id !== item._id))
},
[selected]
)

const emptySelection = useCallback(() => setSelected([]), [setSelected])

const isSelected = useCallback(item => selected.includes(item), [selected])

useEffect(() => {
if (isSelectionModeActive && selected.length === 0) {
deactivateSelectionMode()
}
if (!isSelectionModeActive && selected.length > 0) {
activateSelectionMode()
}
}, [selected, activateSelectionMode, isSelectionModeActive])

const value = useMemo(
() => ({
isSelectionModeActive,
selected,
addToSelection,
isSelected,
emptySelection,
removeFromSelection,
isSelectionModeEnabled
}),
[
addToSelection,
emptySelection,
isSelected,
isSelectionModeActive,
isSelectionModeEnabled,
removeFromSelection,
selected
]
)

return (
<SelectionContext.Provider value={value}>
{children}
</SelectionContext.Provider>
)
}

export default SelectionProvider
63 changes: 63 additions & 0 deletions src/ducks/selection/SelectionBar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React, { useCallback, useMemo } from 'react'

import UISelectionBar from 'cozy-ui/transpiled/react/SelectionBar'
import Alerter from 'cozy-ui/transpiled/react/Alerter'
import { useI18n } from 'cozy-ui/transpiled/react/I18n'

import { useSelectionContext } from 'ducks/context/SelectionContext'
import { makeSelectionBarActions } from 'ducks/selection/helpers'
import { useTransactionCategoryModal } from 'ducks/transactions/TransactionRow'

const SelectionBar = () => {
const {
isSelectionModeEnabled,
isSelectionModeActive,
selected,
emptySelection
} = useSelectionContext()

const { t } = useI18n()

const beforeUpdate = useCallback(() => {
emptySelection()
}, [emptySelection])

const afterUpdates = () => {
Alerter.success(
t('Categorization.success', {
smart_count: selected.length
})
)
}

const [
showTransactionCategoryModal,
,
transactionCategoryModal
] = useTransactionCategoryModal({
transactions: selected,
beforeUpdate,
afterUpdates
})

const actions = useMemo(
() => makeSelectionBarActions(showTransactionCategoryModal),
[showTransactionCategoryModal]
)

if (!isSelectionModeEnabled) return null
return (
<>
{isSelectionModeActive && (
<UISelectionBar
actions={actions}
selected={selected}
hideSelectionBar={emptySelection}
/>
)}
{transactionCategoryModal}
</>
)
}

export default SelectionBar
11 changes: 11 additions & 0 deletions src/ducks/selection/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import GraphCircleIcon from 'cozy-ui/transpiled/react/Icons/GraphCircle'

export const makeSelectionBarActions = showTransactionCategoryModal => {
return {
categorize: {
action: () => showTransactionCategoryModal(),
displayCondition: selected => selected.length > 0,
icon: GraphCircleIcon
}
}
}
72 changes: 50 additions & 22 deletions src/ducks/transactions/TransactionCategoryEditor.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import React, { useMemo, useCallback } from 'react'
import { useClient } from 'cozy-client'
import {
updateTransactionCategory,
Expand All @@ -9,32 +9,60 @@ import CategoryChoice from 'ducks/categories/CategoryChoice'
/**
* Edits a transaction's category through CategoryChoice
*/
const TransactionCategoryEditor = props => {
const TransactionCategoryEditor = ({
transactions,
beforeUpdate,
afterUpdate,
afterUpdates,
onCancel
}) => {
const client = useClient()
const { transaction, beforeUpdate, afterUpdate, onCancel } = props

const handleSelect = async category => {
if (beforeUpdate) {
await beforeUpdate()
}
const newTransaction = await updateTransactionCategory(
client,
transaction,
category
)
if (afterUpdate) {
await afterUpdate(newTransaction)
}
}

const handleCancel = async () => {
const categoryId = useMemo(() => getCategoryId(transactions[0]), [
transactions
])

const handleUpdate = useCallback(
async (transaction, category) => {
const newTransaction = await updateTransactionCategory(
client,
transaction,
category
)

if (afterUpdate) {
await afterUpdate(newTransaction)
}
},
[afterUpdate, client]
)

const handleSelect = useCallback(
async category => {
if (beforeUpdate) {
await beforeUpdate()
}

const promises = transactions.map(transaction =>
handleUpdate(transaction, category)
)

await Promise.all(promises)

if (afterUpdates) {
afterUpdates()
}
},
[afterUpdates, beforeUpdate, handleUpdate, transactions]
)

const handleCancel = useCallback(async () => {
await onCancel()
}
}, [onCancel])

return (
<CategoryChoice
modal={true}
categoryId={getCategoryId(transaction)}
categoryId={categoryId}
onSelect={handleSelect}
onCancel={handleCancel}
/>
Expand All @@ -45,4 +73,4 @@ TransactionCategoryEditor.defaultProps = {
modal: false
}

export default TransactionCategoryEditor
export default React.memo(TransactionCategoryEditor)
2 changes: 1 addition & 1 deletion src/ducks/transactions/TransactionModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ const TransactionCategoryEditorDialog = ({ transaction, onClose }) => {
beforeUpdate={handlePop}
afterUpdate={onAfterUpdate}
onCancel={handlePop}
transaction={transaction}
transactions={[transaction]}
/>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const TransactionRowDesktop = props => {
showTransactionCategoryModal,
,
categoryModal
] = useTransactionCategoryModal(transaction)
] = useTransactionCategoryModal({ transactions: [transaction] })

const handleClickCategory = useCallback(
ev => {
Expand Down
Loading

0 comments on commit a20dfcc

Please sign in to comment.