support showing geolocation on map
This commit is contained in:
@@ -137,6 +137,12 @@ func startWebServer(c *cli.Context) error {
|
|||||||
|
|
||||||
router.GET("/healthz.json", bindApi(api.Healths.HealthStatusHandler))
|
router.GET("/healthz.json", bindApi(api.Healths.HealthStatusHandler))
|
||||||
|
|
||||||
|
proxyRoute := router.Group("/proxy")
|
||||||
|
proxyRoute.Use(bindMiddleware(middlewares.JWTAuthorizationByQueryString))
|
||||||
|
{
|
||||||
|
proxyRoute.GET("/openstreetmap/tile/:zoomLevel/:coordinateX/:fileName", bindProxy(api.MapImages.OpenStreetMapTileImageProxyHandler))
|
||||||
|
}
|
||||||
|
|
||||||
apiRoute := router.Group("/api")
|
apiRoute := router.Group("/api")
|
||||||
|
|
||||||
apiRoute.Use(bindMiddleware(middlewares.RequestId(config)))
|
apiRoute.Use(bindMiddleware(middlewares.RequestId(config)))
|
||||||
@@ -288,3 +294,16 @@ func bindCsv(fn core.DataHandlerFunc) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func bindProxy(fn core.ProxyHandlerFunc) gin.HandlerFunc {
|
||||||
|
return func(ginCtx *gin.Context) {
|
||||||
|
c := core.WrapContext(ginCtx)
|
||||||
|
proxy, err := fn(c)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
utils.PrintDataErrorResult(c, "text/text", err)
|
||||||
|
} else {
|
||||||
|
proxy.ServeHTTP(c.Writer, c.Request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Generated
+6
@@ -19,6 +19,7 @@
|
|||||||
"framework7-icons": "^5.0.5",
|
"framework7-icons": "^5.0.5",
|
||||||
"framework7-vue": "^8.0.3",
|
"framework7-vue": "^8.0.3",
|
||||||
"js-cookie": "^3.0.1",
|
"js-cookie": "^3.0.1",
|
||||||
|
"leaflet": "^1.9.3",
|
||||||
"line-awesome": "^1.3.0",
|
"line-awesome": "^1.3.0",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"moment-timezone": "^0.5.43",
|
"moment-timezone": "^0.5.43",
|
||||||
@@ -5200,6 +5201,11 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/leaflet": {
|
||||||
|
"version": "1.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.3.tgz",
|
||||||
|
"integrity": "sha512-iB2cR9vAkDOu5l3HAay2obcUHZ7xwUBBjph8+PGtmW/2lYhbLizWtG7nTeYht36WfOslixQF9D/uSIzhZgGMfQ=="
|
||||||
|
},
|
||||||
"node_modules/leven": {
|
"node_modules/leven": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
"framework7-icons": "^5.0.5",
|
"framework7-icons": "^5.0.5",
|
||||||
"framework7-vue": "^8.0.3",
|
"framework7-vue": "^8.0.3",
|
||||||
"js-cookie": "^3.0.1",
|
"js-cookie": "^3.0.1",
|
||||||
|
"leaflet": "^1.9.3",
|
||||||
"line-awesome": "^1.3.0",
|
"line-awesome": "^1.3.0",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"moment-timezone": "^0.5.43",
|
"moment-timezone": "^0.5.43",
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
|
)
|
||||||
|
|
||||||
|
const openStreetMapTileImageUrlFormat = "https://tile.openstreetmap.org/%s/%s/%s" // https://tile.openstreetmap.org/{z}/{x}/{y}.png
|
||||||
|
|
||||||
|
// MapImageProxy represents map image proxy
|
||||||
|
type MapImageProxy struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize a map image proxy singleton instance
|
||||||
|
var (
|
||||||
|
MapImages = &MapImageProxy{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// OpenStreetMapTileImageProxyHandler returns open street map tile image
|
||||||
|
func (p *MapImageProxy) OpenStreetMapTileImageProxyHandler(c *core.Context) (*httputil.ReverseProxy, *errs.Error) {
|
||||||
|
director := func(req *http.Request) {
|
||||||
|
zoomLevel := c.Param("zoomLevel")
|
||||||
|
coordinateX := c.Param("coordinateX")
|
||||||
|
fileName := c.Param("fileName")
|
||||||
|
|
||||||
|
imageRawUrl := fmt.Sprintf(openStreetMapTileImageUrlFormat, zoomLevel, coordinateX, fileName)
|
||||||
|
imageUrl, _ := url.Parse(imageRawUrl)
|
||||||
|
|
||||||
|
req.Header.Del("Authorization")
|
||||||
|
req.URL = imageUrl
|
||||||
|
req.RequestURI = req.URL.RequestURI()
|
||||||
|
req.Host = imageUrl.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
return &httputil.ReverseProxy{Director: director}, nil
|
||||||
|
}
|
||||||
+8
-1
@@ -1,6 +1,10 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
import "github.com/mayswind/ezbookkeeping/pkg/errs"
|
import (
|
||||||
|
"net/http/httputil"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
|
)
|
||||||
|
|
||||||
// MiddlewareHandlerFunc represents the middleware handler function
|
// MiddlewareHandlerFunc represents the middleware handler function
|
||||||
type MiddlewareHandlerFunc func(*Context)
|
type MiddlewareHandlerFunc func(*Context)
|
||||||
@@ -10,3 +14,6 @@ type ApiHandlerFunc func(*Context) (interface{}, *errs.Error)
|
|||||||
|
|
||||||
// DataHandlerFunc represents the handler function that returns byte array
|
// DataHandlerFunc represents the handler function that returns byte array
|
||||||
type DataHandlerFunc func(*Context) ([]byte, string, *errs.Error)
|
type DataHandlerFunc func(*Context) ([]byte, string, *errs.Error)
|
||||||
|
|
||||||
|
// ProxyHandlerFunc represents the reverse proxy handler function
|
||||||
|
type ProxyHandlerFunc func(*Context) (*httputil.ReverseProxy, *errs.Error)
|
||||||
|
|||||||
@@ -37,6 +37,21 @@ func JWTAuthorization(c *core.Context) {
|
|||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JWTAuthorizationByQueryString verifies whether current request is valid by jwt token
|
||||||
|
func JWTAuthorizationByQueryString(c *core.Context) {
|
||||||
|
token, exists := c.GetQuery(tokenQueryStringParam)
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
log.WarnfWithRequestId(c, "[authorization.JWTAuthorizationByQueryString] no token provided")
|
||||||
|
utils.PrintJsonErrorResult(c, errs.ErrUnauthorizedAccess)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Request.Header.Set("Authorization", token)
|
||||||
|
|
||||||
|
JWTAuthorization(c)
|
||||||
|
}
|
||||||
|
|
||||||
// JWTTwoFactorAuthorization verifies whether current request is valid by 2fa passcode
|
// JWTTwoFactorAuthorization verifies whether current request is valid by 2fa passcode
|
||||||
func JWTTwoFactorAuthorization(c *core.Context) {
|
func JWTTwoFactorAuthorization(c *core.Context) {
|
||||||
claims, err := getTokenClaims(c)
|
claims, err := getTokenClaims(c)
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
<template>
|
||||||
|
<f7-sheet swipe-to-close swipe-handler=".swipe-handler" style="height:auto"
|
||||||
|
:opened="show" @sheet:open="onSheetOpen" @sheet:closed="onSheetClosed">
|
||||||
|
<f7-toolbar>
|
||||||
|
<div class="swipe-handler"></div>
|
||||||
|
<div class="left"></div>
|
||||||
|
<div class="right">
|
||||||
|
<f7-link :text="$t('Done')" @click="save"></f7-link>
|
||||||
|
</div>
|
||||||
|
</f7-toolbar>
|
||||||
|
<f7-page-content class="no-margin-vertical no-padding-vertical">
|
||||||
|
<div ref="map" style="height: 400px; width: 100%"></div>
|
||||||
|
</f7-page-content>
|
||||||
|
</f7-sheet>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: [
|
||||||
|
'modelValue',
|
||||||
|
'show'
|
||||||
|
],
|
||||||
|
emits: [
|
||||||
|
'update:modelValue',
|
||||||
|
'update:show'
|
||||||
|
],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
leaflet: null,
|
||||||
|
tileLayer: null,
|
||||||
|
zoomControl: null,
|
||||||
|
attribution: null,
|
||||||
|
marker: null,
|
||||||
|
initCenter: [ 0, 0 ],
|
||||||
|
zoomLevel: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
save() {
|
||||||
|
this.$emit('update:show', false);
|
||||||
|
},
|
||||||
|
onSheetOpen() {
|
||||||
|
let isFirstInit = false;
|
||||||
|
let centerChanged = false;
|
||||||
|
|
||||||
|
if (this.modelValue && (this.modelValue.longitude || this.modelValue.latitude)) {
|
||||||
|
if (this.initCenter[0] !== this.modelValue.latitude || this.initCenter[1] !== this.modelValue.longitude) {
|
||||||
|
this.initCenter[0] = this.modelValue.latitude;
|
||||||
|
this.initCenter[1] = this.modelValue.longitude;
|
||||||
|
this.zoomLevel = 14;
|
||||||
|
|
||||||
|
centerChanged = true;
|
||||||
|
}
|
||||||
|
} else if (!this.modelValue || (!this.modelValue.longitude && !this.modelValue.latitude)) {
|
||||||
|
if (this.initCenter[0] || this.initCenter[1]) {
|
||||||
|
this.initCenter[0] = 0;
|
||||||
|
this.initCenter[1] = 0;
|
||||||
|
this.zoomLevel = 1;
|
||||||
|
|
||||||
|
centerChanged = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.leaflet) {
|
||||||
|
const mapContainer = this.$refs.map;
|
||||||
|
|
||||||
|
this.leaflet = this.$map.leaflet.map(mapContainer, {
|
||||||
|
attributionControl: false,
|
||||||
|
zoomControl: false
|
||||||
|
});
|
||||||
|
|
||||||
|
this.tileLayer = this.$map.leaflet.tileLayer(this.$map.generateOpenStreetMapTileImageUrl(), {
|
||||||
|
maxZoom: 19
|
||||||
|
});
|
||||||
|
this.tileLayer.addTo(this.leaflet);
|
||||||
|
|
||||||
|
this.zoomControl = this.$map.leaflet.control.zoom({
|
||||||
|
zoomInTitle: this.$t('Zoom in'),
|
||||||
|
zoomOutTitle: this.$t('Zoom out'),
|
||||||
|
});
|
||||||
|
this.zoomControl.addTo(this.leaflet);
|
||||||
|
|
||||||
|
this.attribution = this.$map.leaflet.control.attribution({
|
||||||
|
prefix: '© <a href="http://www.openstreetmap.org/copyright" class="external" target="_blank">OpenStreetMap</a>'
|
||||||
|
});
|
||||||
|
this.attribution.addTo(this.leaflet);
|
||||||
|
|
||||||
|
isFirstInit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFirstInit || centerChanged) {
|
||||||
|
this.leaflet.setView(this.initCenter, this.zoomLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (centerChanged && this.zoomLevel > 1) {
|
||||||
|
if (!this.marker) {
|
||||||
|
this.marker = this.$map.leaflet.marker(this.initCenter).addTo(this.leaflet);
|
||||||
|
} else {
|
||||||
|
this.marker.setLatLng(this.initCenter);
|
||||||
|
}
|
||||||
|
} else if (centerChanged && this.zoomLevel <= 1) {
|
||||||
|
if (this.marker) {
|
||||||
|
this.marker.remove();
|
||||||
|
this.marker = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSheetClosed() {
|
||||||
|
this.close();
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.$emit('update:show', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -389,4 +389,9 @@ export default {
|
|||||||
ignoreError: !!ignoreError
|
ignoreError: !!ignoreError
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
generateOpenStreetMapTileImageUrl: () => {
|
||||||
|
const token = userState.getToken();
|
||||||
|
|
||||||
|
return 'proxy/openstreetmap/tile/{z}/{x}/{y}.png?token=' + token;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -703,6 +703,8 @@ export default {
|
|||||||
'Back': 'Back',
|
'Back': 'Back',
|
||||||
'Load More': 'Load More',
|
'Load More': 'Load More',
|
||||||
'No data': 'No data',
|
'No data': 'No data',
|
||||||
|
'Zoom in': 'Zoom in',
|
||||||
|
'Zoom out': 'Zoom out',
|
||||||
'Change Language': 'Change Language',
|
'Change Language': 'Change Language',
|
||||||
'Date is too early': 'Date is too early',
|
'Date is too early': 'Date is too early',
|
||||||
'Unlock Application': 'Unlock Application',
|
'Unlock Application': 'Unlock Application',
|
||||||
@@ -833,6 +835,7 @@ export default {
|
|||||||
'Geographic Location': 'Geographic Location',
|
'Geographic Location': 'Geographic Location',
|
||||||
'No Location': 'No Location',
|
'No Location': 'No Location',
|
||||||
'Getting Location...': 'Getting Location...',
|
'Getting Location...': 'Getting Location...',
|
||||||
|
'Show on the map': 'Show on the map',
|
||||||
'Update Geographic Location': 'Update Geographic Location',
|
'Update Geographic Location': 'Update Geographic Location',
|
||||||
'Clear Geographic Location': 'Clear Geographic Location',
|
'Clear Geographic Location': 'Clear Geographic Location',
|
||||||
'Unable to get current position': 'Unable to get current position',
|
'Unable to get current position': 'Unable to get current position',
|
||||||
|
|||||||
@@ -703,6 +703,8 @@ export default {
|
|||||||
'Back': '返回',
|
'Back': '返回',
|
||||||
'Load More': '加载更多',
|
'Load More': '加载更多',
|
||||||
'No data': '没有数据',
|
'No data': '没有数据',
|
||||||
|
'Zoom in': '放大',
|
||||||
|
'Zoom out': '缩小',
|
||||||
'Change Language': '修改语言',
|
'Change Language': '修改语言',
|
||||||
'Date is too early': '日期过早',
|
'Date is too early': '日期过早',
|
||||||
'Unlock Application': '解锁应用',
|
'Unlock Application': '解锁应用',
|
||||||
@@ -833,6 +835,7 @@ export default {
|
|||||||
'Geographic Location': '地理位置',
|
'Geographic Location': '地理位置',
|
||||||
'No Location': '没有位置',
|
'No Location': '没有位置',
|
||||||
'Getting Location...': '正在获取位置...',
|
'Getting Location...': '正在获取位置...',
|
||||||
|
'Show on the map': '在地图上显示',
|
||||||
'Update Geographic Location': '更新地理位置',
|
'Update Geographic Location': '更新地理位置',
|
||||||
'Clear Geographic Location': '清除地理位置',
|
'Clear Geographic Location': '清除地理位置',
|
||||||
'Unable to get current position': '无法获取当前地理位置',
|
'Unable to get current position': '无法获取当前地理位置',
|
||||||
|
|||||||
@@ -73,6 +73,9 @@ import 'line-awesome/dist/line-awesome/css/line-awesome.css';
|
|||||||
import VueDatePicker from '@vuepic/vue-datepicker';
|
import VueDatePicker from '@vuepic/vue-datepicker';
|
||||||
import '@vuepic/vue-datepicker/dist/main.css';
|
import '@vuepic/vue-datepicker/dist/main.css';
|
||||||
|
|
||||||
|
import * as Leaflet from 'leaflet/dist/leaflet-src.esm.js';
|
||||||
|
import 'leaflet/dist/leaflet.css';
|
||||||
|
|
||||||
import api from './consts/api.js';
|
import api from './consts/api.js';
|
||||||
import datetime from './consts/datetime.js';
|
import datetime from './consts/datetime.js';
|
||||||
import currency from './consts/currency.js';
|
import currency from './consts/currency.js';
|
||||||
@@ -135,6 +138,7 @@ import IconSelectionSheet from './components/mobile/IconSelectionSheet.vue';
|
|||||||
import ColorSelectionSheet from './components/mobile/ColorSelectionSheet.vue';
|
import ColorSelectionSheet from './components/mobile/ColorSelectionSheet.vue';
|
||||||
import InformationSheet from './components/mobile/InformationSheet.vue';
|
import InformationSheet from './components/mobile/InformationSheet.vue';
|
||||||
import NumberPadSheet from './components/mobile/NumberPadSheet.vue';
|
import NumberPadSheet from './components/mobile/NumberPadSheet.vue';
|
||||||
|
import MapSheet from './components/mobile/MapSheet.vue';
|
||||||
import TransactionTagSelectionSheet from './components/mobile/TransactionTagSelectionSheet.vue';
|
import TransactionTagSelectionSheet from './components/mobile/TransactionTagSelectionSheet.vue';
|
||||||
|
|
||||||
import TextareaAutoSize from "./directives/mobile/textareaAutoSize.js";
|
import TextareaAutoSize from "./directives/mobile/textareaAutoSize.js";
|
||||||
@@ -254,6 +258,7 @@ app.component('IconSelectionSheet', IconSelectionSheet);
|
|||||||
app.component('ColorSelectionSheet', ColorSelectionSheet);
|
app.component('ColorSelectionSheet', ColorSelectionSheet);
|
||||||
app.component('InformationSheet', InformationSheet);
|
app.component('InformationSheet', InformationSheet);
|
||||||
app.component('NumberPadSheet', NumberPadSheet);
|
app.component('NumberPadSheet', NumberPadSheet);
|
||||||
|
app.component('MapSheet', MapSheet);
|
||||||
app.component('TransactionTagSelectionSheet', TransactionTagSelectionSheet);
|
app.component('TransactionTagSelectionSheet', TransactionTagSelectionSheet);
|
||||||
|
|
||||||
app.directive('TextareaAutoSize', TextareaAutoSize);
|
app.directive('TextareaAutoSize', TextareaAutoSize);
|
||||||
@@ -299,6 +304,10 @@ app.config.globalProperties.$locale = {
|
|||||||
getDisplayCurrency: (value, currencyCode, notConvertValue) => getDisplayCurrency(value, currencyCode, notConvertValue, i18n.global.t),
|
getDisplayCurrency: (value, currencyCode, notConvertValue) => getDisplayCurrency(value, currencyCode, notConvertValue, i18n.global.t),
|
||||||
initLocale: initLocale
|
initLocale: initLocale
|
||||||
};
|
};
|
||||||
|
app.config.globalProperties.$map = {
|
||||||
|
leaflet: Leaflet,
|
||||||
|
generateOpenStreetMapTileImageUrl: services.generateOpenStreetMapTileImageUrl
|
||||||
|
};
|
||||||
app.config.globalProperties.$tIf = (text, isTranslate) => transateIf(text, isTranslate, i18n.global.t);
|
app.config.globalProperties.$tIf = (text, isTranslate) => transateIf(text, isTranslate, i18n.global.t);
|
||||||
|
|
||||||
app.config.globalProperties.$alert = (message, confirmCallback) => showAlert(message, confirmCallback, i18n.global.t);
|
app.config.globalProperties.$alert = (message, confirmCallback) => showAlert(message, confirmCallback, i18n.global.t);
|
||||||
|
|||||||
@@ -38,12 +38,12 @@
|
|||||||
<f7-list-input type="textarea" label="Description" placeholder="Your transaction description (optional)"></f7-list-input>
|
<f7-list-input type="textarea" label="Description" placeholder="Your transaction description (optional)"></f7-list-input>
|
||||||
</f7-list>
|
</f7-list>
|
||||||
|
|
||||||
<f7-list form strong inset dividers class="margin-vertical" :class="{ 'readonly': mode === 'view' }"
|
<f7-list form strong inset dividers class="margin-vertical"
|
||||||
v-else-if="!loading">
|
v-else-if="!loading">
|
||||||
<f7-list-item
|
<f7-list-item
|
||||||
class="transaction-edit-amount"
|
class="transaction-edit-amount"
|
||||||
link="#" no-chevron
|
link="#" no-chevron
|
||||||
:class="{ 'color-teal': transaction.type === $constants.transaction.allTransactionTypes.Expense, 'color-red': transaction.type === $constants.transaction.allTransactionTypes.Income }"
|
:class="{ 'readonly': mode === 'view', 'color-teal': transaction.type === $constants.transaction.allTransactionTypes.Expense, 'color-red': transaction.type === $constants.transaction.allTransactionTypes.Income }"
|
||||||
:style="{ fontSize: sourceAmountFontSize + 'px' }"
|
:style="{ fontSize: sourceAmountFontSize + 'px' }"
|
||||||
:header="$t(sourceAmountName)"
|
:header="$t(sourceAmountName)"
|
||||||
:title="getDisplayAmount(transaction.sourceAmount, transaction.hideAmount)"
|
:title="getDisplayAmount(transaction.sourceAmount, transaction.hideAmount)"
|
||||||
@@ -59,6 +59,7 @@
|
|||||||
<f7-list-item
|
<f7-list-item
|
||||||
class="transaction-edit-amount"
|
class="transaction-edit-amount"
|
||||||
link="#" no-chevron
|
link="#" no-chevron
|
||||||
|
:class="{ 'readonly': mode === 'view' }"
|
||||||
:style="{ fontSize: destinationAmountFontSize + 'px' }"
|
:style="{ fontSize: destinationAmountFontSize + 'px' }"
|
||||||
:header="$t('Transfer In Amount')"
|
:header="$t('Transfer In Amount')"
|
||||||
:title="getDisplayAmount(transaction.destinationAmount, transaction.hideAmount)"
|
:title="getDisplayAmount(transaction.destinationAmount, transaction.hideAmount)"
|
||||||
@@ -76,7 +77,7 @@
|
|||||||
class="list-item-with-header-and-title list-item-title-hide-overflow"
|
class="list-item-with-header-and-title list-item-title-hide-overflow"
|
||||||
key="expenseCategorySelection"
|
key="expenseCategorySelection"
|
||||||
link="#" no-chevron
|
link="#" no-chevron
|
||||||
:class="{ 'disabled': !hasAvailableExpenseCategories }"
|
:class="{ 'disabled': !hasAvailableExpenseCategories, 'readonly': mode === 'view' }"
|
||||||
:header="$t('Category')"
|
:header="$t('Category')"
|
||||||
@click="showCategorySheet = true"
|
@click="showCategorySheet = true"
|
||||||
v-if="transaction.type === $constants.transaction.allTransactionTypes.Expense"
|
v-if="transaction.type === $constants.transaction.allTransactionTypes.Expense"
|
||||||
@@ -106,7 +107,7 @@
|
|||||||
class="list-item-with-header-and-title list-item-title-hide-overflow"
|
class="list-item-with-header-and-title list-item-title-hide-overflow"
|
||||||
key="incomeCategorySelection"
|
key="incomeCategorySelection"
|
||||||
link="#" no-chevron
|
link="#" no-chevron
|
||||||
:class="{ 'disabled': !hasAvailableIncomeCategories }"
|
:class="{ 'disabled': !hasAvailableIncomeCategories, 'readonly': mode === 'view' }"
|
||||||
:header="$t('Category')"
|
:header="$t('Category')"
|
||||||
@click="showCategorySheet = true"
|
@click="showCategorySheet = true"
|
||||||
v-if="transaction.type === $constants.transaction.allTransactionTypes.Income"
|
v-if="transaction.type === $constants.transaction.allTransactionTypes.Income"
|
||||||
@@ -136,7 +137,7 @@
|
|||||||
class="list-item-with-header-and-title list-item-title-hide-overflow"
|
class="list-item-with-header-and-title list-item-title-hide-overflow"
|
||||||
key="transferCategorySelection"
|
key="transferCategorySelection"
|
||||||
link="#" no-chevron
|
link="#" no-chevron
|
||||||
:class="{ 'disabled': !hasAvailableTransferCategories }"
|
:class="{ 'disabled': !hasAvailableTransferCategories, 'readonly': mode === 'view' }"
|
||||||
:header="$t('Category')"
|
:header="$t('Category')"
|
||||||
@click="showCategorySheet = true"
|
@click="showCategorySheet = true"
|
||||||
v-if="transaction.type === $constants.transaction.allTransactionTypes.Transfer"
|
v-if="transaction.type === $constants.transaction.allTransactionTypes.Transfer"
|
||||||
@@ -165,7 +166,7 @@
|
|||||||
<f7-list-item
|
<f7-list-item
|
||||||
class="list-item-with-header-and-title"
|
class="list-item-with-header-and-title"
|
||||||
link="#" no-chevron
|
link="#" no-chevron
|
||||||
:class="{ 'disabled': !allVisibleAccounts.length }"
|
:class="{ 'disabled': !allVisibleAccounts.length, 'readonly': mode === 'view' }"
|
||||||
:header="$t(sourceAccountName)"
|
:header="$t(sourceAccountName)"
|
||||||
:title="transaction.sourceAccountId ? $utilities.getNameByKeyValue(allAccounts, transaction.sourceAccountId, 'id', 'name') : $t('None')"
|
:title="transaction.sourceAccountId ? $utilities.getNameByKeyValue(allAccounts, transaction.sourceAccountId, 'id', 'name') : $t('None')"
|
||||||
@click="showSourceAccountSheet = true"
|
@click="showSourceAccountSheet = true"
|
||||||
@@ -187,7 +188,7 @@
|
|||||||
<f7-list-item
|
<f7-list-item
|
||||||
class="list-item-with-header-and-title"
|
class="list-item-with-header-and-title"
|
||||||
link="#" no-chevron
|
link="#" no-chevron
|
||||||
:class="{ 'disabled': !allVisibleAccounts.length }"
|
:class="{ 'disabled': !allVisibleAccounts.length, 'readonly': mode === 'view' }"
|
||||||
:header="$t('Destination Account')"
|
:header="$t('Destination Account')"
|
||||||
:title="transaction.destinationAccountId ? $utilities.getNameByKeyValue(allAccounts, transaction.destinationAccountId, 'id', 'name') : $t('None')"
|
:title="transaction.destinationAccountId ? $utilities.getNameByKeyValue(allAccounts, transaction.destinationAccountId, 'id', 'name') : $t('None')"
|
||||||
v-if="transaction.type === $constants.transaction.allTransactionTypes.Transfer"
|
v-if="transaction.type === $constants.transaction.allTransactionTypes.Transfer"
|
||||||
@@ -210,6 +211,7 @@
|
|||||||
<f7-list-item
|
<f7-list-item
|
||||||
class="list-item-with-header-and-title"
|
class="list-item-with-header-and-title"
|
||||||
link="#" no-chevron
|
link="#" no-chevron
|
||||||
|
:class="{ 'readonly': mode === 'view' }"
|
||||||
:header="$t('Transaction Time')"
|
:header="$t('Transaction Time')"
|
||||||
:title="$utilities.formatUnixTime($utilities.getActualUnixTimeForStore(transaction.time, $utilities.getTimezoneOffsetMinutes(), $utilities.getBrowserTimezoneOffsetMinutes()), this.$t('format.datetime.long'))"
|
:title="$utilities.formatUnixTime($utilities.getActualUnixTimeForStore(transaction.time, $utilities.getTimezoneOffsetMinutes(), $utilities.getBrowserTimezoneOffsetMinutes()), this.$t('format.datetime.long'))"
|
||||||
@click="showTransactionDateTimeSheet = true"
|
@click="showTransactionDateTimeSheet = true"
|
||||||
@@ -222,6 +224,7 @@
|
|||||||
<f7-list-item
|
<f7-list-item
|
||||||
:no-chevron="mode === 'view'"
|
:no-chevron="mode === 'view'"
|
||||||
class="list-item-with-header-and-title list-item-title-hide-overflow list-item-no-item-after"
|
class="list-item-with-header-and-title list-item-title-hide-overflow list-item-no-item-after"
|
||||||
|
:class="{ 'readonly': mode === 'view' }"
|
||||||
:header="$t('Transaction Time Zone')"
|
:header="$t('Transaction Time Zone')"
|
||||||
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Timezone'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), pageTitle: $t('Transaction Time Zone'), popupCloseLinkText: $t('Done') }">
|
smart-select :smart-select-params="{ openIn: 'popup', popupPush: true, closeOnSelect: true, scrollToSelectedItem: true, searchbar: true, searchbarPlaceholder: $t('Timezone'), searchbarDisableText: $t('Cancel'), appendSearchbarNotFound: $t('No results'), pageTitle: $t('Transaction Time Zone'), popupCloseLinkText: $t('Done') }">
|
||||||
<select v-model="transaction.timeZone">
|
<select v-model="transaction.timeZone">
|
||||||
@@ -240,6 +243,7 @@
|
|||||||
<f7-list-item
|
<f7-list-item
|
||||||
link="#" no-chevron
|
link="#" no-chevron
|
||||||
class="list-item-with-header-and-title list-item-title-hide-overflow"
|
class="list-item-with-header-and-title list-item-title-hide-overflow"
|
||||||
|
:class="{ 'readonly': mode === 'view' && !transaction.geoLocation }"
|
||||||
:header="$t('Geographic Location')"
|
:header="$t('Geographic Location')"
|
||||||
@click="showGeoLocationActionSheet = true"
|
@click="showGeoLocationActionSheet = true"
|
||||||
>
|
>
|
||||||
@@ -249,10 +253,15 @@
|
|||||||
<span v-else-if="!transaction.geoLocation">{{ geoLocationStatusInfo }}</span>
|
<span v-else-if="!transaction.geoLocation">{{ geoLocationStatusInfo }}</span>
|
||||||
</f7-block>
|
</f7-block>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<map-sheet v-model="transaction.geoLocation"
|
||||||
|
v-model:show="showGeoLocationMapSheet">
|
||||||
|
</map-sheet>
|
||||||
</f7-list-item>
|
</f7-list-item>
|
||||||
|
|
||||||
<f7-list-item
|
<f7-list-item
|
||||||
link="#" no-chevron
|
link="#" no-chevron
|
||||||
|
:class="{ 'readonly': mode === 'view' }"
|
||||||
:header="$t('Tags')"
|
:header="$t('Tags')"
|
||||||
@click="showTransactionTagSheet = true"
|
@click="showTransactionTagSheet = true"
|
||||||
>
|
>
|
||||||
@@ -283,6 +292,7 @@
|
|||||||
type="textarea"
|
type="textarea"
|
||||||
class="transaction-edit-comment"
|
class="transaction-edit-comment"
|
||||||
style="height: auto"
|
style="height: auto"
|
||||||
|
:class="{ 'readonly': mode === 'view' }"
|
||||||
:label="$t('Description')"
|
:label="$t('Description')"
|
||||||
:placeholder="mode !== 'view' ? $t('Your transaction description (optional)') : ''"
|
:placeholder="mode !== 'view' ? $t('Your transaction description (optional)') : ''"
|
||||||
v-textarea-auto-size
|
v-textarea-auto-size
|
||||||
@@ -295,6 +305,9 @@
|
|||||||
<f7-actions-button v-if="mode !== 'view'" @click="updateGeoLocation(true)">{{ $t('Update Geographic Location') }}</f7-actions-button>
|
<f7-actions-button v-if="mode !== 'view'" @click="updateGeoLocation(true)">{{ $t('Update Geographic Location') }}</f7-actions-button>
|
||||||
<f7-actions-button v-if="mode !== 'view'" @click="clearGeoLocation">{{ $t('Clear Geographic Location') }}</f7-actions-button>
|
<f7-actions-button v-if="mode !== 'view'" @click="clearGeoLocation">{{ $t('Clear Geographic Location') }}</f7-actions-button>
|
||||||
</f7-actions-group>
|
</f7-actions-group>
|
||||||
|
<f7-actions-group>
|
||||||
|
<f7-actions-button :class="{ 'disabled': !transaction.geoLocation }" @click="showGeoLocationMapSheet = true">{{ $t('Show on the map') }}</f7-actions-button>
|
||||||
|
</f7-actions-group>
|
||||||
<f7-actions-group>
|
<f7-actions-group>
|
||||||
<f7-actions-button bold close>{{ $t('Cancel') }}</f7-actions-button>
|
<f7-actions-button bold close>{{ $t('Cancel') }}</f7-actions-button>
|
||||||
</f7-actions-group>
|
</f7-actions-group>
|
||||||
@@ -372,6 +385,7 @@ export default {
|
|||||||
showSourceAccountSheet: false,
|
showSourceAccountSheet: false,
|
||||||
showDestinationAccountSheet: false,
|
showDestinationAccountSheet: false,
|
||||||
showTransactionDateTimeSheet: false,
|
showTransactionDateTimeSheet: false,
|
||||||
|
showGeoLocationMapSheet: false,
|
||||||
showTransactionTagSheet: false
|
showTransactionTagSheet: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -165,6 +165,12 @@
|
|||||||
"url": "https://momentjs.com",
|
"url": "https://momentjs.com",
|
||||||
"licenseUrl": "https://github.com/moment/moment-timezone/blob/0.5.43/LICENSE"
|
"licenseUrl": "https://github.com/moment/moment-timezone/blob/0.5.43/LICENSE"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Leaflet",
|
||||||
|
"copyright": "Copyright (c) 2010-2022, Vladimir Agafonkin, Copyright (c) 2010-2011, CloudMade, All rights reserved.",
|
||||||
|
"url": "https://leafletjs.com/",
|
||||||
|
"licenseUrl": "https://github.com/Leaflet/Leaflet/blob/v1.9.3/LICENSE"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "crypto-js",
|
"name": "crypto-js",
|
||||||
"copyright": "Copyright (c) 2009-2013 Jeff Mott, Copyright (c) 2013-2016 Evan Vosberg",
|
"copyright": "Copyright (c) 2009-2013 Jeff Mott, Copyright (c) 2013-2016 Evan Vosberg",
|
||||||
|
|||||||
@@ -129,6 +129,10 @@ export default defineConfig(async () => {
|
|||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://127.0.0.1:8080/',
|
target: 'http://127.0.0.1:8080/',
|
||||||
changeOrigin: true
|
changeOrigin: true
|
||||||
|
},
|
||||||
|
'/proxy': {
|
||||||
|
target: 'http://127.0.0.1:8080/',
|
||||||
|
changeOrigin: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user