support transaction pictures
This commit is contained in:
@@ -320,6 +320,7 @@ func startWebServer(c *core.CliContext) error {
|
|||||||
// Transaction Pictures
|
// Transaction Pictures
|
||||||
if config.EnableTransactionPictures {
|
if config.EnableTransactionPictures {
|
||||||
apiV1Route.POST("/transaction/pictures/upload.json", bindApi(api.TransactionPictures.TransactionPictureUploadHandler))
|
apiV1Route.POST("/transaction/pictures/upload.json", bindApi(api.TransactionPictures.TransactionPictureUploadHandler))
|
||||||
|
apiV1Route.POST("/transaction/pictures/remove_unused.json", bindApi(api.TransactionPictures.TransactionPictureRemoveUnusedHandler))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transaction Categories
|
// Transaction Categories
|
||||||
|
|||||||
@@ -144,6 +144,27 @@ func (a *TransactionPicturesApi) TransactionPictureGetHandler(c *core.WebContext
|
|||||||
return pictureData, contentType, nil
|
return pictureData, contentType, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TransactionPictureRemoveUnusedHandler removes unused transaction picture by request parameters for current user
|
||||||
|
func (a *TransactionPicturesApi) TransactionPictureRemoveUnusedHandler(c *core.WebContext) (any, *errs.Error) {
|
||||||
|
var pictureDeleteReq models.TransactionPictureUnusedDeleteRequest
|
||||||
|
err := c.ShouldBindJSON(&pictureDeleteReq)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf(c, "[transaction_pictures.TransactionPictureRemoveUnusedHandler] parse request failed, because %s", err.Error())
|
||||||
|
return nil, errs.NewIncompleteOrIncorrectSubmissionError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
err = a.pictures.RemoveUnusedTransactionPicture(c, uid, pictureDeleteReq.Id)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf(c, "[transaction_pictures.TransactionPictureRemoveUnusedHandler] failed to remove unused transaction picture for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
return nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *TransactionPicturesApi) createNewPictureInfoModel(uid int64, fileExtension string, clientIp string) *models.TransactionPictureInfo {
|
func (a *TransactionPicturesApi) createNewPictureInfoModel(uid int64, fileExtension string, clientIp string) *models.TransactionPictureInfo {
|
||||||
return &models.TransactionPictureInfo{
|
return &models.TransactionPictureInfo{
|
||||||
Uid: uid,
|
Uid: uid,
|
||||||
|
|||||||
+12
-4
@@ -589,7 +589,7 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.WebContext) (any, *errs.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if transactionGetReq.WithPictures {
|
if transactionGetReq.WithPictures && a.CurrentConfig().EnableTransactionPictures {
|
||||||
pictureInfos, err = a.transactionPictures.GetPictureInfosByTransactionId(c, uid, transaction.TransactionId)
|
pictureInfos, err = a.transactionPictures.GetPictureInfosByTransactionId(c, uid, transaction.TransactionId)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -622,7 +622,7 @@ func (a *TransactionsApi) TransactionGetHandler(c *core.WebContext) (any, *errs.
|
|||||||
transactionResp.Tags = a.getTransactionTagInfoResponses(transactionTagIds, tagMap)
|
transactionResp.Tags = a.getTransactionTagInfoResponses(transactionTagIds, tagMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
if transactionGetReq.WithPictures {
|
if transactionGetReq.WithPictures && a.CurrentConfig().EnableTransactionPictures {
|
||||||
transactionResp.Pictures = a.GetTransactionPictureInfoResponseList(pictureInfos)
|
transactionResp.Pictures = a.GetTransactionPictureInfoResponseList(pictureInfos)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -653,6 +653,10 @@ func (a *TransactionsApi) TransactionCreateHandler(c *core.WebContext) (any, *er
|
|||||||
return nil, errs.ErrTransactionPictureIdInvalid
|
return nil, errs.ErrTransactionPictureIdInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(pictureIds) > 10 {
|
||||||
|
return nil, errs.ErrTransactionPictureTooMuch
|
||||||
|
}
|
||||||
|
|
||||||
if transactionCreateReq.Type < models.TRANSACTION_TYPE_MODIFY_BALANCE || transactionCreateReq.Type > models.TRANSACTION_TYPE_TRANSFER {
|
if transactionCreateReq.Type < models.TRANSACTION_TYPE_MODIFY_BALANCE || transactionCreateReq.Type > models.TRANSACTION_TYPE_TRANSFER {
|
||||||
log.Warnf(c, "[transactions.TransactionCreateHandler] transaction type is invalid")
|
log.Warnf(c, "[transactions.TransactionCreateHandler] transaction type is invalid")
|
||||||
return nil, errs.ErrTransactionTypeInvalid
|
return nil, errs.ErrTransactionTypeInvalid
|
||||||
@@ -775,6 +779,10 @@ func (a *TransactionsApi) TransactionModifyHandler(c *core.WebContext) (any, *er
|
|||||||
return nil, errs.ErrTransactionPictureIdInvalid
|
return nil, errs.ErrTransactionPictureIdInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(pictureIds) > 10 {
|
||||||
|
return nil, errs.ErrTransactionPictureTooMuch
|
||||||
|
}
|
||||||
|
|
||||||
uid := c.GetCurrentUid()
|
uid := c.GetCurrentUid()
|
||||||
user, err := a.users.GetUserById(c, uid)
|
user, err := a.users.GetUserById(c, uid)
|
||||||
|
|
||||||
@@ -1214,7 +1222,7 @@ func (a *TransactionsApi) getTransactionResponseListResult(c *core.WebContext, u
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if withPictures {
|
if withPictures && a.CurrentConfig().EnableTransactionPictures {
|
||||||
pictureInfoMap, err = a.transactionPictures.GetPictureInfosByTransactionIds(c, uid, utils.ToUniqueInt64Slice(a.transactions.GetTransactionIds(transactions)))
|
pictureInfoMap, err = a.transactionPictures.GetPictureInfosByTransactionIds(c, uid, utils.ToUniqueInt64Slice(a.transactions.GetTransactionIds(transactions)))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1256,7 +1264,7 @@ func (a *TransactionsApi) getTransactionResponseListResult(c *core.WebContext, u
|
|||||||
result[i].Tags = a.getTransactionTagInfoResponses(transactionTagIds, tagMap)
|
result[i].Tags = a.getTransactionTagInfoResponses(transactionTagIds, tagMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
if withPictures {
|
if withPictures && a.CurrentConfig().EnableTransactionPictures {
|
||||||
pictureInfos, exists := pictureInfoMap[transaction.TransactionId]
|
pictureInfos, exists := pictureInfoMap[transaction.TransactionId]
|
||||||
|
|
||||||
if exists {
|
if exists {
|
||||||
|
|||||||
@@ -10,4 +10,5 @@ var (
|
|||||||
ErrTransactionPictureIsEmpty = NewNormalError(NormalSubcategoryPicture, 3, http.StatusBadRequest, "transaction picture is empty")
|
ErrTransactionPictureIsEmpty = NewNormalError(NormalSubcategoryPicture, 3, http.StatusBadRequest, "transaction picture is empty")
|
||||||
ErrTransactionPictureNoExists = NewNormalError(NormalSubcategoryPicture, 4, http.StatusNotFound, "transaction picture not exists")
|
ErrTransactionPictureNoExists = NewNormalError(NormalSubcategoryPicture, 4, http.StatusNotFound, "transaction picture not exists")
|
||||||
ErrTransactionPictureExtensionInvalid = NewNormalError(NormalSubcategoryPicture, 5, http.StatusNotFound, "transaction picture file extension invalid")
|
ErrTransactionPictureExtensionInvalid = NewNormalError(NormalSubcategoryPicture, 5, http.StatusNotFound, "transaction picture file extension invalid")
|
||||||
|
ErrTransactionPictureTooMuch = NewNormalError(NormalSubcategoryPicture, 6, http.StatusBadRequest, "transaction pictures too much")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ type TransactionPictureInfo struct {
|
|||||||
DeletedUnixTime int64
|
DeletedUnixTime int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TransactionPictureUnusedDeleteRequest represents all parameters of unused transaction picture deleting request
|
||||||
|
type TransactionPictureUnusedDeleteRequest struct {
|
||||||
|
Id int64 `json:"id,string" binding:"required,min=1"`
|
||||||
|
}
|
||||||
|
|
||||||
// TransactionPictureInfoBasicResponse represents a view-object of transaction picture basic info
|
// TransactionPictureInfoBasicResponse represents a view-object of transaction picture basic info
|
||||||
type TransactionPictureInfoBasicResponse struct {
|
type TransactionPictureInfoBasicResponse struct {
|
||||||
PictureId int64 `json:"pictureId,string"`
|
PictureId int64 `json:"pictureId,string"`
|
||||||
|
|||||||
@@ -211,6 +211,36 @@ func (s *TransactionPictureService) UploadPicture(c core.Context, pictureInfo *m
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveUnusedTransactionPicture removes the unused transaction picture of specified user
|
||||||
|
func (s *TransactionPictureService) RemoveUnusedTransactionPicture(c core.Context, uid int64, pictureId int64) error {
|
||||||
|
if uid <= 0 {
|
||||||
|
return errs.ErrUserIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
if pictureId <= 0 {
|
||||||
|
return errs.ErrTransactionPictureIdInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
|
||||||
|
updateModel := &models.TransactionPictureInfo{
|
||||||
|
Deleted: true,
|
||||||
|
DeletedUnixTime: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.UserDB().DoTransaction(c, func(sess *xorm.Session) error {
|
||||||
|
deletedRows, err := sess.ID(pictureId).Cols("deleted", "deleted_unix_time").Where("uid=? AND deleted=? AND transaction_id=?", uid, false, models.TransactionPictureNewPictureTransactionId).Update(updateModel)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if deletedRows < 1 {
|
||||||
|
return errs.ErrTransactionPictureNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// GetPictureInfoMapByList returns a transaction picture info list map by a list
|
// GetPictureInfoMapByList returns a transaction picture info list map by a list
|
||||||
func (s *TransactionPictureService) GetPictureInfoMapByList(pictureInfos []*models.TransactionPictureInfo) map[int64]*models.TransactionPictureInfo {
|
func (s *TransactionPictureService) GetPictureInfoMapByList(pictureInfos []*models.TransactionPictureInfo) map[int64]*models.TransactionPictureInfo {
|
||||||
pictureInfoMap := make(map[int64]*models.TransactionPictureInfo)
|
pictureInfoMap := make(map[int64]*models.TransactionPictureInfo)
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ export function isUserVerifyEmailEnabled() {
|
|||||||
return getServerSetting('v') === '1';
|
return getServerSetting('v') === '1';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isTransactionPicturesEnabled() {
|
||||||
|
return getServerSetting('p') === '1';
|
||||||
|
}
|
||||||
|
|
||||||
export function isUserScheduledTransactionEnabled() {
|
export function isUserScheduledTransactionEnabled() {
|
||||||
return getServerSetting('s') === '1';
|
return getServerSetting('s') === '1';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -443,6 +443,11 @@ export default {
|
|||||||
clientSessionId: clientSessionId
|
clientSessionId: clientSessionId
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
removeUnusedTransactionPicture: ({ id }) => {
|
||||||
|
return axios.post('v1/transaction/pictures/remove_unused.json', {
|
||||||
|
id
|
||||||
|
});
|
||||||
|
},
|
||||||
getAllTransactionCategories: () => {
|
getAllTransactionCategories: () => {
|
||||||
return axios.get('v1/transaction/categories/list.json');
|
return axios.get('v1/transaction/categories/list.json');
|
||||||
},
|
},
|
||||||
@@ -651,5 +656,27 @@ export default {
|
|||||||
} else {
|
} else {
|
||||||
return avatarUrl + '?' + params.join('&');
|
return avatarUrl + '?' + params.join('&');
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
getTransactionPictureUrlWithToken(pictureUrl, disableBrowserCache) {
|
||||||
|
if (!pictureUrl) {
|
||||||
|
return pictureUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = [];
|
||||||
|
params.push('token=' + userState.getToken());
|
||||||
|
|
||||||
|
if (disableBrowserCache) {
|
||||||
|
if (isBoolean(disableBrowserCache)) {
|
||||||
|
params.push('_nocache=' + generateRandomUUID());
|
||||||
|
} else {
|
||||||
|
params.push('_nocache=' + disableBrowserCache);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pictureUrl.indexOf('?') >= 0) {
|
||||||
|
return pictureUrl + '&' + params.join('&');
|
||||||
|
} else {
|
||||||
|
return pictureUrl + '?' + params.join('&');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -165,6 +165,11 @@ export function setTransactionModelByTransaction(transaction, transaction2, allC
|
|||||||
|
|
||||||
transaction.hideAmount = transaction2.hideAmount;
|
transaction.hideAmount = transaction2.hideAmount;
|
||||||
transaction.tagIds = transaction2.tagIds || [];
|
transaction.tagIds = transaction2.tagIds || [];
|
||||||
|
|
||||||
|
if (setContextData) {
|
||||||
|
transaction.pictures = transaction2.pictures || [];
|
||||||
|
}
|
||||||
|
|
||||||
transaction.comment = transaction2.comment;
|
transaction.comment = transaction2.comment;
|
||||||
|
|
||||||
if (setContextData) {
|
if (setContextData) {
|
||||||
|
|||||||
@@ -1094,6 +1094,7 @@
|
|||||||
"transaction picture is empty": "Transaction picture file is empty",
|
"transaction picture is empty": "Transaction picture file is empty",
|
||||||
"transaction picture not exists": "Transaction picture does not exist",
|
"transaction picture not exists": "Transaction picture does not exist",
|
||||||
"transaction picture file extension invalid": "Transaction picture file extension is invalid",
|
"transaction picture file extension invalid": "Transaction picture file extension is invalid",
|
||||||
|
"transaction pictures too much": "There are too many transaction pictures",
|
||||||
"query items cannot be blank": "There are no query items",
|
"query items cannot be blank": "There are no query items",
|
||||||
"query items too much": "There are too many query items",
|
"query items too much": "There are too many query items",
|
||||||
"query items have invalid item": "There is invalid item in query items",
|
"query items have invalid item": "There is invalid item in query items",
|
||||||
@@ -1466,7 +1467,11 @@
|
|||||||
"Unable to save transaction": "Unable to save transaction",
|
"Unable to save transaction": "Unable to save transaction",
|
||||||
"You have added a new transaction": "You have added a new transaction",
|
"You have added a new transaction": "You have added a new transaction",
|
||||||
"You have saved this transaction": "You have saved this transaction",
|
"You have saved this transaction": "You have saved this transaction",
|
||||||
|
"Add Picture": "Add Picture",
|
||||||
|
"Remove Picture": "Remove Picture",
|
||||||
|
"Are you sure you want to remove this transaction picture?": "Are you sure you want to remove this transaction picture?",
|
||||||
"Unable to upload transaction picture": "Unable to upload transaction picture",
|
"Unable to upload transaction picture": "Unable to upload transaction picture",
|
||||||
|
"Unable to remove transaction picture": "Unable to remove transaction picture",
|
||||||
"Search transaction description": "Search transaction description",
|
"Search transaction description": "Search transaction description",
|
||||||
"Unable to retrieve transaction list": "Unable to retrieve transaction list",
|
"Unable to retrieve transaction list": "Unable to retrieve transaction list",
|
||||||
"Custom Date Range": "Custom Date Range",
|
"Custom Date Range": "Custom Date Range",
|
||||||
|
|||||||
@@ -1094,6 +1094,7 @@
|
|||||||
"transaction picture is empty": "交易图片文件为空",
|
"transaction picture is empty": "交易图片文件为空",
|
||||||
"transaction picture not exists": "交易图片不存在",
|
"transaction picture not exists": "交易图片不存在",
|
||||||
"transaction picture file extension invalid": "交易图片文件扩展名无效",
|
"transaction picture file extension invalid": "交易图片文件扩展名无效",
|
||||||
|
"transaction pictures too much": "交易图片过多",
|
||||||
"query items cannot be blank": "请求项目不能为空",
|
"query items cannot be blank": "请求项目不能为空",
|
||||||
"query items too much": "请求项目过多",
|
"query items too much": "请求项目过多",
|
||||||
"query items have invalid item": "请求项目中有非法项目",
|
"query items have invalid item": "请求项目中有非法项目",
|
||||||
@@ -1466,7 +1467,11 @@
|
|||||||
"Unable to save transaction": "无法保存交易",
|
"Unable to save transaction": "无法保存交易",
|
||||||
"You have added a new transaction": "您已经添加新交易",
|
"You have added a new transaction": "您已经添加新交易",
|
||||||
"You have saved this transaction": "您已经保存该交易",
|
"You have saved this transaction": "您已经保存该交易",
|
||||||
|
"Add Picture": "添加图片",
|
||||||
|
"Remove Picture": "删除图片",
|
||||||
|
"Are you sure you want to remove this transaction picture?": "您确定要删除这张交易图片?",
|
||||||
"Unable to upload transaction picture": "无法上传交易图片",
|
"Unable to upload transaction picture": "无法上传交易图片",
|
||||||
|
"Unable to remove transaction picture": "无法删除交易图片",
|
||||||
"Search transaction description": "搜索交易描述",
|
"Search transaction description": "搜索交易描述",
|
||||||
"Unable to retrieve transaction list": "无法获取交易列表",
|
"Unable to retrieve transaction list": "无法获取交易列表",
|
||||||
"Custom Date Range": "自定义日期范围",
|
"Custom Date Range": "自定义日期范围",
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ import Framework7Tooltip from 'framework7/components/tooltip';
|
|||||||
import Framework7Skeleton from 'framework7/components/skeleton';
|
import Framework7Skeleton from 'framework7/components/skeleton';
|
||||||
import Framework7Treeview from 'framework7/components/treeview';
|
import Framework7Treeview from 'framework7/components/treeview';
|
||||||
import Framework7Typography from 'framework7/components/typography';
|
import Framework7Typography from 'framework7/components/typography';
|
||||||
|
import Framework7Swiper from 'framework7/components/swiper';
|
||||||
|
import Framework7PhotoBrowser from 'framework7/components/photo-browser';
|
||||||
import Framework7Vue, { registerComponents } from 'framework7-vue/bundle';
|
import Framework7Vue, { registerComponents } from 'framework7-vue/bundle';
|
||||||
|
|
||||||
import 'framework7/css';
|
import 'framework7/css';
|
||||||
@@ -68,6 +70,8 @@ import 'framework7/components/tooltip/css';
|
|||||||
import 'framework7/components/skeleton/css';
|
import 'framework7/components/skeleton/css';
|
||||||
import 'framework7/components/treeview/css';
|
import 'framework7/components/treeview/css';
|
||||||
import 'framework7/components/typography/css';
|
import 'framework7/components/typography/css';
|
||||||
|
import 'framework7/components/swiper/css';
|
||||||
|
import 'framework7/components/photo-browser/css';
|
||||||
|
|
||||||
import 'framework7-icons';
|
import 'framework7-icons';
|
||||||
import 'line-awesome/dist/line-awesome/css/line-awesome.css';
|
import 'line-awesome/dist/line-awesome/css/line-awesome.css';
|
||||||
@@ -158,6 +162,8 @@ Framework7.use([
|
|||||||
Framework7Skeleton,
|
Framework7Skeleton,
|
||||||
Framework7Treeview,
|
Framework7Treeview,
|
||||||
Framework7Typography,
|
Framework7Typography,
|
||||||
|
Framework7Swiper,
|
||||||
|
Framework7PhotoBrowser,
|
||||||
Framework7Vue
|
Framework7Vue
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -451,6 +451,7 @@ export const useTransactionsStore = defineStore('transactions', {
|
|||||||
destinationAmount: 0,
|
destinationAmount: 0,
|
||||||
hideAmount: false,
|
hideAmount: false,
|
||||||
tagIds: [],
|
tagIds: [],
|
||||||
|
pictures: [],
|
||||||
comment: '',
|
comment: '',
|
||||||
geoLocation: null
|
geoLocation: null
|
||||||
};
|
};
|
||||||
@@ -873,7 +874,7 @@ export const useTransactionsStore = defineStore('transactions', {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction.pictureIds = pictureIds;
|
submitTransaction.pictureIds = pictureIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
@@ -1027,6 +1028,39 @@ export const useTransactionsStore = defineStore('transactions', {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
removeUnusedTransactionPicture({ pictureInfo }) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
services.removeUnusedTransactionPicture({
|
||||||
|
id: pictureInfo.pictureId
|
||||||
|
}).then(response => {
|
||||||
|
const data = response.data;
|
||||||
|
|
||||||
|
if (!data || !data.success || !data.result) {
|
||||||
|
reject({ message: 'Unable to remove transaction picture' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(data.result);
|
||||||
|
}).catch(error => {
|
||||||
|
logger.error('failed to remove transaction picture', error);
|
||||||
|
|
||||||
|
if (error.response && error.response.data && error.response.data.errorMessage) {
|
||||||
|
reject({ error: error.response.data });
|
||||||
|
} else if (!error.processed) {
|
||||||
|
reject({ message: 'Unable to remove transaction picture' });
|
||||||
|
} else {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getTransactionPictureUrl(pictureInfo, disableBrowserCache) {
|
||||||
|
if (!pictureInfo || !pictureInfo.originalUrl) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return services.getTransactionPictureUrlWithToken(pictureInfo.originalUrl, disableBrowserCache);
|
||||||
|
},
|
||||||
collapseMonthInTransactionList({ month, collapse }) {
|
collapseMonthInTransactionList({ month, collapse }) {
|
||||||
if (month) {
|
if (month) {
|
||||||
month.opened = !collapse;
|
month.opened = !collapse;
|
||||||
|
|||||||
@@ -61,6 +61,9 @@
|
|||||||
<v-tab value="map" :disabled="!transaction.geoLocation" v-if="type === 'transaction' && mapProvider">
|
<v-tab value="map" :disabled="!transaction.geoLocation" v-if="type === 'transaction' && mapProvider">
|
||||||
<span>{{ $t('Location on Map') }}</span>
|
<span>{{ $t('Location on Map') }}</span>
|
||||||
</v-tab>
|
</v-tab>
|
||||||
|
<v-tab value="pictures" :disabled="mode !== 'add' && mode !== 'edit' && (!transaction.pictures || !transaction.pictures.length)" v-if="type === 'transaction' && isTransactionPicturesEnabled">
|
||||||
|
<span>{{ $t('Pictures') }}</span>
|
||||||
|
</v-tab>
|
||||||
</v-tabs>
|
</v-tabs>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -314,6 +317,38 @@
|
|||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-window-item>
|
</v-window-item>
|
||||||
|
<v-window-item value="pictures">
|
||||||
|
<v-row class="transaction-pictures align-content-start" :class="{ 'readonly': submitting || uploadingPicture || removingPictureId }">
|
||||||
|
<v-col :key="picIdx" cols="6" md="3" v-for="(pictureInfo, picIdx) in transaction.pictures">
|
||||||
|
<v-avatar rounded="lg" variant="tonal" size="160"
|
||||||
|
class="cursor-pointer transaction-picture"
|
||||||
|
color="rgba(0,0,0,0)" @click="viewOrRemovePicture(pictureInfo)">
|
||||||
|
<v-img :src="getTransactionPictureUrl(pictureInfo)">
|
||||||
|
<template #placeholder>
|
||||||
|
<div class="d-flex align-center justify-center fill-height bg-light-primary">
|
||||||
|
<v-progress-circular color="grey-500" indeterminate size="48"></v-progress-circular>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-img>
|
||||||
|
<div class="picture-control-icon" :class="{ 'show-control-icon': pictureInfo.pictureId === removingPictureId }">
|
||||||
|
<v-icon size="64" :icon="icons.remove" v-if="(mode === 'add' || mode === 'edit') && pictureInfo.pictureId !== removingPictureId"/>
|
||||||
|
<v-progress-circular color="grey-500" indeterminate size="48" v-if="(mode === 'add' || mode === 'edit') && pictureInfo.pictureId === removingPictureId"></v-progress-circular>
|
||||||
|
<v-icon size="64" :icon="icons.fullscreen" v-if="mode !== 'add' && mode !== 'edit'"/>
|
||||||
|
</div>
|
||||||
|
</v-avatar>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="6" md="3" v-if="canAddTransactionPicture">
|
||||||
|
<v-avatar rounded="lg" variant="tonal" size="160"
|
||||||
|
class="transaction-picture transaction-picture-add"
|
||||||
|
:class="{ 'enabled': !submitting, 'cursor-pointer': !submitting }"
|
||||||
|
color="rgba(0,0,0,0)" @click="showOpenPictureDialog">
|
||||||
|
<v-tooltip activator="parent" v-if="!submitting">{{ $t('Add Picture') }}</v-tooltip>
|
||||||
|
<v-icon class="transaction-picture-add-icon" size="56" :icon="icons.add" v-if="!uploadingPicture"/>
|
||||||
|
<v-progress-circular color="grey-500" indeterminate size="48" v-if="uploadingPicture"></v-progress-circular>
|
||||||
|
</v-avatar>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-window-item>
|
||||||
</v-window>
|
</v-window>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-text class="overflow-y-visible">
|
<v-card-text class="overflow-y-visible">
|
||||||
@@ -342,6 +377,7 @@
|
|||||||
|
|
||||||
<confirm-dialog ref="confirmDialog"/>
|
<confirm-dialog ref="confirmDialog"/>
|
||||||
<snack-bar ref="snackbar" />
|
<snack-bar ref="snackbar" />
|
||||||
|
<input ref="pictureInput" type="file" style="display: none" :accept="supportedImageExtensions" @change="uploadPicture($event)" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -355,11 +391,13 @@ import { useTransactionsStore } from '@/stores/transaction.js';
|
|||||||
import { useTransactionTemplatesStore } from '@/stores/transactionTemplate.js';
|
import { useTransactionTemplatesStore } from '@/stores/transactionTemplate.js';
|
||||||
import { useExchangeRatesStore } from '@/stores/exchangeRates.js';
|
import { useExchangeRatesStore } from '@/stores/exchangeRates.js';
|
||||||
|
|
||||||
|
import fileConstants from '@/consts/file.js';
|
||||||
import categoryConstants from '@/consts/category.js';
|
import categoryConstants from '@/consts/category.js';
|
||||||
import transactionConstants from '@/consts/transaction.js';
|
import transactionConstants from '@/consts/transaction.js';
|
||||||
import templateConstants from '@/consts/template.js';
|
import templateConstants from '@/consts/template.js';
|
||||||
import logger from '@/lib/logger.js';
|
import logger from '@/lib/logger.js';
|
||||||
import {
|
import {
|
||||||
|
isArray,
|
||||||
getNameByKeyValue
|
getNameByKeyValue
|
||||||
} from '@/lib/common.js';
|
} from '@/lib/common.js';
|
||||||
import {
|
import {
|
||||||
@@ -374,14 +412,21 @@ import {
|
|||||||
getFirstAvailableCategoryId
|
getFirstAvailableCategoryId
|
||||||
} from '@/lib/category.js';
|
} from '@/lib/category.js';
|
||||||
import { setTransactionModelByTransaction } from '@/lib/transaction.js';
|
import { setTransactionModelByTransaction } from '@/lib/transaction.js';
|
||||||
import { getMapProvider } from '@/lib/server_settings.js';
|
import {
|
||||||
|
isTransactionPicturesEnabled,
|
||||||
|
getMapProvider
|
||||||
|
} from '@/lib/server_settings.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
mdiDotsVertical,
|
mdiDotsVertical,
|
||||||
mdiEyeOffOutline,
|
mdiEyeOffOutline,
|
||||||
mdiEyeOutline,
|
mdiEyeOutline,
|
||||||
mdiSwapHorizontal,
|
mdiSwapHorizontal,
|
||||||
mdiPound
|
mdiPound,
|
||||||
|
mdiImageOutline,
|
||||||
|
mdiImagePlusOutline,
|
||||||
|
mdiTrashCanOutline,
|
||||||
|
mdiFullscreen
|
||||||
} from '@mdi/js';
|
} from '@mdi/js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -409,6 +454,8 @@ export default {
|
|||||||
geoLocationStatus: null,
|
geoLocationStatus: null,
|
||||||
geoMenuState: false,
|
geoMenuState: false,
|
||||||
submitting: false,
|
submitting: false,
|
||||||
|
uploadingPicture: false,
|
||||||
|
removingPictureId: '',
|
||||||
isSupportGeoLocation: !!navigator.geolocation,
|
isSupportGeoLocation: !!navigator.geolocation,
|
||||||
resolve: null,
|
resolve: null,
|
||||||
reject: null,
|
reject: null,
|
||||||
@@ -417,7 +464,11 @@ export default {
|
|||||||
show: mdiEyeOutline,
|
show: mdiEyeOutline,
|
||||||
hide: mdiEyeOffOutline,
|
hide: mdiEyeOffOutline,
|
||||||
swap: mdiSwapHorizontal,
|
swap: mdiSwapHorizontal,
|
||||||
tag: mdiPound
|
tag: mdiPound,
|
||||||
|
picture: mdiImageOutline ,
|
||||||
|
add: mdiImagePlusOutline,
|
||||||
|
remove: mdiTrashCanOutline,
|
||||||
|
fullscreen : mdiFullscreen
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -542,6 +593,9 @@ export default {
|
|||||||
allTagsMap() {
|
allTagsMap() {
|
||||||
return this.transactionTagsStore.allTransactionTagsMap;
|
return this.transactionTagsStore.allTransactionTagsMap;
|
||||||
},
|
},
|
||||||
|
supportedImageExtensions() {
|
||||||
|
return fileConstants.supportedImageExtensions;
|
||||||
|
},
|
||||||
hasAvailableExpenseCategories() {
|
hasAvailableExpenseCategories() {
|
||||||
if (!this.allCategories || !this.allCategories[this.allCategoryTypes.Expense] || !this.allCategories[this.allCategoryTypes.Expense].length) {
|
if (!this.allCategories || !this.allCategories[this.allCategoryTypes.Expense] || !this.allCategories[this.allCategoryTypes.Expense].length) {
|
||||||
return false;
|
return false;
|
||||||
@@ -609,6 +663,16 @@ export default {
|
|||||||
showAccountBalance() {
|
showAccountBalance() {
|
||||||
return this.settingsStore.appSettings.showAccountBalance;
|
return this.settingsStore.appSettings.showAccountBalance;
|
||||||
},
|
},
|
||||||
|
isTransactionPicturesEnabled() {
|
||||||
|
return isTransactionPicturesEnabled();
|
||||||
|
},
|
||||||
|
canAddTransactionPicture() {
|
||||||
|
if (this.type !== 'transaction' || (this.mode !== 'add' && this.mode !== 'edit')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !isArray(this.transaction.pictures) || this.transaction.pictures.length < 10;
|
||||||
|
},
|
||||||
mapProvider() {
|
mapProvider() {
|
||||||
return getMapProvider();
|
return getMapProvider();
|
||||||
},
|
},
|
||||||
@@ -926,6 +990,7 @@ export default {
|
|||||||
this.transaction.timeZone = this.settingsStore.appSettings.timeZone;
|
this.transaction.timeZone = this.settingsStore.appSettings.timeZone;
|
||||||
this.transaction.utcOffset = getTimezoneOffsetMinutes(this.transaction.timeZone);
|
this.transaction.utcOffset = getTimezoneOffsetMinutes(this.transaction.timeZone);
|
||||||
this.transaction.geoLocation = null;
|
this.transaction.geoLocation = null;
|
||||||
|
this.transaction.pictures = [];
|
||||||
this.mode = 'add';
|
this.mode = 'add';
|
||||||
},
|
},
|
||||||
edit() {
|
edit() {
|
||||||
@@ -1034,6 +1099,86 @@ export default {
|
|||||||
this.transaction.destinationAmount = oldSourceAmount;
|
this.transaction.destinationAmount = oldSourceAmount;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
showOpenPictureDialog() {
|
||||||
|
if (!this.canAddTransactionPicture || this.submitting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$refs.pictureInput.click();
|
||||||
|
},
|
||||||
|
uploadPicture(event) {
|
||||||
|
if (!event || !event.target || !event.target.files || !event.target.files.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const self = this;
|
||||||
|
const pictureFile = event.target.files[0];
|
||||||
|
|
||||||
|
event.target.value = null;
|
||||||
|
|
||||||
|
self.uploadingPicture = true;
|
||||||
|
self.submitting = true;
|
||||||
|
|
||||||
|
self.transactionsStore.uploadTransactionPicture({ pictureFile }).then(response => {
|
||||||
|
if (!isArray(self.transaction.pictures)) {
|
||||||
|
self.transaction.pictures = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
self.transaction.pictures.push(response);
|
||||||
|
|
||||||
|
self.uploadingPicture = false;
|
||||||
|
self.submitting = false;
|
||||||
|
}).catch(error => {
|
||||||
|
self.uploadingPicture = false;
|
||||||
|
self.submitting = false;
|
||||||
|
|
||||||
|
if (!error.processed) {
|
||||||
|
self.$refs.snackbar.showError(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
viewOrRemovePicture(pictureInfo) {
|
||||||
|
if (this.mode !== 'add' && this.mode !== 'edit') {
|
||||||
|
window.open(this.getTransactionPictureUrl(pictureInfo), '_blank');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
self.$refs.confirmDialog.open('Are you sure you want to remove this transaction picture?').then(() => {
|
||||||
|
self.removingPictureId = pictureInfo.pictureId;
|
||||||
|
self.submitting = true;
|
||||||
|
|
||||||
|
self.transactionsStore.removeUnusedTransactionPicture({ pictureInfo }).then(response => {
|
||||||
|
if (response && isArray(self.transaction.pictures)) {
|
||||||
|
for (let i = 0; i < self.transaction.pictures.length; i++) {
|
||||||
|
if (self.transaction.pictures[i].pictureId === pictureInfo.pictureId) {
|
||||||
|
self.transaction.pictures.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.removingPictureId = '';
|
||||||
|
self.submitting = false;
|
||||||
|
}).catch(error => {
|
||||||
|
if (error.error && error.error.errorCode === 211001) {
|
||||||
|
for (let i = 0; i < self.transaction.pictures.length; i++) {
|
||||||
|
if (self.transaction.pictures[i].pictureId === pictureInfo.pictureId) {
|
||||||
|
self.transaction.pictures.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!error.processed) {
|
||||||
|
self.$refs.snackbar.showError(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.removingPictureId = '';
|
||||||
|
self.submitting = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getTransactionPictureUrl(pictureInfo) {
|
||||||
|
return this.transactionsStore.getTransactionPictureUrl(pictureInfo);
|
||||||
|
},
|
||||||
getPrimaryCategoryName(categoryId, allCategories) {
|
getPrimaryCategoryName(categoryId, allCategories) {
|
||||||
return getTransactionPrimaryCategoryName(categoryId, allCategories);
|
return getTransactionPrimaryCategoryName(categoryId, allCategories);
|
||||||
},
|
},
|
||||||
@@ -1085,23 +1230,88 @@ export default {
|
|||||||
.transaction-edit-map-view {
|
.transaction-edit-map-view {
|
||||||
height: 300px;
|
height: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.transaction-pictures {
|
||||||
|
min-height: 300px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-height: 700px) {
|
@media (min-height: 700px) {
|
||||||
.transaction-edit-map-view {
|
.transaction-edit-map-view {
|
||||||
height: 350px;
|
height: 350px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.transaction-pictures {
|
||||||
|
min-height: 350px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-height: 800px) {
|
@media (min-height: 800px) {
|
||||||
.transaction-edit-map-view {
|
.transaction-edit-map-view {
|
||||||
height: 450px;
|
height: 450px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.transaction-pictures {
|
||||||
|
min-height: 450px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-height: 900px) {
|
@media (min-height: 900px) {
|
||||||
.transaction-edit-map-view {
|
.transaction-edit-map-view {
|
||||||
height: 550px;
|
height: 550px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: 960px) {
|
||||||
|
.transaction-pictures {
|
||||||
|
min-height: 550px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction-picture .picture-control-icon {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction-picture .picture-control-icon > i.v-icon {
|
||||||
|
background-color: transparent;
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction-picture:hover .picture-control-icon,
|
||||||
|
.transaction-picture .picture-control-icon.show-control-icon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction-picture:hover .transaction-picture-placeholder {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction-picture-add {
|
||||||
|
border: 2px dashed rgba(var(--v-theme-grey-500));
|
||||||
|
|
||||||
|
.transaction-picture-add-icon {
|
||||||
|
color: rgba(var(--v-theme-grey-500));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction-picture-add.enabled:hover {
|
||||||
|
border: 2px dashed rgba(var(--v-theme-grey-700));
|
||||||
|
|
||||||
|
.transaction-picture-add-icon {
|
||||||
|
color: rgba(var(--v-theme-grey-700));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -328,6 +328,42 @@
|
|||||||
</template>
|
</template>
|
||||||
</f7-list-item>
|
</f7-list-item>
|
||||||
|
|
||||||
|
<f7-list-item
|
||||||
|
link="#" no-chevron
|
||||||
|
:header="$t('Pictures')"
|
||||||
|
v-if="showTransactionPictures || (transaction.pictures && transaction.pictures.length > 0)"
|
||||||
|
>
|
||||||
|
<template #footer>
|
||||||
|
<f7-block class="margin-top-half no-padding no-margin" :class="{ 'readonly': submitting || uploadingPicture || removingPictureId }">
|
||||||
|
<swiper-container
|
||||||
|
:pagination="false"
|
||||||
|
:space-between="10"
|
||||||
|
:slides-per-view="'auto'"
|
||||||
|
class="transaction-pictures"
|
||||||
|
>
|
||||||
|
<swiper-slide class="transaction-picture-container" :key="picIdx"
|
||||||
|
v-for="(pictureInfo, picIdx) in transaction.pictures"
|
||||||
|
@click="viewOrRemovePicture(pictureInfo)">
|
||||||
|
<div class="transaction-picture">
|
||||||
|
<div class="display-flex justify-content-center align-items-center transaction-picture-control-backdrop"
|
||||||
|
v-if="mode === 'add' || mode === 'edit'">
|
||||||
|
<f7-icon class="picture-control-icon picture-remove-icon" f7="trash" v-if="pictureInfo.pictureId !== removingPictureId"></f7-icon>
|
||||||
|
<f7-preloader color="white" :size="28" v-if="pictureInfo.pictureId === removingPictureId" />
|
||||||
|
</div>
|
||||||
|
<img alt="picture" :src="getTransactionPictureUrl(pictureInfo)"/>
|
||||||
|
</div>
|
||||||
|
</swiper-slide>
|
||||||
|
<swiper-slide @click="showOpenPictureDialog" v-if="canAddTransactionPicture">
|
||||||
|
<div class="display-flex justify-content-center align-items-center transaction-picture transaction-picture-add">
|
||||||
|
<f7-icon class="picture-control-icon" f7="plus" v-if="!uploadingPicture"></f7-icon>
|
||||||
|
<f7-preloader :size="28" v-if="uploadingPicture" />
|
||||||
|
</div>
|
||||||
|
</swiper-slide>
|
||||||
|
</swiper-container>
|
||||||
|
</f7-block>
|
||||||
|
</template>
|
||||||
|
</f7-list-item>
|
||||||
|
|
||||||
<f7-list-input
|
<f7-list-input
|
||||||
type="textarea"
|
type="textarea"
|
||||||
class="transaction-edit-comment"
|
class="transaction-edit-comment"
|
||||||
@@ -363,6 +399,9 @@
|
|||||||
<f7-actions-button v-if="transaction.hideAmount" @click="transaction.hideAmount = false">{{ $t('Show Amount') }}</f7-actions-button>
|
<f7-actions-button v-if="transaction.hideAmount" @click="transaction.hideAmount = false">{{ $t('Show Amount') }}</f7-actions-button>
|
||||||
<f7-actions-button v-if="!transaction.hideAmount" @click="transaction.hideAmount = true">{{ $t('Hide Amount') }}</f7-actions-button>
|
<f7-actions-button v-if="!transaction.hideAmount" @click="transaction.hideAmount = true">{{ $t('Hide Amount') }}</f7-actions-button>
|
||||||
</f7-actions-group>
|
</f7-actions-group>
|
||||||
|
<f7-actions-group v-if="type === 'transaction' && (mode === 'add' || mode === 'edit') && isTransactionPicturesEnabled && !showTransactionPictures">
|
||||||
|
<f7-actions-button @click="showTransactionPictures = true">{{ $t('Add Picture') }}</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>
|
||||||
@@ -373,6 +412,11 @@
|
|||||||
<span class="tabbar-primary-link">{{ $t(saveButtonTitle) }}</span>
|
<span class="tabbar-primary-link">{{ $t(saveButtonTitle) }}</span>
|
||||||
</f7-link>
|
</f7-link>
|
||||||
</f7-toolbar>
|
</f7-toolbar>
|
||||||
|
|
||||||
|
<f7-photo-browser ref="pictureBrowser" type="popup" navbar-of-text="/"
|
||||||
|
:theme="isDarkMode ? 'dark' : 'light'" :navbar-show-count="true" :exposition="false"
|
||||||
|
:photos="transactionPictures" :thumbs="transactionThumbs" />
|
||||||
|
<input ref="pictureInput" type="file" style="display: none" :accept="supportedImageExtensions" @change="uploadPicture($event)" />
|
||||||
</f7-page>
|
</f7-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -387,11 +431,13 @@ import { useTransactionsStore } from '@/stores/transaction.js';
|
|||||||
import { useTransactionTemplatesStore } from '@/stores/transactionTemplate.js';
|
import { useTransactionTemplatesStore } from '@/stores/transactionTemplate.js';
|
||||||
import { useExchangeRatesStore } from '@/stores/exchangeRates.js';
|
import { useExchangeRatesStore } from '@/stores/exchangeRates.js';
|
||||||
|
|
||||||
|
import fileConstants from '@/consts/file.js';
|
||||||
import categoryConstants from '@/consts/category.js';
|
import categoryConstants from '@/consts/category.js';
|
||||||
import transactionConstants from '@/consts/transaction.js';
|
import transactionConstants from '@/consts/transaction.js';
|
||||||
import templateConstants from '@/consts/template.js';
|
import templateConstants from '@/consts/template.js';
|
||||||
import logger from '@/lib/logger.js';
|
import logger from '@/lib/logger.js';
|
||||||
import {
|
import {
|
||||||
|
isArray,
|
||||||
getNameByKeyValue
|
getNameByKeyValue
|
||||||
} from '@/lib/common.js';
|
} from '@/lib/common.js';
|
||||||
import {
|
import {
|
||||||
@@ -407,8 +453,11 @@ import {
|
|||||||
getTransactionSecondaryCategoryName,
|
getTransactionSecondaryCategoryName,
|
||||||
getFirstAvailableCategoryId
|
getFirstAvailableCategoryId
|
||||||
} from '@/lib/category.js';
|
} from '@/lib/category.js';
|
||||||
import { getMapProvider } from '@/lib/server_settings.js';
|
|
||||||
import { setTransactionModelByTransaction } from '@/lib/transaction.js';
|
import { setTransactionModelByTransaction } from '@/lib/transaction.js';
|
||||||
|
import {
|
||||||
|
isTransactionPicturesEnabled,
|
||||||
|
getMapProvider
|
||||||
|
} from '@/lib/server_settings.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: [
|
props: [
|
||||||
@@ -430,6 +479,8 @@ export default {
|
|||||||
loadingError: null,
|
loadingError: null,
|
||||||
geoLocationStatus: null,
|
geoLocationStatus: null,
|
||||||
submitting: false,
|
submitting: false,
|
||||||
|
uploadingPicture: false,
|
||||||
|
removingPictureId: null,
|
||||||
isSupportGeoLocation: !!navigator.geolocation,
|
isSupportGeoLocation: !!navigator.geolocation,
|
||||||
showTimeInDefaultTimezone: false,
|
showTimeInDefaultTimezone: false,
|
||||||
showGeoLocationActionSheet: false,
|
showGeoLocationActionSheet: false,
|
||||||
@@ -442,11 +493,15 @@ export default {
|
|||||||
showTransactionDateTimeSheet: false,
|
showTransactionDateTimeSheet: false,
|
||||||
showTransactionScheduledFrequencySheet: false,
|
showTransactionScheduledFrequencySheet: false,
|
||||||
showGeoLocationMapSheet: false,
|
showGeoLocationMapSheet: false,
|
||||||
showTransactionTagSheet: false
|
showTransactionTagSheet: false,
|
||||||
|
showTransactionPictures: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapStores(useSettingsStore, useUserStore, useAccountsStore, useTransactionCategoriesStore, useTransactionTagsStore, useTransactionsStore, useTransactionTemplatesStore, useExchangeRatesStore),
|
...mapStores(useSettingsStore, useUserStore, useAccountsStore, useTransactionCategoriesStore, useTransactionTagsStore, useTransactionsStore, useTransactionTemplatesStore, useExchangeRatesStore),
|
||||||
|
isDarkMode() {
|
||||||
|
return this.$root.isDarkMode;
|
||||||
|
},
|
||||||
title() {
|
title() {
|
||||||
if (this.type === 'transaction') {
|
if (this.type === 'transaction') {
|
||||||
if (this.mode === 'add') {
|
if (this.mode === 'add') {
|
||||||
@@ -565,6 +620,9 @@ export default {
|
|||||||
allTagsMap() {
|
allTagsMap() {
|
||||||
return this.transactionTagsStore.allTransactionTagsMap;
|
return this.transactionTagsStore.allTransactionTagsMap;
|
||||||
},
|
},
|
||||||
|
supportedImageExtensions() {
|
||||||
|
return fileConstants.supportedImageExtensions;
|
||||||
|
},
|
||||||
hasAvailableExpenseCategories() {
|
hasAvailableExpenseCategories() {
|
||||||
if (!this.allCategories || !this.allCategories[this.allCategoryTypes.Expense] || !this.allCategories[this.allCategoryTypes.Expense].length) {
|
if (!this.allCategories || !this.allCategories[this.allCategoryTypes.Expense] || !this.allCategories[this.allCategoryTypes.Expense].length) {
|
||||||
return false;
|
return false;
|
||||||
@@ -687,6 +745,34 @@ export default {
|
|||||||
return this.$t('No Location');
|
return this.$t('No Location');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
transactionPictures() {
|
||||||
|
const thumbs = [];
|
||||||
|
|
||||||
|
if (!this.transaction.pictures || !this.transaction.pictures.length) {
|
||||||
|
return thumbs;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < this.transaction.pictures.length; i++) {
|
||||||
|
thumbs.push({
|
||||||
|
url: this.getTransactionPictureUrl(this.transaction.pictures[i])
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return thumbs;
|
||||||
|
},
|
||||||
|
transactionThumbs() {
|
||||||
|
const thumbs = [];
|
||||||
|
|
||||||
|
if (!this.transaction.pictures || !this.transaction.pictures.length) {
|
||||||
|
return thumbs;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < this.transaction.pictures.length; i++) {
|
||||||
|
thumbs.push(this.getTransactionPictureUrl(this.transaction.pictures[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return thumbs;
|
||||||
|
},
|
||||||
allowedMinAmount() {
|
allowedMinAmount() {
|
||||||
return transactionConstants.minAmountNumber;
|
return transactionConstants.minAmountNumber;
|
||||||
},
|
},
|
||||||
@@ -696,6 +782,16 @@ export default {
|
|||||||
showAccountBalance() {
|
showAccountBalance() {
|
||||||
return this.settingsStore.appSettings.showAccountBalance;
|
return this.settingsStore.appSettings.showAccountBalance;
|
||||||
},
|
},
|
||||||
|
isTransactionPicturesEnabled() {
|
||||||
|
return isTransactionPicturesEnabled();
|
||||||
|
},
|
||||||
|
canAddTransactionPicture() {
|
||||||
|
if (this.type !== 'transaction' || (this.mode !== 'add' && this.mode !== 'edit')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !isArray(this.transaction.pictures) || this.transaction.pictures.length < 10;
|
||||||
|
},
|
||||||
mapProvider() {
|
mapProvider() {
|
||||||
return getMapProvider();
|
return getMapProvider();
|
||||||
},
|
},
|
||||||
@@ -1062,6 +1158,86 @@ export default {
|
|||||||
this.transaction.destinationAmount = oldSourceAmount;
|
this.transaction.destinationAmount = oldSourceAmount;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
showOpenPictureDialog() {
|
||||||
|
if (!this.canAddTransactionPicture || this.submitting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$refs.pictureInput.click();
|
||||||
|
},
|
||||||
|
uploadPicture(event) {
|
||||||
|
if (!event || !event.target || !event.target.files || !event.target.files.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const self = this;
|
||||||
|
const pictureFile = event.target.files[0];
|
||||||
|
|
||||||
|
event.target.value = null;
|
||||||
|
|
||||||
|
self.uploadingPicture = true;
|
||||||
|
self.submitting = true;
|
||||||
|
|
||||||
|
self.transactionsStore.uploadTransactionPicture({ pictureFile }).then(response => {
|
||||||
|
if (!isArray(self.transaction.pictures)) {
|
||||||
|
self.transaction.pictures = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
self.transaction.pictures.push(response);
|
||||||
|
|
||||||
|
self.uploadingPicture = false;
|
||||||
|
self.submitting = false;
|
||||||
|
}).catch(error => {
|
||||||
|
self.uploadingPicture = false;
|
||||||
|
self.submitting = false;
|
||||||
|
|
||||||
|
if (!error.processed) {
|
||||||
|
self.$toast(error.message || error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
viewOrRemovePicture(pictureInfo) {
|
||||||
|
if (this.mode !== 'add' && this.mode !== 'edit' && this.transaction.pictures && this.transaction.pictures.length) {
|
||||||
|
this.$refs.pictureBrowser.open();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
self.$confirm('Are you sure you want to remove this transaction picture?', () => {
|
||||||
|
self.removingPictureId = pictureInfo.pictureId;
|
||||||
|
self.submitting = true;
|
||||||
|
|
||||||
|
self.transactionsStore.removeUnusedTransactionPicture({ pictureInfo }).then(response => {
|
||||||
|
if (response && isArray(self.transaction.pictures)) {
|
||||||
|
for (let i = 0; i < self.transaction.pictures.length; i++) {
|
||||||
|
if (self.transaction.pictures[i].pictureId === pictureInfo.pictureId) {
|
||||||
|
self.transaction.pictures.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.removingPictureId = '';
|
||||||
|
self.submitting = false;
|
||||||
|
}).catch(error => {
|
||||||
|
if (error.error && error.error.errorCode === 211001) {
|
||||||
|
for (let i = 0; i < self.transaction.pictures.length; i++) {
|
||||||
|
if (self.transaction.pictures[i].pictureId === pictureInfo.pictureId) {
|
||||||
|
self.transaction.pictures.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (!error.processed) {
|
||||||
|
self.$toast(error.message || error);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.removingPictureId = '';
|
||||||
|
self.submitting = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getTransactionPictureUrl(pictureInfo) {
|
||||||
|
return this.transactionsStore.getTransactionPictureUrl(pictureInfo);
|
||||||
|
},
|
||||||
getFontClassByAmount(amount) {
|
getFontClassByAmount(amount) {
|
||||||
if (amount >= 100000000 || amount <= -100000000) {
|
if (amount >= 100000000 || amount <= -100000000) {
|
||||||
return 'ebk-small-amount';
|
return 'ebk-small-amount';
|
||||||
@@ -1130,4 +1306,51 @@ export default {
|
|||||||
.transaction-edit-tag > .chip-media {
|
.transaction-edit-tag > .chip-media {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.transaction-pictures {
|
||||||
|
height: 128px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction-picture-container,
|
||||||
|
.transaction-picture {
|
||||||
|
width: 128px;
|
||||||
|
height: 128px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction-picture .transaction-picture-control-backdrop {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10;
|
||||||
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction-picture .picture-control-icon {
|
||||||
|
z-index: 15;
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction-picture .picture-remove-icon {
|
||||||
|
background-color: transparent;
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction-picture > img {
|
||||||
|
object-fit: cover;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transaction-picture-add {
|
||||||
|
width: 126px;
|
||||||
|
height: 124px;
|
||||||
|
border: 2px dashed #ccc;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user