add my cumulated balance in nav footer

Signed-off-by: Julien Veyssier <julien-nc@posteo.net>
This commit is contained in:
Julien Veyssier
2024-10-20 23:27:46 +02:00
parent 59fae379d9
commit bab0a6f771
8 changed files with 78 additions and 42 deletions

View File

@@ -78,9 +78,6 @@ class PageController extends Controller {
$this->initialStateService->provideInitialState('pathBillId', $billId ?? 0);
$this->eventDispatcher->dispatchTyped(new RenderReferenceEvent());
$response = new TemplateResponse('cospend', 'main');
$csp = new ContentSecurityPolicy();
$csp->allowEvalScript();
$response->setContentSecurityPolicy($csp);
return $response;
}

40
package-lock.json generated
View File

@@ -4888,9 +4888,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "20.16.12",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.12.tgz",
"integrity": "sha512-LfPFB0zOeCeCNQV3i+67rcoVvoN5n0NVuR2vLG0O5ySQMgchuZlC4lgz546ZOJyDtj5KIgOxy+lacOimfqZAIA==",
"version": "20.16.13",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.13.tgz",
"integrity": "sha512-GjQ7im10B0labo8ZGXDGROUl9k0BNyDgzfGpb4g/cl+4yYDWVKcozANF4FGr4/p0O/rAkQClM6Wiwkije++1Tg==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.19.2"
@@ -7337,9 +7337,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.40",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.40.tgz",
"integrity": "sha512-LYm78o6if4zTasnYclgQzxEcgMoIcybWOhkATWepN95uwVVWV0/IW10v+2sIeHE+bIYWipLneTftVyQm45UY7g==",
"version": "1.5.41",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.41.tgz",
"integrity": "sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ==",
"license": "ISC"
},
"node_modules/elliptic": {
@@ -8152,9 +8152,9 @@
}
},
"node_modules/eslint-plugin-vue": {
"version": "9.29.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.29.0.tgz",
"integrity": "sha512-hamyjrBhNH6Li6R1h1VF9KHfshJlKgKEg3ARbGTn72CMNDSMhWbgC7NdkRDEh25AFW+4SDATzyNM+3gWuZii8g==",
"version": "9.29.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.29.1.tgz",
"integrity": "sha512-MH/MbVae4HV/tM8gKAVWMPJbYgW04CK7SuzYRrlNERpxbO0P3+Zdsa2oAcFBW6xNu7W6lIkGOsFAMCRTYmrlWQ==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -13730,9 +13730,9 @@
}
},
"node_modules/sass": {
"version": "1.80.1",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.80.1.tgz",
"integrity": "sha512-9lBwDZ7j3y/1DKj5Ec249EVGo5CVpwnzIyIj+cqlCjKkApLnzsJ/l9SnV4YnORvW9dQwQN+gQvh/mFZ8CnDs7Q==",
"version": "1.80.3",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.80.3.tgz",
"integrity": "sha512-ptDWyVmDMVielpz/oWy3YP3nfs7LpJTHIJZboMVs8GEC9eUmtZTZhMHlTW98wY4aEorDfjN38+Wr/XjskFWcfA==",
"license": "MIT",
"dependencies": {
"@parcel/watcher": "^2.4.1",
@@ -14601,9 +14601,9 @@
}
},
"node_modules/stylelint-scss": {
"version": "6.8.0",
"resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-6.8.0.tgz",
"integrity": "sha512-6gjsCZ30UUF6ivjZB2Z+1lb6k0+JFa1uR2MgGbYu76xRjEfvNTpSS1nQim1Gom1ijFF9GzauOiq1Kr7zKptQOw==",
"version": "6.8.1",
"resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-6.8.1.tgz",
"integrity": "sha512-al+5eRb72bKrFyVAY+CLWKUMX+k+wsDCgyooSfhISJA2exqnJq1PX1iIIpdrvhu3GtJgNJZl9/BIW6EVSMCxdg==",
"dev": true,
"license": "MIT",
"peer": true,
@@ -14611,7 +14611,7 @@
"css-tree": "^3.0.0",
"is-plain-object": "^5.0.0",
"known-css-properties": "^0.34.0",
"mdn-data": "^2.0.30",
"mdn-data": "^2.11.1",
"postcss-media-query-parser": "^0.2.3",
"postcss-resolve-nested-selector": "^0.1.6",
"postcss-selector-parser": "^6.1.2",
@@ -14624,6 +14624,14 @@
"stylelint": "^16.0.2"
}
},
"node_modules/stylelint-scss/node_modules/mdn-data": {
"version": "2.11.1",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.11.1.tgz",
"integrity": "sha512-Hdx3wmyqPFrhd6YHVuSkUK2eIGAcxR0xlndcgZqjA68yMJTbfXrjJwbgsBOsNjI7LnBIVUQnmyMVSdi/ob0GpQ==",
"dev": true,
"license": "CC0-1.0",
"peer": true
},
"node_modules/stylelint/node_modules/balanced-match": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz",

View File

@@ -15,7 +15,7 @@
{{ activity.subject }}
</span>
<span
v-tooltip.top="{ content: formattedTime }"
:title="formattedTime"
class="time">
{{ relativeTime }}
</span>

View File

@@ -67,6 +67,17 @@
<PendingInvitationsModal v-if="!pageIsPublic && showPendingInvitations"
:invitations="pendingInvitations"
@close="showPendingInvitations = false" />
<NcAppNavigationItem v-if="!pageIsPublic && showMyBalance && myBalance !== null"
:name="t('cospend', 'My balance')">
<template #icon>
<ColoredAvatar :user="currentUserId" />
</template>
<template #counter>
<NcCounterBubble>
<span :class="balanceClass">{{ myBalance }}</span>
</NcCounterBubble>
</template>
</NcAppNavigationItem>
<NcAppNavigationItem v-if="!pageIsPublic && pendingInvitations.length > 0"
:name="t('cospend', 'Pending share invitations')"
@click="showPendingInvitations = true">
@@ -128,6 +139,7 @@ import AppNavigationProjectItem from './AppNavigationProjectItem.vue'
import NewProjectModal from './NewProjectModal.vue'
import PendingInvitationsModal from './PendingInvitationsModal.vue'
import AppNavigationUnreachableProjectItem from './AppNavigationUnreachableProjectItem.vue'
import ColoredAvatar from './avatar/ColoredAvatar.vue'
import cospend from '../state.js'
import * as constants from '../constants.js'
@@ -135,10 +147,12 @@ import { strcmp, importCospendProject, importSWProject } from '../utils.js'
import { emit } from '@nextcloud/event-bus'
import { showSuccess } from '@nextcloud/dialogs'
import { getCurrentUser } from '@nextcloud/auth'
export default {
name: 'CospendNavigation',
components: {
ColoredAvatar,
AppNavigationUnreachableProjectItem,
PendingInvitationsModal,
NewProjectModal,
@@ -202,9 +216,29 @@ export default {
showArchivedProjects: false,
showPendingInvitations: false,
projectFilterQuery: '',
currentUserId: getCurrentUser()?.uid,
}
},
computed: {
showMyBalance() {
return cospend.showMyBalance
},
myBalance() {
return Object.values(this.projects)
.filter(p => p.archived_ts === null)
.map(p => {
const me = p.members.find(m => m.userid === this.currentUserId)
return me ? me.balance : null
})
.filter(b => b !== null)
.reduce((acc, balance) => acc + balance, 0)
},
balanceClass() {
return {
balancePositive: this.myBalance >= 0.01,
balanceNegative: this.myBalance <= -0.01,
}
},
filteredProjectIds() {
const projectIds = this.showArchivedProjects ? this.archivedProjectIds : this.nonArchivedProjectIds
return this.projectFilterQuery === ''

View File

@@ -182,9 +182,14 @@
</h3>
<NcCheckboxRadioSwitch
:checked.sync="useTime"
@update:checked="onUseTimeChange">
@update:checked="onCheckboxChange($event, 'useTime')">
{{ t('cospend', 'Use time in dates') }}
</NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch
:checked.sync="showMyBalance"
@update:checked="onCheckboxChange($event, 'showMyBalance')">
{{ t('cospend', 'Show my cumulated balance') }}
</NcCheckboxRadioSwitch>
</NcAppSettingsSection>
</NcAppSettingsDialog>
</div>
@@ -227,6 +232,7 @@ export default {
memberOrder: cospend.memberOrder || 'name',
maxPrecision: cospend.maxPrecision || 2,
useTime: cospend.useTime ?? true,
showMyBalance: cospend.showMyBalance ?? false,
importingProject: false,
importingSWProject: false,
cospendVersion: OC.getCapabilities()?.cospend?.version || '??',
@@ -280,9 +286,9 @@ export default {
cospend.maxPrecision = this.maxPrecision
this.$emit('update-max-precision')
},
onUseTimeChange(checked) {
emit('save-option', { key: 'useTime', value: checked ? '1' : '0' })
cospend.useTime = checked
onCheckboxChange(checked, key) {
emit('save-option', { key, value: checked ? '1' : '0' })
cospend[key] = checked
},
onImportClick() {
importCospendProject(() => {

View File

@@ -388,17 +388,15 @@
</td>
<td v-for="mid in stats.allMemberIds"
:key="value.memberid + '-' + mid"
v-tooltip.top="{
content: value.memberid === 0
? t('cospend', 'Total owed by {name}', { name: myGetSmartMemberName(mid) })
: myGetSmartMemberName(value.memberid) + ' ' + myGetSmartMemberName(mid)
}"
:title="value.memberid === 0
? t('cospend', 'Total owed by {name}', { name: myGetSmartMemberName(mid) })
: myGetSmartMemberName(value.memberid) + ' → ' + myGetSmartMemberName(mid)"
:style="'border: 2px solid ' + (value.memberid === 0 ? 'lightgrey' : '#' + myGetMemberColor(value.memberid)) + ';'">
{{ value[mid].toFixed(2) }}
{{ selectedCurrencyName }}
</td>
<td v-if="value.memberid !== 0"
v-tooltip.top="{ content: t('cospend', 'Total paid by {name}', { name: myGetSmartMemberName(value.memberid) }) }"
:title="t('cospend', 'Total paid by {name}', { name: myGetSmartMemberName(value.memberid) })"
style="border: 2px solid lightgrey;">
{{ value.total.toFixed(2) }}
{{ selectedCurrencyName }}

View File

@@ -14,10 +14,7 @@ import './bootstrap.js'
import App from './App.vue'
import { showError } from '@nextcloud/dialogs'
import '@nextcloud/dialogs/style.css'
// import { getRequestToken } from '@nextcloud/auth'
// import { generateFilePath } from '@nextcloud/router'
import { loadState } from '@nextcloud/initial-state'
import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip.js'
import vueAwesomeCountdown from 'vue-awesome-countdown'
import VueClipboard from 'vue-clipboard2'
import SmartTable from 'vuejs-smart-table'
@@ -29,7 +26,6 @@ import '../css/cospend.scss'
Vue.use(vueAwesomeCountdown, 'vac')
Vue.use(VueClipboard)
Vue.use(SmartTable)
Vue.directive('tooltip', Tooltip)
function restoreOptions() {
network.getOptionValues().then((response) => {
@@ -47,16 +43,12 @@ function getOptionValuesSuccess(response) {
for (const k in optionsValues) {
if (k === 'selectedProject') {
cospend.restoredCurrentProjectId = optionsValues[k]
} else if (k === 'outputDirectory') {
cospend.outputDirectory = optionsValues[k]
} else if (k === 'sortOrder') {
cospend.sortOrder = optionsValues[k]
} else if (k === 'memberOrder') {
cospend.memberOrder = optionsValues[k]
} else if (k === 'maxPrecision') {
cospend.maxPrecision = optionsValues[k]
} else if (k === 'useTime') {
cospend.useTime = optionsValues[k] !== '0'
} else if (k === 'showMyBalance') {
cospend.showMyBalance = optionsValues[k] !== '0'
} else {
cospend[k] = optionsValues[k]
}
}
}

View File

@@ -22,6 +22,7 @@ const cospend = {
memberOrder: 'name',
useTime: true,
activity_enabled: false,
showMyBalance: false,
}
export default cospend