@tasenor/common-plugins 1.9.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/.eslintrc.js +4 -0
  2. package/.turbo/turbo-fix.log +4 -0
  3. package/.turbo/turbo-lint.log +4 -0
  4. package/.turbo/turbo-version.log +5 -0
  5. package/LICENSE +21 -0
  6. package/README.md +15 -0
  7. package/data/FinnishBalanceSheetReport.mjs +9 -0
  8. package/data/FinnishBalanceSheetReportInvestment.mjs +11 -0
  9. package/data/FinnishBalanceSheetReportLite.mjs +9 -0
  10. package/data/FinnishIncomeStatementReport.mjs +9 -0
  11. package/data/FinnishIncomeStatementReportInvestment.mjs +11 -0
  12. package/data/FinnishIncomeStatementReportLite.mjs +9 -0
  13. package/data/FinnishInvestmentCompany.mjs +5 -0
  14. package/data/FinnishLimitedCompanyComplete.mjs +5 -0
  15. package/data/FinnishLimitedCompanyLite.mjs +9 -0
  16. package/data/IncomeAndExpenses.mjs +64 -0
  17. package/data/README.md +8 -0
  18. package/data/VATFinland.mjs +22 -0
  19. package/data/bin/build_all +11 -0
  20. package/data/lib/utils.mjs +314 -0
  21. package/data/src/Assets Tree - Definitions.tsv +101 -0
  22. package/data/src/Expense Tree - Definitions.tsv +99 -0
  23. package/data/src/Finland VAT - Definitions.tsv +22 -0
  24. package/data/src/FinnishBalanceSheetReport - balance-sheet-detailed-fi.tsv +215 -0
  25. package/data/src/FinnishBalanceSheetReport - balance-sheet-fi.tsv +93 -0
  26. package/data/src/FinnishBalanceSheetReportInvestment - balance-sheet-detailed-fi.tsv +100 -0
  27. package/data/src/FinnishBalanceSheetReportInvestment - balance-sheet-fi.tsv +52 -0
  28. package/data/src/FinnishBalanceSheetReportLite - balance-sheet-en.tsv +21 -0
  29. package/data/src/FinnishBalanceSheetReportLite - balance-sheet-fi.tsv +21 -0
  30. package/data/src/FinnishBalanceSheetReportLite - balance-sheet-lite-en.tsv +21 -0
  31. package/data/src/FinnishBalanceSheetReportLite - balance-sheet-lite-fi.tsv +21 -0
  32. package/data/src/FinnishIncomeStatementReport - income-statement-detailed-fi.tsv +118 -0
  33. package/data/src/FinnishIncomeStatementReport - income-statement-fi.tsv +45 -0
  34. package/data/src/FinnishIncomeStatementReportInvestment - income-statement-detailed-fi.tsv +96 -0
  35. package/data/src/FinnishIncomeStatementReportInvestment - income-statement-fi.tsv +39 -0
  36. package/data/src/FinnishIncomeStatementReportLite - income-statement-lite-en.tsv +23 -0
  37. package/data/src/FinnishIncomeStatementReportLite - income-statement-lite-fi.tsv +23 -0
  38. package/data/src/FinnishInvestmentCompany - fi-EUR.tsv +722 -0
  39. package/data/src/FinnishLimitedCompanyComplete - fi-EUR.tsv +1086 -0
  40. package/data/src/FinnishLimitedCompanyLite - en-EUR.tsv +97 -0
  41. package/data/src/FinnishLimitedCompanyLite - fi-EUR.tsv +99 -0
  42. package/data/src/Income Tree - Definitions.tsv +60 -0
  43. package/data/src/Tax Types - Definitions.tsv +11 -0
  44. package/package.json +51 -0
  45. package/src/CoinAPI/backend/index.ts +102 -0
  46. package/src/CoinbaseImport/backend/CoinbaseHandler.ts +35 -0
  47. package/src/CoinbaseImport/backend/index.ts +24 -0
  48. package/src/CoinbaseImport/backend/rules.json +64 -0
  49. package/src/DocumentCleaner/ui/index.tsx +165 -0
  50. package/src/Euro/ui/index.tsx +27 -0
  51. package/src/Finnish/ui/finnish.json +341 -0
  52. package/src/Finnish/ui/index.tsx +54 -0
  53. package/src/FinnishBalanceSheetReport/backend/balance-sheet-detailed-fi.tsv +215 -0
  54. package/src/FinnishBalanceSheetReport/backend/balance-sheet-fi.tsv +93 -0
  55. package/src/FinnishBalanceSheetReport/backend/index.ts +107 -0
  56. package/src/FinnishBalanceSheetReportInvestment/backend/balance-sheet-investment-detailed-fi.tsv +100 -0
  57. package/src/FinnishBalanceSheetReportInvestment/backend/balance-sheet-investment-fi.tsv +52 -0
  58. package/src/FinnishBalanceSheetReportInvestment/backend/index.ts +107 -0
  59. package/src/FinnishBalanceSheetReportLite/backend/balance-sheet-lite-en.tsv +21 -0
  60. package/src/FinnishBalanceSheetReportLite/backend/balance-sheet-lite-fi.tsv +21 -0
  61. package/src/FinnishBalanceSheetReportLite/backend/index.ts +121 -0
  62. package/src/FinnishIncomeStatementReport/backend/income-statement-detailed-fi.tsv +118 -0
  63. package/src/FinnishIncomeStatementReport/backend/income-statement-fi.tsv +45 -0
  64. package/src/FinnishIncomeStatementReport/backend/index.ts +212 -0
  65. package/src/FinnishIncomeStatementReportInvestment/backend/income-statement-detailed-fi.tsv +118 -0
  66. package/src/FinnishIncomeStatementReportInvestment/backend/income-statement-fi.tsv +45 -0
  67. package/src/FinnishIncomeStatementReportInvestment/backend/income-statement-investment-detailed-fi.tsv +96 -0
  68. package/src/FinnishIncomeStatementReportInvestment/backend/income-statement-investment-fi.tsv +39 -0
  69. package/src/FinnishIncomeStatementReportInvestment/backend/index.ts +212 -0
  70. package/src/FinnishIncomeStatementReportLite/backend/income-statement-lite-en.tsv +23 -0
  71. package/src/FinnishIncomeStatementReportLite/backend/income-statement-lite-fi.tsv +23 -0
  72. package/src/FinnishIncomeStatementReportLite/backend/index.ts +210 -0
  73. package/src/FinnishInvestmentCompany/backend/fi-EUR.tsv +722 -0
  74. package/src/FinnishInvestmentCompany/backend/index.ts +46 -0
  75. package/src/FinnishInvestmentCompany/ui/index.tsx +26 -0
  76. package/src/FinnishLimitedCompanyComplete/backend/fi-EUR.tsv +1086 -0
  77. package/src/FinnishLimitedCompanyComplete/backend/index.ts +46 -0
  78. package/src/FinnishLimitedCompanyComplete/ui/index.tsx +26 -0
  79. package/src/FinnishLimitedCompanyLite/backend/en-EUR.tsv +97 -0
  80. package/src/FinnishLimitedCompanyLite/backend/fi-EUR.tsv +99 -0
  81. package/src/FinnishLimitedCompanyLite/backend/index.ts +53 -0
  82. package/src/FinnishLimitedCompanyLite/ui/index.tsx +28 -0
  83. package/src/GitBackup/backend/index.ts +109 -0
  84. package/src/GitBackup/ui/index.tsx +127 -0
  85. package/src/IncomeAndExpenses/backend/assetCodes.json +126 -0
  86. package/src/IncomeAndExpenses/backend/expense.json +190 -0
  87. package/src/IncomeAndExpenses/backend/income.json +120 -0
  88. package/src/IncomeAndExpenses/backend/index.ts +354 -0
  89. package/src/IncomeAndExpenses/backend/taxTypes.json +12 -0
  90. package/src/JournalReport/backend/index.ts +157 -0
  91. package/src/KrakenImport/backend/KrakenHandler.ts +88 -0
  92. package/src/KrakenImport/backend/index.ts +24 -0
  93. package/src/KrakenImport/backend/rules.json +52 -0
  94. package/src/LedgerReport/backend/index.ts +161 -0
  95. package/src/LynxImport/backend/LynxHandler.ts +389 -0
  96. package/src/LynxImport/backend/index.ts +24 -0
  97. package/src/LynxImport/backend/rules.json +412 -0
  98. package/src/NordeaImport/backend/NordeaHandler.ts +44 -0
  99. package/src/NordeaImport/backend/index.ts +24 -0
  100. package/src/NordeaImport/backend/rules.json +4 -0
  101. package/src/NordnetImport/backend/NordnetHandler.ts +78 -0
  102. package/src/NordnetImport/backend/index.ts +24 -0
  103. package/src/NordnetImport/backend/rules.json +271 -0
  104. package/src/Rand/ui/index.tsx +27 -0
  105. package/src/RapidAPI/backend/index.ts +133 -0
  106. package/src/TITOImport/backend/TITOHandler.ts +268 -0
  107. package/src/TITOImport/backend/index.ts +24 -0
  108. package/src/TITOImport/backend/rules.json +4 -0
  109. package/src/TagEditor/ui/index.tsx +510 -0
  110. package/src/USDollar/ui/index.tsx +27 -0
  111. package/src/VAT/ui/index.tsx +572 -0
  112. package/src/VATFinland/backend/index.ts +22 -0
  113. package/src/VATFinland/backend/vat.json +23 -0
  114. package/tsconfig.json +13 -0
@@ -0,0 +1,24 @@
1
+ import { ImportPlugin } from '@dataplug/tasenor-common-node'
2
+ import { PluginCode, Version } from '@dataplug/tasenor-common'
3
+ import { TITOHandler } from './TITOHandler'
4
+
5
+ class TITOImportPlugin extends ImportPlugin {
6
+
7
+ constructor() {
8
+ super(new TITOHandler())
9
+
10
+ this.code = 'TITOImport' as PluginCode
11
+ this.title = 'Import for TITO'
12
+ this.version = '1.0.30' as Version
13
+ this.icon = '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M5 6.99h3V14h2V6.99h3L9 3zM14 10v7.01h-3L15 21l4-3.99h-3V10z"/></svg>'
14
+ this.releaseDate = '2023-07-03'
15
+ this.use = 'backend'
16
+ this.type = 'import'
17
+ this.description = 'Import plugin for importing transaction data from old fixed length field TITO format.'
18
+
19
+ this.languages = this.getLanguages()
20
+ }
21
+
22
+ }
23
+
24
+ export default TITOImportPlugin
@@ -0,0 +1,4 @@
1
+ {
2
+ "rules": [
3
+ ]
4
+ }
@@ -0,0 +1,510 @@
1
+ import React, { useState } from 'react'
2
+ import { Title, ToolPlugin, QuestionMarkInline, FileUploadData, FileUploader } from '@dataplug/tasenor-common-ui'
3
+ import { DatabaseModel, haveCursor, isTag, Tag, TagModel, TagType, Url } from '@dataplug/tasenor-common'
4
+ import { Trans, useTranslation } from 'react-i18next'
5
+ import { Box, Button, Card, CardContent, CardMedia, Grid, IconButton, Tab, Tabs, TextField, Typography } from '@mui/material'
6
+ import { Add, Cancel, CheckCircle } from '@mui/icons-material'
7
+ import { runInAction } from 'mobx'
8
+ import clone from 'clone'
9
+ import { green, red } from '@mui/material/colors'
10
+ import { observer } from 'mobx-react'
11
+
12
+ /**
13
+ * A single tag display and editor.
14
+ * @param props
15
+ * @returns
16
+ */
17
+ type TagCardProps = {
18
+ tag?: TagModel
19
+ existingTags: Set<Tag>
20
+ onSave?: (tag: Partial<TagModel>) => Promise<void>
21
+ onCreate?: (tag: Partial<TagModel>) => Promise<void>
22
+ onDelete?: (tag: Tag) => Promise<void>
23
+ }
24
+
25
+ const TagCard = observer((props: TagCardProps): JSX.Element => {
26
+
27
+ const { t } = useTranslation()
28
+ const cursor = haveCursor()
29
+
30
+ const [edit, setEdit] = useState(false)
31
+ const [changed, setChanged] = useState(false)
32
+ const [isNew, setIsNew] = useState(false)
33
+ const [isDeleting, setIsDeleting] = useState(false)
34
+
35
+ const { name, tag, order, picture, mime } = props.tag ? props.tag : { name: '', tag: '', order: 0, picture: '', mime: '' }
36
+
37
+ const [tagName, setTagName] = useState<string>(name)
38
+ const [tagTag, setTagTag] = useState<Tag>(tag as Tag)
39
+ const [tagPicture, setTagPicture] = useState<string>('')
40
+ const [tagMime, setTagMime] = useState(mime)
41
+
42
+ let url = props.tag && props.tag.url
43
+ if (tagPicture) {
44
+ url = `data:${tagMime};base64,${tagPicture}` as Url
45
+ } else if (props.tag && props.tag.picture) {
46
+ url = `data:image/png;base64,${props.tag.picture}` as Url
47
+ }
48
+ if (!url) {
49
+ url = QuestionMarkInline as Url
50
+ }
51
+
52
+ // Handle saving the tag.
53
+ const onSave = async (): Promise<void> => {
54
+ if (isNew) {
55
+ props.onCreate && await props.onCreate({ name: tagName, tag: tagTag, picture: tagPicture, mime: tagMime })
56
+ } else {
57
+ props.onSave && await props.onSave({ name: tagName, tag: tagTag, picture: tagPicture, mime: tagMime })
58
+ }
59
+ setEdit(false)
60
+ setTagName('')
61
+ setTagTag('' as Tag)
62
+ setTagPicture('')
63
+ setTagMime('')
64
+ }
65
+
66
+ // Handle deleting the tag.
67
+ const onDelete = async (): Promise<void> => {
68
+ props.onDelete && await props.onDelete(tag as Tag)
69
+ }
70
+
71
+ // Handle file upload.
72
+ const onUpload = (file: FileUploadData): void => {
73
+ setTagPicture(file.data)
74
+ setTagMime(file.type as TagType)
75
+ setChanged(true)
76
+ }
77
+
78
+ // Cancel editing.
79
+ const onCancel = (): void => {
80
+ setEdit(false)
81
+ setTagName('')
82
+ setTagTag('' as Tag)
83
+ setTagPicture('')
84
+ setTagMime('')
85
+ }
86
+
87
+ // Key shortcuts.
88
+ const onKeyUp = (key: string): void => {
89
+ if (key === 'Enter') {
90
+ if (validateName() === null && validateTag() === null) {
91
+ onSave()
92
+ }
93
+ }
94
+ if (key === 'Escape') {
95
+ onCancel()
96
+ }
97
+ }
98
+
99
+ // Validator for name.
100
+ const validateName = (): string | null => {
101
+ if (!changed) {
102
+ return null
103
+ }
104
+ return tagName ? null : t('Tag name is required')
105
+ }
106
+
107
+ // Validator for tag.
108
+ const validateTag = (): string | null => {
109
+ if (!changed) {
110
+ return null
111
+ }
112
+ if (!tagTag) {
113
+ return t('Tag code is required')
114
+ }
115
+ if ((!props.tag || tagTag !== props.tag.tag) && props.existingTags.has(tagTag as Tag)) {
116
+ return 'Tag already exists'
117
+ }
118
+ return isTag(tagTag as Tag) ? null : 'Invalid tag'
119
+ }
120
+
121
+ return (
122
+ <Card raised sx={{ display: 'flex', width: 450 }}>
123
+ <CardContent sx={{ flex: '1 0 auto' }}>
124
+ {
125
+ !edit && <>
126
+ <Typography component="div" variant="h5">
127
+ {name}
128
+ </Typography>
129
+ <Typography variant="subtitle1" color="text.secondary" component="div">
130
+ {tag} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
131
+ <Typography variant="subtitle2" component="span">
132
+ {order ? `#${order}` : ''}
133
+ </Typography>
134
+ </Typography>
135
+ {props.tag && !isDeleting &&
136
+ <>
137
+ <Button size="small" onClick={() => {
138
+ setIsNew(false)
139
+ setEdit(true)
140
+ setTagName(name)
141
+ setTagTag(tag as Tag)
142
+ setTagPicture(picture || '')
143
+ setTagMime(mime)
144
+ }}><Trans>Edit</Trans></Button>
145
+ <Button size="small" onClick={() => { setIsDeleting(true) }}><Trans>Delete</Trans></Button>
146
+ </>
147
+ }
148
+ {isDeleting &&
149
+ <>
150
+ <Trans>Are you sure?</Trans>
151
+ <Button size="small" onClick={() => { setIsDeleting(false) }}><Trans>No</Trans></Button>
152
+ <Button size="small" onClick={() => { setIsDeleting(false); onDelete() }}><Trans>Yes</Trans></Button>
153
+ </>
154
+ }
155
+ {!props.tag &&
156
+ <Button size="small" onClick={() => { setIsNew(true); setEdit(!edit) }}><Trans>Add New</Trans></Button>}
157
+ </>
158
+ }
159
+ {
160
+ edit && <>
161
+ <TextField
162
+ autoFocus
163
+ onFocus={() => cursor.disableHandler()}
164
+ onBlur={() => cursor.enableHandler()}
165
+ error={!!validateName()}
166
+ label={validateName()}
167
+ value={tagName}
168
+ onChange={(ev) => { setTagName(ev.target.value); setChanged(true) }}
169
+ onKeyUp={(ev) => onKeyUp(ev.key)}
170
+ />
171
+ <IconButton title={t('Cancel')} onClick={onCancel} sx={{ color: red[800] }}><Cancel/></IconButton>
172
+ <br />
173
+ <TextField
174
+ onFocus={() => cursor.disableHandler()}
175
+ onBlur={() => cursor.enableHandler()}
176
+ value={tagTag}
177
+ error={!!validateTag()}
178
+ label={validateTag()}
179
+ onChange={(ev) => { setTagTag(ev.target.value as Tag); setChanged(true) }}
180
+ onKeyUp={(ev) => onKeyUp(ev.key)}
181
+ />
182
+ <IconButton title={t('Save')} onClick={onSave} sx={{ color: green[800] }}><CheckCircle/></IconButton>
183
+ </>
184
+ }
185
+ </CardContent>
186
+ {
187
+ edit &&
188
+ <Box
189
+ sx={{
190
+ width: 150,
191
+ height: 150,
192
+ borderLeft: 1,
193
+ opacity: 0.5,
194
+ borderColor: 'divider',
195
+ backgroundImage: `url(${url})`,
196
+ backgroundSize: 'cover',
197
+ backgroundRepeat: 'no-repeat'
198
+ }}
199
+ >
200
+ <FileUploader text="" color="info" iconSize={130} onUpload={(files) => onUpload(files[0])} />
201
+ </Box>
202
+ }
203
+ {
204
+ !edit &&
205
+ <CardMedia
206
+ component="img"
207
+ sx={{ width: 150, height: 150, borderLeft: 1, borderColor: 'divider' }}
208
+ image={url}
209
+ alt={name}
210
+ />
211
+ }
212
+ </Card>
213
+ )
214
+ })
215
+
216
+ /**
217
+ * A panel providing editors for all tags in the group.
218
+ */
219
+ type GroupPanelProps = {
220
+ group: TagType
221
+ tags: TagModel[]
222
+ onSave: (tag: TagModel) => Promise<void>
223
+ onDelete: (tag: TagModel) => Promise<void>
224
+ onCreate: (tag: Partial<TagModel>) => Promise<void>
225
+ existingTags: Set<Tag>
226
+ }
227
+
228
+ const GroupPanel = observer((props: GroupPanelProps): JSX.Element => {
229
+
230
+ // Handler for saving a tag
231
+ const onSave = async (tag: TagModel, changes: Partial<TagModel>): Promise<void> => {
232
+ runInAction(() => {
233
+ tag.name = changes.name || ''
234
+ tag.tag = changes.tag as Tag
235
+ tag.picture = changes.picture || null
236
+ tag.mime = changes.mime || null
237
+ tag.type = props.group
238
+ })
239
+ await props.onSave(tag)
240
+ }
241
+
242
+ // Handler for creating a tag
243
+ const onCreate = async (changes: Partial<TagModel>): Promise<void> => {
244
+ await props.onCreate(changes)
245
+ }
246
+
247
+ // Handler for deleting a tag
248
+ const onDelete = async (tag: TagModel): Promise<void> => {
249
+ await props.onDelete(tag)
250
+ }
251
+
252
+ return (
253
+ <Grid container spacing={2}>
254
+ {props.tags.map((tag, idx) => (
255
+ <Grid item key={idx}>
256
+ <TagCard tag={tag} existingTags={props.existingTags} onSave={(changes) => onSave(tag, changes)} onDelete={() => onDelete(tag)} />
257
+ </Grid>
258
+ ))}
259
+ <Grid item key={-1}><TagCard existingTags={props.existingTags} onCreate={(tag) => onCreate(tag)} /></Grid>
260
+ </Grid>
261
+ )
262
+ })
263
+
264
+ /**
265
+ * An editor for tags collection in a database.
266
+ */
267
+ export type TagsEditorProps = {
268
+ database: DatabaseModel
269
+ onSave: (tag: TagModel) => Promise<void>
270
+ onDelete: (tag: TagModel) => Promise<void>
271
+ onCreate: (tag: Partial<TagModel>) => Promise<TagModel>
272
+ }
273
+
274
+ const TagsEditor = observer((props: TagsEditorProps): JSX.Element => {
275
+
276
+ const cursor = haveCursor()
277
+ const { t } = useTranslation()
278
+ const tags = props.database.tagsByTag
279
+ const [tab, setTab] = useState(0)
280
+ const [addingTab, setAddingTab] = useState<TagType>('' as TagType)
281
+
282
+ // Sort types and tags by their order number found.
283
+ const typeRange: Record<TagType, { min: number, max: number }> = {}
284
+ const byGroups: Record<TagType, TagModel[]> = {}
285
+ const orderNumbers: Set<number> = new Set()
286
+ const existingTags: Set<Tag> = new Set()
287
+
288
+ Object.values(tags).forEach(tag => {
289
+ if (!tag.tag || !tag.type) {
290
+ return
291
+ }
292
+ if (typeRange[tag.type] === undefined) {
293
+ typeRange[tag.type] = { min: tag.order, max: tag.order }
294
+ } else {
295
+ if (tag.order < typeRange[tag.type].min) {
296
+ typeRange[tag.type].min = tag.order
297
+ } else if (tag.order > typeRange[tag.type].max) {
298
+ typeRange[tag.type].max = tag.order
299
+ }
300
+ }
301
+ orderNumbers.add(tag.order)
302
+ existingTags.add(tag.tag)
303
+ byGroups[tag.type] = byGroups[tag.type] || []
304
+ byGroups[tag.type].push(tag)
305
+ })
306
+
307
+ // Sort each group and types and make types as a state.
308
+ const sortedTypes: TagType[] = Object.keys(typeRange).sort((a: TagType, b: TagType) => typeRange[a].min - typeRange[b].min) as TagType[]
309
+ Object.keys(byGroups).forEach(name => {
310
+ byGroups[name] = byGroups[name].sort((a, b) => a.order - b.order)
311
+ })
312
+ const [types, setTypes] = useState<TagType[]>(sortedTypes)
313
+ const [groups, setGroups] = useState<Record<string, TagModel[]>>(byGroups)
314
+
315
+ // Handler for new type creation.
316
+ const onCreateTab = (): void => {
317
+ if (!addingTab) {
318
+ setTab(types.length - 1)
319
+ return
320
+ }
321
+ setTypes(types.concat(addingTab))
322
+ setGroups({ ...groups, [addingTab]: [] })
323
+ setAddingTab('' as TagType)
324
+ }
325
+
326
+ // Handler for saving a tag
327
+ const onSave = async (tag: TagModel): Promise<void> => {
328
+ await props.onSave(tag)
329
+ }
330
+
331
+ // Handler for deleting a tag
332
+ const onDelete = async (tag: TagModel): Promise<void> => {
333
+ await props.onDelete(tag)
334
+ const newGroups = clone(groups)
335
+ newGroups[tag.type] = newGroups[tag.type].filter(t => t.tag !== tag.tag)
336
+ setGroups(newGroups)
337
+ }
338
+
339
+ // Handler for creating a tag
340
+ const onCreate = async (tag: Partial<TagModel>): Promise<void> => {
341
+ const type = types[tab]
342
+ let order = Math.max(...groups[type].map(t => t.order)) + 1
343
+ // Set number to new group.
344
+ if (groups[type].length === 0) {
345
+ const idx = types.indexOf(type)
346
+ order = (idx + 1) * 1000 + 1
347
+ }
348
+ // Ensure non-overlapping.
349
+ while (orderNumbers.has(order)) {
350
+ order++
351
+ }
352
+ if (tag.tag) {
353
+ const model = await props.onCreate({
354
+ tag: tag.tag as Tag,
355
+ name: tag.name,
356
+ picture: tag.picture,
357
+ mime: tag.mime,
358
+ type,
359
+ order
360
+ })
361
+ setGroups({ ...groups, [type]: groups[type].concat([model]) })
362
+ }
363
+ }
364
+
365
+ // Handler for tab editor key presses.
366
+ const onKeyUp = (key: string): void => {
367
+ if (key === 'Enter') {
368
+ onCreateTab()
369
+ }
370
+ if (key === 'Escape') {
371
+ setAddingTab('' as TagType)
372
+ if (tab) setTab(tab - 1)
373
+ }
374
+ }
375
+
376
+ // TODO: Editing type name by clicking already selected tab.
377
+
378
+ return (
379
+ <Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
380
+ <Tabs value={tab} onChange={(event, newValue) => setTab(newValue)}>
381
+ {
382
+ types.map((type, idx) => (
383
+ <Tab key={idx} label={type} />
384
+ ))
385
+ }
386
+ <Tab
387
+ key={-1}
388
+ disableRipple
389
+ icon={types[tab] && <Add />}
390
+ iconPosition="start"
391
+ label={types[tab]
392
+ ? t('Add Tag Type')
393
+ : <TextField
394
+ autoFocus
395
+ onFocus={() => cursor.disableHandler()}
396
+ onBlur={() => cursor.enableHandler()}
397
+ value={addingTab}
398
+ onChange={ev => setAddingTab(ev.target.value as TagType)}
399
+ onKeyUp={(ev) => onKeyUp(ev.key)}
400
+ />
401
+ }
402
+ />
403
+ </Tabs>
404
+ {types[tab] && (
405
+ <>
406
+ <GroupPanel
407
+ group={types[tab] as TagType}
408
+ tags={groups[types[tab]]}
409
+ existingTags={existingTags}
410
+ onSave={tag => onSave(tag)}
411
+ onCreate={tag => onCreate(tag)}
412
+ onDelete={tag => onDelete(tag)}
413
+ />
414
+ </>
415
+ )}
416
+ {types.length === 0 && (
417
+ <Typography variant="h6" sx={{ padding: 1 }}>
418
+ <Trans>You don&apos;t have any tag groups yet. Please create the firt one by naming it above.</Trans>
419
+ </Typography>
420
+ )}
421
+ </Box>
422
+ )
423
+ })
424
+
425
+ class TagEditor extends ToolPlugin {
426
+
427
+ static code = 'TagEditor'
428
+ static title = 'Tag Editor'
429
+ static version = '1.0.33'
430
+ static icon = '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M16 7H5v10h11l3.55-5z" opacity=".3"/><path d="M17.63 5.84C17.27 5.33 16.67 5 16 5L5 5.01C3.9 5.01 3 5.9 3 7v10c0 1.1.9 1.99 2 1.99L16 19c.67 0 1.27-.33 1.63-.84L22 12l-4.37-6.16zM16 17H5V7h11l3.55 5L16 17z"/></svg>'
431
+ static releaseDate = '2022-03-11'
432
+ static use = 'ui'
433
+ static type = 'tool'
434
+ static description = 'Allows creating, deleting and editing tags and tag groups. Tags are useful for example for filtering transaction lists and combining reports by customizable groups.'
435
+
436
+ constructor() {
437
+ super()
438
+ this.languages = {
439
+ fi: {
440
+ 'Add Tag Type': 'Lisää uusi ryhmä',
441
+ 'Tag Editor': 'Tägieditori',
442
+ 'Tag name is required': 'Tägillä pitää olla nimi',
443
+ 'Tag code is required': 'Tägillä pitää olla koodi',
444
+ 'Tag already exists': 'Tägi on jo olemassa',
445
+ 'Invalid tag': 'Virheellinen tägi',
446
+ 'Edit Tags in the Database': 'Muokkaa tietokannan tägejä',
447
+ 'A tag is a special notation that can be added to the beginning of the transaction description.': 'Tägi on erityisnotaatio, jota voidaan käyttää tapahtuman kuvauksen alussa.',
448
+ 'A short code, e.g. XYZ an be added in the beginning of the description as [XYZ].': 'Esimerkiksi tägi, jonka koodi on XYZ, voidaan lisätä kuvauksen alkuun kirjoittamalla [XYZ].',
449
+ 'Once added, the tag can be used in transaction list as a filter by showing only the transactions with the give tag.': 'Kun tapahtumaan on lisätty tägi, sitä voidaan käyttää tapahtumalistan filtteröintiin näyttämään vain kyseise tägin sisältämät rivit.',
450
+ 'Tag grouping is used to keep tags of the specific dimension in the single group.': 'Tägien ryhmittelyä käytetään tietyn dimension tägien yhdistämiseen saman nimen alle.',
451
+ 'It is usable in some reports for example.': 'Ryhmittelyä voidaan hyödyntää esimerkiksi joissakin raporteissa.',
452
+ 'You don&apos;t have any tag groups yet. Please create the firt one by naming it above.': 'Yhtään tägiryhmää ei ole vielä olemassa. Ole hyvä ja luo ensimmäinen antamalla sille nimi yllä.',
453
+ }
454
+ }
455
+ }
456
+
457
+ toolMenu() {
458
+ return [{ title: 'Tag Editor', disabled: !this.store || !this.store.db }]
459
+ }
460
+
461
+ toolTitle() {
462
+ return 'Tag Editor'
463
+ }
464
+
465
+ toolTopPanel() {
466
+ return <Typography sx={{ padding: 1 }}>
467
+ <Trans>A tag is a special notation that can be added to the beginning of the transaction description.</Trans>
468
+ <Trans>A short code, e.g. XYZ an be added in the beginning of the description as [XYZ].</Trans>
469
+ <Trans>Once added, the tag can be used in transaction list as a filter by showing only the transactions with the give tag.</Trans>
470
+ <Trans>Tag grouping is used to keep tags of the specific dimension in the single group.</Trans>
471
+ <Trans>It is usable in some reports for example.</Trans>
472
+ </Typography>
473
+ }
474
+
475
+ toolMainPanel(): JSX.Element {
476
+ const { store } = this
477
+
478
+ if (!store || !store.database) {
479
+ return <></>
480
+ }
481
+
482
+ const onSave = async (tag: TagModel) => {
483
+ await tag.save()
484
+ }
485
+
486
+ const onDelete = async (tag: TagModel) => {
487
+ await tag.delete()
488
+ }
489
+
490
+ const onCreate = async (tag: Partial<TagModel>): Promise<TagModel> => {
491
+ const model = await store.database.addTag(tag)
492
+ await model.save()
493
+ return model
494
+ }
495
+
496
+ return (
497
+ <Box sx={{ padding: 1 }}>
498
+ <Title><Trans>Edit Tags in the Database</Trans></Title>
499
+ <TagsEditor
500
+ database={store.database}
501
+ onDelete={onDelete}
502
+ onSave={onSave}
503
+ onCreate={onCreate}
504
+ />
505
+ </Box>
506
+ )
507
+ }
508
+ }
509
+
510
+ export default TagEditor
@@ -0,0 +1,27 @@
1
+ import { CurrencyPlugin } from '@dataplug/tasenor-common-ui'
2
+
3
+ class USDollar extends CurrencyPlugin {
4
+
5
+ static code = 'USDollar'
6
+ static title = 'Currency US Dollar'
7
+ static version = '1.0.9'
8
+ static icon = '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M11.8 10.9c-2.27-.59-3-1.2-3-2.15 0-1.09 1.01-1.85 2.7-1.85 1.78 0 2.44.85 2.5 2.1h2.21c-.07-1.72-1.12-3.3-3.21-3.81V3h-3v2.16c-1.94.42-3.5 1.68-3.5 3.61 0 2.31 1.91 3.46 4.7 4.13 2.5.6 3 1.48 3 2.41 0 .69-.49 1.79-2.7 1.79-2.06 0-2.87-.92-2.98-2.1h-2.2c.12 2.19 1.76 3.42 3.68 3.83V21h3v-2.15c1.95-.37 3.5-1.5 3.5-3.55 0-2.84-2.43-3.81-4.7-4.4z"/></svg>'
9
+ static releaseDate = '2022-04-08'
10
+ static use = 'ui'
11
+ static type = 'currency'
12
+ static description = 'Support for US dollar currency.'
13
+
14
+ getCurrencySymbol() {
15
+ return '$'
16
+ }
17
+
18
+ getCurrencyCode() {
19
+ return 'USD'
20
+ }
21
+
22
+ money2str(cents) {
23
+ return this.makeMoney(cents, 100, 2, '$ ', ',', '.', '')
24
+ }
25
+ }
26
+
27
+ export default USDollar