Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions src/3-widgets/Navigation/SettingsMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,11 @@ import { resetData } from 'store/data'
import { userSettingsModel } from '5-entities/userSettings'
import { useRegularSync } from '3-widgets/RegularSyncHandler'
import { logOut } from '4-features/authorization'
import { exportCSV } from '4-features/export/exportCSV'
import { exportJSON } from '4-features/export/exportJSON'
import {
exportCSV,
exportJSON,
exportSimpleJSON,
} from '4-features/export'
import { clearLocalData } from '4-features/localData'
import { convertZmBudgetsToZerro } from '4-features/budget/convertZmBudgetsToZerro'
import { registerPopover } from '6-shared/historyPopovers'
Expand Down Expand Up @@ -96,6 +99,7 @@ const Settings = (props: { onClose: () => void; showLinks?: boolean }) => {
<Divider sx={{ opacity: '0.6' }} />
<ListSubheader>{t('export')}</ListSubheader>
<ExportCsvItem />
<ExportSimpleJsonItem />
<ExportJsonItem />
<Divider sx={{ opacity: '0.6' }} />
<LangItem onClose={props.onClose} />
Expand Down Expand Up @@ -127,12 +131,12 @@ function ExportCsvItem() {
function ExportJsonItem() {
const { t } = useTranslation('settings')
const dispatch = useAppDispatch()
const handleExportCSV = () => {
const handleExportJSON = () => {
sendEvent('Settings: export json')
dispatch(exportJSON)
}
return (
<MenuItem onClick={handleExportCSV}>
<MenuItem onClick={handleExportJSON}>
<ListItemIcon>
<SaveAltIcon />
</ListItemIcon>
Expand All @@ -141,6 +145,23 @@ function ExportJsonItem() {
)
}

function ExportSimpleJsonItem() {
const { t } = useTranslation('settings')
const dispatch = useAppDispatch()
const handleExportSimpleJSON = () => {
sendEvent('Settings: export simple json')
dispatch(exportSimpleJSON)
}
return (
<MenuItem onClick={handleExportSimpleJSON}>
<ListItemIcon>
<SaveAltIcon />
</ListItemIcon>
<ListItemText>{t('downloadJSON')}</ListItemText>
</MenuItem>
)
}

function ThemeItem({ onClose }: ItemProps) {
const { t } = useTranslation('settings')
const theme = useColorScheme()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,11 @@
import { createSelector } from '@reduxjs/toolkit'
import {
PopulatedTransaction,
populateTransaction,
getPopulatedTransactions,
} from './populateTransaction'
import { formatDate } from '6-shared/helpers/date'
import { ById } from '6-shared/types'
import { AppThunk } from 'store'
import { trModel, TrType } from '5-entities/transaction'
import { instrumentModel } from '5-entities/currency/instrument'
import { accountModel } from '5-entities/account'
import { tagModel } from '5-entities/tag'

// Only for CSV
const getPopulatedTransactions = createSelector(
[
instrumentModel.getInstruments,
accountModel.getAccounts,
tagModel.getPopulatedTags,
trModel.getTransactions,
],
(instruments, accounts, tags, transactions) => {
const result: { [id: string]: PopulatedTransaction } = {}
for (const id in transactions) {
result[id] = populateTransaction(
{ instruments, accounts, tags },
transactions[id]
)
}
return result
}
)
import { TrType } from '5-entities/transaction'

export const exportCSV: AppThunk = (_, getState) => {
const tr = getPopulatedTransactions(getState())
Expand Down
1 change: 0 additions & 1 deletion src/4-features/export/exportCSV/index.ts

This file was deleted.

62 changes: 62 additions & 0 deletions src/4-features/export/exportSimpleJSON.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {
PopulatedTransaction,
getPopulatedTransactions,
} from './populateTransaction'
import { formatDate } from '6-shared/helpers/date'
import { AppThunk } from 'store'
import { TrType } from '5-entities/transaction'

// TODO: i18n
const transactionTypes = {
[TrType.Income]: 'Income',
[TrType.Outcome]: 'Expense',
[TrType.Transfer]: 'Transfer',
[TrType.OutcomeDebt]: 'Debt Payment',
[TrType.IncomeDebt]: 'Debt Repayment',
}

function transactionToJsonObj(t: PopulatedTransaction) {
return {
id: t.id,
date: formatDate(t.created, 'yyyy-MM-dd'),
created: formatDate(t.created, 'yyyy-MM-dd HH:mm'),
type: transactionTypes[t.type as TrType] || t.type,

fromAccount: t.outcomeAccount ? t.outcomeAccount.title : null,
toAccount: t.incomeAccount ? t.incomeAccount.title : null,
outcome: t.outcome || 0,
outcomeCurrency: t.outcomeInstrument ? t.outcomeInstrument.shortTitle : null,
income: t.income || 0,
incomeCurrency: t.incomeInstrument ? t.incomeInstrument.shortTitle : null,

payee: t.payee || null,
comment: t.comment || null,
tags: t.tag ? t.tag.map(tag => tag.title) : [],
}
}

export const exportSimpleJSON: AppThunk = (_, getState) => {
const transactions = getPopulatedTransactions(getState())

const jsonData = Object.values(transactions)
.filter(tr => !tr.deleted)
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime())
.map(transactionToJsonObj)

const content = JSON.stringify(jsonData, null, 2)
const blob = new Blob([content], { type: 'application/json' })
const href = window.URL.createObjectURL(blob)
const fileName = `transactions-${formatDate(Date.now(), 'yyyyMMdd-HHmm')}.json`

const link = document.createElement('a')
link.setAttribute('href', href)
link.setAttribute('download', fileName)
document.body.appendChild(link) // Required for FF
link.click()

// Clean up
setTimeout(() => {
URL.revokeObjectURL(href)
document.body.removeChild(link)
}, 100)
}
3 changes: 3 additions & 0 deletions src/4-features/export/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { exportCSV } from './exportCSV'
export { exportJSON } from './exportJSON'
export { exportSimpleJSON } from './exportSimpleJSON'
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TrType } from '5-entities/transaction'
import { createSelector } from '@reduxjs/toolkit'
import { getType } from '5-entities/transaction/helpers'
import {
ByIdOld,
Expand All @@ -9,6 +9,10 @@ import {
TTagId,
TTransaction,
} from '6-shared/types'
import { trModel, TrType } from '5-entities/transaction'
import { instrumentModel } from '5-entities/currency/instrument'
import { accountModel } from '5-entities/account'
import { tagModel } from '5-entities/tag'

interface DataSources {
instruments: { [id: number]: TInstrument }
Expand Down Expand Up @@ -50,3 +54,22 @@ function mapTags(ids: TTagId[] | null, tags: ByIdOld<TTag>) {
// TODO: Надо что-то придумать с null тегом 🤔 ⤵
return ids && ids.length ? ids.map(id => tags[id + '']) : null
}

export const getPopulatedTransactions = createSelector(
[
instrumentModel.getInstruments,
accountModel.getAccounts,
tagModel.getPopulatedTags,
trModel.getTransactions,
],
(instruments, accounts, tags, transactions) => {
const result: { [id: string]: PopulatedTransaction } = {}
for (const id in transactions) {
result[id] = populateTransaction(
{ instruments, accounts, tags },
transactions[id]
)
}
return result
}
)
1 change: 1 addition & 0 deletions src/6-shared/localization/translations/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,7 @@ export const en: typeof ru = {
export: 'Export',
downloadCSV: 'Download CSV',
fullBackup: 'Full backup',
downloadJSON: 'Download JSON',
darkMode: 'Dark Mode',
lightMode: 'Light Mode',
reloadData: 'Reload data',
Expand Down
1 change: 1 addition & 0 deletions src/6-shared/localization/translations/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@
"export": "Экспорт",
"downloadCSV": "Скачать CSV",
"fullBackup": "Полный бэкап",
"downloadJSON": "Скачать JSON",
"darkMode": "Тёмная тема",
"lightMode": "Светлая тема",
"reloadData": "Перезагрузить данные",
Expand Down