Compare commits

...

89 Commits

Author SHA1 Message Date
Alf Sebastian Houge
2eb78ab73c Merge pull request #67 from AlfHou/AlfHou-patch-1
Update Dockerfile
2023-07-20 00:57:24 +02:00
Alf Sebastian Houge
edf4647549 Update Dockerfile 2023-07-20 00:57:15 +02:00
Alf Sebastian Houge
e87a348b90 Merge pull request #66 from AlfHou/AlfHou-patch-1
Add autoconf dependency to dockerfile
2023-07-20 00:48:13 +02:00
Alf Sebastian Houge
c15b22c71a Update Dockerfile 2023-07-20 00:47:52 +02:00
Alf Sebastian Houge
1c9f9c7803 Merge pull request #65 from AlfHou/AlfHou-patch-1
Update hub.yml
2023-07-20 00:40:23 +02:00
Alf Sebastian Houge
89bdfdefd4 Update hub.yml 2023-07-20 00:40:13 +02:00
Alf Sebastian Houge
2974fd783f Merge pull request #64 from AlfHou/AlfHou-patch-1
Fix typo in hub.yml
2023-07-20 00:37:42 +02:00
Alf Sebastian Houge
bd22b5a497 Fix typo in hub.yml 2023-07-20 00:36:08 +02:00
Alf Sebastian Houge
8f6408a92b Merge pull request #63 from AlfHou/AlfHou-patch-1
Update github workflow for publishing releases
2023-07-20 00:30:13 +02:00
Alf Sebastian Houge
20bc28fffa Update workflow for publishing releases 2023-07-20 00:28:44 +02:00
Alf Sebastian Houge
3c89e75a34 Merge pull request #62 from AlfHou/AlfHou-patch-2
Update Dockerfile
2023-07-19 23:27:46 +02:00
Alf Sebastian Houge
5594356166 Update Dockerfile 2023-07-19 23:25:20 +02:00
Alf Sebastian Houge
cea08a59be Merge pull request #61 from AlfHou/AlfHou-patch-2
Update Dockerfile
2023-07-19 23:23:22 +02:00
Alf Sebastian Houge
f16ed1a39f Update Dockerfile 2023-07-19 23:23:03 +02:00
Alf Sebastian Houge
66032fcf55 Merge pull request #60 from AlfHou/AlfHou-patch-2
Update Dockerfile
2023-07-19 15:46:55 +02:00
Alf Sebastian Houge
34a9d56726 Update Dockerfile 2023-07-19 15:45:30 +02:00
Alf Sebastian Houge
a0880ad5b6 Merge pull request #58 from AlfHou/AlfHou-patch-2
Update go and npm version in Dockerfile
2023-07-19 11:24:11 +02:00
Alf Sebastian Houge
17e8e5914e Update go and npm version in Dockerfile 2023-07-19 11:19:15 +02:00
Alf Sebastian Houge
a14f298822 Merge pull request #56 from AlfHou/feat/copy-odometer-to-fillup
Copy last odometer value to new fillup
2023-07-18 22:50:33 +02:00
Alf Sebastian Houge
afe4078897 Copy last odometer value to new fillup 2023-07-14 16:25:47 +02:00
Alf Sebastian Houge
01f9b455cf Merge pull request #55 from AlfHou/AlfHou-patch-1
Update README.md
2023-07-14 10:28:31 +02:00
Alf Sebastian Houge
c9c06f865c Update README.md 2023-07-14 10:26:40 +02:00
Alf Sebastian Houge
e2c14afc99 Merge pull request #49 from alienp4nda/feat/generic-import
Feat/generic import
2023-04-27 08:51:13 +02:00
Alex H
415d0abc83 removed references to json, clearly stated import is only for fillups 2023-04-21 20:47:16 -04:00
Alex H
d32fd8073d fixed tank full radio not highlighting when selected 2023-04-19 20:23:53 -04:00
Alex H
d6eab70ca6 string value of not/filled is now case insensitive 2023-04-19 02:36:00 +00:00
Alex H
cc82536970 fixed error in if logic which caused all fields to be returned as true 2023-04-19 02:24:28 +00:00
Alex H
094cf0d7c9 added the ability to clear non-required fields, added a new icon 2023-04-17 22:19:01 -04:00
Alex H
24f295c632 forgot to remove a print statement 2023-04-17 21:17:32 -04:00
Alex H
e9812e7e27 fixed dateLayout, fixed variable order in call to ParseInLocation, code formatting 2023-04-17 21:10:31 -04:00
Alex H
785ff9a089 improper totalAmount comparison, wrong case on object prop 2023-04-17 21:08:20 -04:00
Alex H
b99c3921d7 added logic to convert numbers to booleans, fixed capitalization issues 2023-04-17 21:43:23 +00:00
Alex H
d64777dca6 fixed if statement, if some npm warnings 2023-04-17 00:59:18 -04:00
Alex H
5208437ec2 initial generic import backend code 2023-04-16 05:59:32 +00:00
Alex H
654087b990 dates to ISO strings, getTimeZone gets timezone as string 2023-04-16 05:58:55 +00:00
Alex H
d294db34fc replace v-if with a v-else, marked all required fields required 2023-04-14 23:57:49 -04:00
Alex H
9f9f90fd1d handling isFullTankString and making post request to api 2023-04-14 23:20:44 -04:00
Alex H
051e3476a7 csvToJson is mostly complete 2023-04-14 20:45:12 -04:00
Alex H
845dcb242a renamed 4 variables to better describe what they're for, fixed if logic 2023-04-14 19:07:32 -04:00
Alex H
df165dae6e added full tank string value inputs 2023-04-14 01:54:03 -04:00
Alex H
e389a9ac2a forgot to commit package.json after I added papaparse 2023-04-13 22:57:01 -04:00
Alex H
e2e4169787 user selects if they track full tank or partial fillup 2023-04-13 22:55:09 -04:00
Alex H
2a8325c6ce initial server side processing 2023-04-13 17:52:13 +00:00
Alex H
cd2e9ebc61 first interation of a generic csv importer 2023-04-13 07:32:24 +00:00
Alex H
1ac3a8b31b import-generic view and route added 2023-04-13 07:13:17 +00:00
Alf Sebastian Houge
f07922763b Merge pull request #41 from AlfHou/chore/change-go-module-name
Change go module name
2023-02-16 22:38:13 +01:00
Alf Sebastian Houge
9da21b2192 Change go module name 2023-02-16 22:35:03 +01:00
Alf Sebastian Houge
a8c85bcd7d Merge pull request #34 from AlfHou/chore/update-dependencies
Chore/update dependencies
2023-02-12 21:06:27 +01:00
Alf Sebastian Houge
d597a4ed30 Fix sizing issues in font awesome icons 2023-02-12 17:38:26 +01:00
Alf Sebastian Houge
45456280b4 Fix scss warnings 2023-02-07 21:00:20 +01:00
Alf Sebastian Houge
1d5794e344 Update lint staged 2023-02-07 20:36:08 +01:00
Alf Sebastian Houge
cd558ba744 Update buefy 2023-02-07 20:34:51 +01:00
Alf Sebastian Houge
3a2c82c789 Fix warnings and translation errors in user creator 2023-02-05 00:39:21 +01:00
Alf Sebastian Houge
d196536d74 Update vue packages 2023-02-04 23:35:07 +01:00
Alf Sebastian Houge
630a7f2ec6 Update vuex and vue router 2023-02-04 23:28:09 +01:00
Alf Sebastian Houge
f2bc01289a update icon fonts 2023-02-04 22:04:19 +01:00
Alf Sebastian Houge
a8d2b37087 Fix eslint errors 2023-02-04 22:01:27 +01:00
Alf Sebastian Houge
7436399d90 Update eslint 2023-02-04 21:48:16 +01:00
Alf Sebastian Houge
bc3e1f0982 Update stylelint-scss 2023-02-04 21:43:56 +01:00
Alf Sebastian Houge
d429fa34bd Update hygen 2023-02-04 21:43:09 +01:00
Alf Sebastian Houge
5095cb4c61 Update lintstaged 2023-02-04 21:41:48 +01:00
Alf Sebastian Houge
e0df7ee80e Update markdown-cli 2023-02-04 21:38:53 +01:00
Alf Sebastian Houge
431de8c3eb Update sass-loader 2023-02-04 21:36:47 +01:00
Alf Sebastian Houge
41793784ea Update stylelint 2023-02-04 21:34:34 +01:00
Alf Sebastian Houge
85b5ad28bf Update core.js 2023-02-04 21:22:17 +01:00
Alf Sebastian Houge
b386012e13 Use node version 16 and higher 2023-02-04 21:14:26 +01:00
Alf Sebastian Houge
df2d7288df Update caniuse browser list 2023-02-04 15:41:42 +01:00
Alf Sebastian Houge
7a6f796561 Update date-fns 2023-02-04 15:25:32 +01:00
Alf Sebastian Houge
aee52d0594 Update icon fonts 2023-02-04 15:16:54 +01:00
Alf Sebastian Houge
f8b1de8d15 Update eslint dependencies 2023-02-04 14:56:07 +01:00
Alf Sebastian Houge
a7896340e1 Update express 2023-02-04 14:03:37 +01:00
Alf Sebastian Houge
f2a3bb2e9f Update lint-staged 2023-02-04 14:01:44 +01:00
Alf Sebastian Houge
d343619f13 Update sass 2023-02-04 13:58:56 +01:00
Alf Sebastian Houge
adce0efa8b Update stylelint dependency 2023-02-04 13:57:28 +01:00
Alf Sebastian Houge
fc6f4bc00d Update stylelint-config-recess-order dependency 2023-02-04 13:55:27 +01:00
Alf Sebastian Houge
a16bcf850f Update stylelint-scss 2023-02-04 13:53:51 +01:00
Alf Sebastian Houge
4ace38f8f3 Update currency-formatter 2023-01-31 22:28:05 +01:00
Alf Sebastian Houge
63e330ffb0 Update outdated packages accoording to version locks 2023-01-31 22:07:26 +01:00
Alf Sebastian Houge
fc9796081e Remove yarn from project 2023-01-31 21:53:53 +01:00
Alf Sebastian Houge
440913af9c Merge pull request #26 from AlfHou/fix/clean-vscode-files
Remove .vscode specific files from being tracked
2023-01-30 22:46:57 +01:00
Alf Sebastian Houge
66d01afe6e Merge pull request #25 from AlfHou/helm
Add notes about Helm/Kubernetes
2023-01-30 22:39:39 +01:00
Alf Sebastian Houge
ad4a399dc8 Remove .vscode specific files 2023-01-30 22:18:36 +01:00
Jonathan Gazeley
2137bf7702 Add notes about Helm/Kubernetes 2023-01-30 21:15:41 +00:00
Alf Sebastian Houge
47bdf7b505 Merge pull request #24 from AlfHou/fix/translation-fixes
Fix 'engine size' field's label
2023-01-30 22:11:36 +01:00
Alf Sebastian Houge
669bffa955 Fix wrong translation for engine size field 2023-01-30 22:06:47 +01:00
Alf Sebastian Houge
05c5381a06 Remove ui .vscode directory 2023-01-30 22:03:23 +01:00
Alf Sebastian Houge
e623e3ad1a Ignore .vscode directory in git 2023-01-30 22:01:19 +01:00
Alf Sebastian Houge
c43a2f639a Merge pull request #23 from AlfHou/update-gitignore
Add vscode specific files to gitignore
2023-01-30 21:56:01 +01:00
Alf Sebastian Houge
d314ed4a16 Add vscode specific files to gitignore 2022-03-05 19:21:10 +01:00
67 changed files with 21338 additions and 25686 deletions

View File

@@ -12,25 +12,18 @@ jobs:
uses: actions/checkout@v2
- name: Set up QEMU
id: qemu
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v2
with:
platforms: linux/amd64,linux/arm64,linux/arm/v7
- name: Available platforms
run: echo ${{ steps.qemu.outputs.platforms }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Set up build cache
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
uses: docker/setup-buildx-action@v2
- name: Parse the git tag
id: get_tag
run: echo ::set-output name=TAG::$(echo $GITHUB_REF | cut -d / -f 3)
- name: Login to DockerHub
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
@@ -41,12 +34,10 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile
#platforms: linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64
#platforms: linux/amd64,linux/arm64,linux/arm/v6,linux/arm/v7
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
# cache-from: type=local,src=/tmp/.buildx-cache

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
# Don't track .vscode directory
.vscode
!.vscode/launch.json

View File

@@ -1,4 +1,4 @@
ARG GO_VERSION=1.16.2
ARG GO_VERSION=1.20.6
FROM golang:${GO_VERSION}-alpine AS builder
RUN apk update && apk add alpine-sdk git && rm -rf /var/cache/apk/*
RUN mkdir -p /api
@@ -9,9 +9,10 @@ RUN go mod download
COPY ./server .
RUN go build -o ./app ./main.go
FROM node:14 as build-stage
FROM node:16-alpine as build-stage
WORKDIR /app
COPY ./ui/package*.json ./
RUN apk add --no-cache autoconf automake
RUN npm install
COPY ./ui .
RUN npm run build

View File

@@ -1,6 +1,5 @@
<p align="center">
<h1 align="center" style="margin-bottom:0">Hammond</h1>
<p align="center">Current Version - 2022.07.06</p>
<p align="center">
A self-hosted vehicle expense tracking system with support for multiple users.
@@ -116,6 +115,21 @@ services:
docker-compose up -d
```
### Install on Kubernetes
You can install Hammond on Kubernetes by using Helm. The
[Helm chart for Hammond](https://github.com/djjudas21/charts/tree/main/charts/hammond)
is maintained by djjudas21.
Check out the default [`values.yaml`](https://github.com/djjudas21/charts/blob/main/charts/hammond/values.yaml)
to see what you can override.
```console
helm repo add djjudas21 https://djjudas21.github.io/charts/
helm repo update djjudas21
helm install djjudas21/hammond
```
### Build from Source / Ubuntu Installation
Although personally I feel that using the docker container is the best way of using

3
server/.gitignore vendored
View File

@@ -14,6 +14,7 @@
# MS VSCode
.vscode
!.vscode/launch.json
__debug_bin
# Dependency directories (remove the comment below to include it)
@@ -22,4 +23,4 @@ assets/*
keys/*
backups/*
nodemon.json
dist/*
dist/*

View File

@@ -7,7 +7,8 @@ import (
"os"
"time"
"github.com/akhilrex/hammond/db"
"hammond/db"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"

View File

@@ -7,10 +7,11 @@ import (
"os"
"strings"
"github.com/akhilrex/hammond/common"
"github.com/akhilrex/hammond/db"
"github.com/akhilrex/hammond/models"
"github.com/akhilrex/hammond/service"
"hammond/common"
"hammond/db"
"hammond/models"
"hammond/service"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
)

View File

@@ -5,10 +5,11 @@ import (
"net/http"
"os"
"github.com/akhilrex/hammond/common"
"github.com/akhilrex/hammond/db"
"github.com/akhilrex/hammond/models"
"github.com/akhilrex/hammond/service"
"hammond/common"
"hammond/db"
"hammond/models"
"hammond/service"
"github.com/gin-gonic/gin"
)

View File

@@ -4,13 +4,16 @@ import (
"net/http"
"strconv"
"github.com/akhilrex/hammond/service"
"hammond/models"
"hammond/service"
"github.com/gin-gonic/gin"
)
func RegisteImportController(router *gin.RouterGroup) {
router.POST("/import/fuelly", fuellyImport)
router.POST("/import/drivvo", drivvoImport)
router.POST("/import/generic", genericImport)
}
func fuellyImport(c *gin.Context) {
@@ -51,3 +54,21 @@ func drivvoImport(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{})
}
func genericImport(c *gin.Context) {
var json models.ImportData
if err := c.ShouldBindJSON(&json); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if json.VehicleId == "" {
c.JSON(http.StatusUnprocessableEntity, "Missing Vehicle ID")
return
}
errors := service.GenericImport(json, c.MustGet("userId").(string))
if len(errors) > 0 {
c.JSON(http.StatusUnprocessableEntity, gin.H{"errors": errors})
return
}
c.JSON(http.StatusOK, gin.H{})
}

View File

@@ -3,10 +3,11 @@ package controllers
import (
"net/http"
"github.com/akhilrex/hammond/common"
"github.com/akhilrex/hammond/db"
"github.com/akhilrex/hammond/models"
"github.com/akhilrex/hammond/service"
"hammond/common"
"hammond/db"
"hammond/models"
"hammond/service"
"github.com/gin-gonic/gin"
)

View File

@@ -5,7 +5,8 @@ import (
"os"
"strings"
"github.com/akhilrex/hammond/db"
"hammond/db"
"github.com/dgrijalva/jwt-go"
"github.com/dgrijalva/jwt-go/request"
"github.com/gin-gonic/gin"

View File

@@ -3,9 +3,10 @@ package controllers
import (
"net/http"
"github.com/akhilrex/hammond/common"
"github.com/akhilrex/hammond/models"
"github.com/akhilrex/hammond/service"
"hammond/common"
"hammond/models"
"hammond/service"
"github.com/gin-gonic/gin"
)

View File

@@ -4,10 +4,11 @@ import (
"fmt"
"net/http"
"github.com/akhilrex/hammond/common"
"github.com/akhilrex/hammond/db"
"github.com/akhilrex/hammond/models"
"github.com/akhilrex/hammond/service"
"hammond/common"
"hammond/db"
"hammond/models"
"hammond/service"
"github.com/gin-gonic/gin"
)

View File

@@ -3,10 +3,11 @@ package controllers
import (
"net/http"
"github.com/akhilrex/hammond/common"
"github.com/akhilrex/hammond/db"
"github.com/akhilrex/hammond/models"
"github.com/akhilrex/hammond/service"
"hammond/common"
"hammond/db"
"hammond/models"
"hammond/service"
"github.com/gin-gonic/gin"
)

View File

@@ -4,9 +4,10 @@ import (
"errors"
"net/http"
"github.com/akhilrex/hammond/common"
"github.com/akhilrex/hammond/models"
"github.com/akhilrex/hammond/service"
"hammond/common"
"hammond/models"
"hammond/service"
"github.com/gin-gonic/gin"
)

View File

@@ -1,4 +1,4 @@
module github.com/akhilrex/hammond
module hammond
go 1.16

View File

@@ -5,9 +5,10 @@ import (
"log"
"os"
"github.com/akhilrex/hammond/controllers"
"github.com/akhilrex/hammond/db"
"github.com/akhilrex/hammond/service"
"hammond/controllers"
"hammond/db"
"hammond/service"
"github.com/gin-contrib/location"
"github.com/gin-gonic/contrib/static"
"github.com/gin-gonic/gin"

View File

@@ -3,7 +3,7 @@ package models
import (
"time"
"github.com/akhilrex/hammond/db"
"hammond/db"
)
type CreateAlertModel struct {

View File

@@ -1,6 +1,6 @@
package models
import "github.com/akhilrex/hammond/db"
import "hammond/db"
type LoginResponse struct {
Name string `json:"name"`

22
server/models/import.go Normal file
View File

@@ -0,0 +1,22 @@
package models
type ImportData struct {
Data []ImportFillup `json:"data" binding:"required"`
VehicleId string `json:"vehicleId" binding:"required"`
TimeZone string `json:"timezone" binding:"required"`
}
type ImportFillup struct {
VehicleID string `json:"vehicleId"`
FuelQuantity float32 `json:"fuelQuantity"`
PerUnitPrice float32 `json:"perUnitPrice"`
TotalAmount float32 `json:"totalAmount"`
OdoReading int `json:"odoReading"`
IsTankFull *bool `json:"isTankFull"`
HasMissedFillup *bool `json:"hasMissedFillup"`
Comments string `json:"comments"`
FillingStation string `json:"fillingStation"`
UserID string `json:"userId"`
Date string `json:"date"`
FuelSubType string `json:"fuelSubType"`
}

View File

@@ -1,6 +1,6 @@
package models
import "github.com/akhilrex/hammond/db"
import "hammond/db"
type UpdateSettingModel struct {
Currency string `json:"currency" form:"currency" query:"currency"`

View File

@@ -4,7 +4,7 @@ import (
"encoding/json"
"time"
"github.com/akhilrex/hammond/db"
"hammond/db"
)
type MileageModel struct {

View File

@@ -3,7 +3,8 @@ package models
import (
"time"
"github.com/akhilrex/hammond/db"
"hammond/db"
_ "github.com/go-playground/validator/v10"
)

View File

@@ -4,8 +4,8 @@ import (
"errors"
"time"
"github.com/akhilrex/hammond/db"
"github.com/akhilrex/hammond/models"
"hammond/db"
"hammond/models"
)
func CreateAlert(model models.CreateAlertModel, vehicleId, userId string) (*db.VehicleAlert, error) {

View File

@@ -8,7 +8,7 @@ import (
"strings"
"time"
"github.com/akhilrex/hammond/db"
"hammond/db"
)
func DrivvoParseExpenses(content []byte, user *db.User, vehicle *db.Vehicle) ([]db.Expense, []string) {

View File

@@ -13,9 +13,10 @@ import (
"strconv"
"time"
"github.com/akhilrex/hammond/db"
"github.com/akhilrex/hammond/internal/sanitize"
"github.com/akhilrex/hammond/models"
"hammond/db"
"hammond/internal/sanitize"
"hammond/models"
uuid "github.com/satori/go.uuid"
)

View File

@@ -7,7 +7,8 @@ import (
"strconv"
"time"
"github.com/akhilrex/hammond/db"
"hammond/db"
"github.com/leekchan/accounting"
)

View File

@@ -0,0 +1,47 @@
package service
import (
"hammond/db"
"hammond/models"
"time"
)
func GenericParseRefuelings(content []models.ImportFillup, user *db.User, vehicle *db.Vehicle, timezone string) ([]db.Fillup, []string) {
var errors []string
var fillups []db.Fillup
dateLayout := "2006-01-02T15:04:05.000Z"
loc, _ := time.LoadLocation(timezone)
for _, record := range content {
date, err := time.ParseInLocation(dateLayout, record.Date, loc)
if err != nil {
date = time.Date(2000, time.December, 0, 0, 0, 0, 0, loc)
}
var missedFillup bool
if record.HasMissedFillup == nil {
missedFillup = false
} else {
missedFillup = *record.HasMissedFillup
}
fillups = append(fillups, db.Fillup{
VehicleID: vehicle.ID,
UserID: user.ID,
Date: date,
IsTankFull: record.IsTankFull,
HasMissedFillup: &missedFillup,
FuelQuantity: float32(record.FuelQuantity),
PerUnitPrice: float32(record.PerUnitPrice),
FillingStation: record.FillingStation,
OdoReading: record.OdoReading,
TotalAmount: float32(record.TotalAmount),
FuelUnit: vehicle.FuelUnit,
Currency: user.Currency,
DistanceUnit: user.DistanceUnit,
Comments: record.Comments,
Source: "Generic Import",
})
}
return fillups, errors
}

View File

@@ -3,7 +3,8 @@ package service
import (
"bytes"
"github.com/akhilrex/hammond/db"
"hammond/db"
"hammond/models"
)
func WriteToDB(fillups []db.Fillup, expenses []db.Expense) []string {
@@ -105,3 +106,27 @@ func FuellyImport(content []byte, userId string) []string {
return WriteToDB(fillups, expenses)
}
func GenericImport(content models.ImportData, userId string) []string {
var errors []string
user, err := GetUserById(userId)
if err != nil {
errors = append(errors, err.Error())
return errors
}
vehicle, err := GetVehicleById(content.VehicleId)
if err != nil {
errors = append(errors, err.Error())
return errors
}
var fillups []db.Fillup
fillups, errors = GenericParseRefuelings(content.Data, user, vehicle, content.TimeZone)
if len(errors) != 0 {
return errors
}
return WriteToDB(fillups, nil)
}

View File

@@ -1,7 +1,7 @@
package service
import (
"github.com/akhilrex/hammond/db"
"hammond/db"
)
func CanInitializeSystem() (bool, error) {

View File

@@ -4,9 +4,9 @@ import (
"sort"
"time"
"github.com/akhilrex/hammond/common"
"github.com/akhilrex/hammond/db"
"github.com/akhilrex/hammond/models"
"hammond/common"
"hammond/db"
"hammond/models"
)
func GetMileageByVehicleId(vehicleId string, since time.Time, mileageOption string) (mileage []models.MileageModel, err error) {

View File

@@ -3,8 +3,8 @@ package service
import (
"strings"
"github.com/akhilrex/hammond/db"
"github.com/akhilrex/hammond/models"
"hammond/db"
"hammond/models"
)
func CreateUser(userModel *models.RegisterRequest, role db.Role) error {

View File

@@ -3,8 +3,9 @@ package service
import (
"fmt"
"github.com/akhilrex/hammond/db"
"github.com/akhilrex/hammond/models"
"hammond/db"
"hammond/models"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)

View File

@@ -20,6 +20,7 @@ module.exports = {
'no-console': process.env.PRE_COMMIT
? ['error', { allow: ['warn', 'error'] }]
: 'off',
'vue/multi-word-component-names': 0,
'import/no-relative-parent-imports': 'error',
'import/order': 'error',
'vue/array-bracket-spacing': 'error',

5
ui/.gitignore vendored
View File

@@ -29,3 +29,8 @@ yarn-error.log*
*.njsproj
*.sln
*.sw*
#Vs code files
.vscode
!.vscode/launch.json

View File

@@ -1,30 +0,0 @@
{
"BaseButton": {
"scope": "vue-html",
"prefix": "BaseButton",
"body": ["<BaseButton>", "\t${3}", "</BaseButton>"],
"description": "<BaseButton>"
},
"BaseIcon": {
"scope": "vue-html",
"prefix": "BaseIcon",
"body": ["<BaseIcon name=\"${1}\">", "\t${2}", "</BaseIcon>"],
"description": "<BaseIcon>"
},
"BaseInputText": {
"scope": "vue-html",
"prefix": "BaseInputText",
"body": ["<BaseInputText ${1}/>"],
"description": "<BaseInputText>"
},
"BaseLink": {
"scope": "vue-html",
"prefix": "BaseLink",
"body": [
"<BaseLink ${1|name,:to,href|}=\"${2:route}\">",
"\t${3}",
"</BaseLink>"
],
"description": "<BaseLink>"
}
}

View File

@@ -1,26 +0,0 @@
{
"script": {
"scope": "vue",
"prefix": "script",
"body": ["<script>", "export default {", "\t${0}", "}", "</script>"],
"description": "<script>"
},
"template": {
"scope": "vue",
"prefix": "template",
"body": ["<template>", "\t${0}", "</template>"],
"description": "<template>"
},
"style": {
"scope": "vue",
"prefix": "style",
"body": [
"<style lang=\"scss\" module>",
"@import '@design';",
"",
"${0}",
"</style>"
],
"description": "<style lang=\"scss\" module>"
}
}

View File

@@ -1,37 +0,0 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
// Syntax highlighting and more for .vue files
// https://github.com/vuejs/vetur
"octref.vetur",
// Peek and go-to-definition for .vue files
// https://github.com/fuzinato/vscode-vue-peek
"dariofuzinato.vue-peek",
// Lint-on-save with ESLint
// https://github.com/microsoft/vscode-eslint
"dbaeumer.vscode-eslint",
// Lint-on-save with Stylelint
// https://github.com/stylelint/vscode-stylelint
"stylelint.vscode-stylelint",
// Lint-on-save markdown in README files
// https://github.com/DavidAnson/vscode-markdownlint
"DavidAnson.vscode-markdownlint",
// Format-on-save with Prettier
// https://github.com/prettier/prettier-vscode
"esbenp.prettier-vscode",
// SCSS intellisense
// https://github.com/mrmlnc/vscode-scss
"mrmlnc.vscode-scss",
// Test `.unit.js` files on save with Jest
// https://github.com/jest-community/vscode-jest
"Orta.vscode-jest"
]
}

View File

@@ -1,93 +0,0 @@
{
// ===
// Spacing
// ===
"editor.insertSpaces": true,
"editor.tabSize": 2,
"editor.trimAutoWhitespace": true,
"files.trimTrailingWhitespace": true,
"files.eol": "\n",
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,
// ===
// Files
// ===
"files.exclude": {
"**/*.log": true,
"**/*.log*": true,
"**/dist": true,
"**/coverage": true
},
"files.associations": {
".markdownlintrc": "jsonc"
},
// ===
// Event Triggers
// ===
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.fixAll.stylelint": true,
"source.fixAll.markdownlint": true
},
"eslint.validate": [
"javascript",
"javascriptreact",
"vue",
"vue-html",
"html"
],
"vetur.format.enable": false,
"vetur.completion.scaffoldSnippetSources": {
"user": "🗒️",
"workspace": "💼",
"vetur": ""
},
"prettier.disableLanguages": [],
// ===
// HTML
// ===
"html.format.enable": false,
"vetur.validation.template": false,
"emmet.triggerExpansionOnTab": true,
"emmet.includeLanguages": {
"vue-html": "html"
},
"vetur.completion.tagCasing": "initial",
// ===
// JS(ON)
// ===
"jest.autoEnable": false,
"jest.enableCodeLens": false,
"javascript.format.enable": false,
"json.format.enable": false,
"vetur.validation.script": false,
// ===
// CSS
// ===
"stylelint.enable": true,
"css.validate": false,
"scss.validate": false,
"vetur.validation.style": false,
// ===
// MARKDOWN
// ===
"[markdown]": {
"editor.wordWrap": "wordWrapColumn",
"editor.wordWrapColumn": 80
}
}

View File

@@ -6,10 +6,9 @@ WORKDIR /app
# Copy dependency-related files
COPY package.json ./
COPY yarn.lock ./
# Install project dependencies
RUN yarn install
RUN npm install
# Expose ports 8080, which the dev server will be bound to
EXPOSE 8080

30847
ui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,91 +6,82 @@
"dev": "vue-cli-service serve",
"dev:e2e": "cross-env VUE_APP_TEST=e2e vue-cli-service test:e2e --mode=development",
"build": "vue-cli-service build --modern",
"build:ci": "yarn build --report",
"build:ci": "npm build --report",
"lint:eslint": "eslint --fix",
"lint:stylelint": "stylelint --fix",
"lint:markdownlint": "markdownlint",
"lint:prettier": "prettier --write --loglevel warn",
"lint:all:eslint": "yarn lint:eslint --ext .js,.vue .",
"lint:all:stylelint": "yarn lint:stylelint \"src/**/*.{vue,scss}\"",
"lint:all:markdownlint": "yarn lint:markdownlint \"docs/*.md\" \"*.md\"",
"lint:all:prettier": "yarn lint:prettier \"**/*.{js,json,css,scss,vue,html,md}\"",
"lint": "run-s lint:all:*",
"test:unit": "cross-env VUE_APP_TEST=unit vue-cli-service test:unit",
"test:unit:file": "yarn test:unit --bail --findRelatedTests",
"test:unit:watch": "yarn test:unit --watch --notify --notifyMode change",
"test:unit:ci": "yarn test:unit --coverage --ci",
"test:e2e": "cross-env VUE_APP_TEST=e2e vue-cli-service test:e2e --headless",
"test": "run-s test:unit test:e2e",
"test:ci": "run-s test:unit:ci test:e2e",
"new": "cross-env HYGEN_TMPLS=generators hygen new",
"docs": "vuepress dev",
"docker": "docker-compose exec dev yarn"
"docs": "vuepress dev"
},
"gitHooks": {
"pre-commit": "cross-env PRE_COMMIT=true lint-staged"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.27",
"@fortawesome/free-solid-svg-icons": "^5.12.1",
"@fortawesome/vue-fontawesome": "0.1.9",
"axios": "^0.27.2",
"buefy": "^0.9.7",
"@fortawesome/fontawesome-svg-core": "^6.3.0",
"@fortawesome/free-solid-svg-icons": "^6.3.0",
"@fortawesome/vue-fontawesome": "^2.0.10",
"axios": "^1.3.2",
"buefy": "^0.9.22",
"chart.js": "^2.9.4",
"core-js": "3.6.4",
"currency-formatter": "^1.5.7",
"date-fns": "2.10.0",
"core-js": "^3.27.2",
"currency-formatter": "^1.5.9",
"date-fns": "^2.29.3",
"lodash": "^4.17.21",
"node-gyp": "^9.3.1",
"normalize.css": "8.0.1",
"nprogress": "0.2.0",
"vue": "2.6.11",
"normalize.css": "^8.0.1",
"nprogress": "^0.2.0",
"papaparse": "^5.4.1",
"vue": "^2.6.11",
"vue-chartjs": "^3.5.1",
"vue-i18n": "^8.28.2",
"vue-meta": "2.3.3",
"vue-router": "3.1.6",
"vuex": "3.1.2"
"vue-meta": "^2.4.0",
"vue-router": "^3.6.5",
"vuex": "^3.6.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "4.2.x",
"@vue/cli-plugin-eslint": "4.2.x",
"@vue/cli-plugin-unit-jest": "4.2.x",
"@vue/cli-service": "4.2.x",
"@vue/cli-plugin-babel": "^4.5.19",
"@vue/cli-plugin-eslint": "^4.5.19",
"@vue/cli-plugin-unit-jest": "^4.5.19",
"@vue/cli-service": "^4.5.19",
"@vue/eslint-config-prettier": "6.0.x",
"@vue/eslint-config-standard": "5.1.x",
"@vue/test-utils": "1.0.0-beta.31",
"babel-core": "7.0.0-bridge.0",
"@vue/eslint-config-standard": "^5.1.1",
"@vue/test-utils": "^1.3.4",
"babel-core": "^7.0.0-bridge.0",
"babel-eslint": "10.1.x",
"cross-env": "7.0.x",
"eslint": "6.8.x",
"eslint-plugin-import": "2.20.x",
"eslint-plugin-node": "11.0.x",
"eslint-plugin-promise": "4.2.x",
"eslint-plugin-standard": "4.0.x",
"eslint-plugin-vue": "6.2.x",
"express": "4.17.x",
"hygen": "4.0.x",
"imagemin-lint-staged": "0.4.x",
"lint-staged": "10.0.x",
"markdownlint-cli": "^0.31.1",
"npm-run-all": "4.1.x",
"sass": "1.26.x",
"sass-loader": "8.0.x",
"stylelint": "13.2.x",
"stylelint-config-css-modules": "2.2.x",
"stylelint-config-prettier": "8.0.x",
"stylelint-config-recess-order": "2.0.x",
"stylelint-config-standard": "20.0.x",
"stylelint-scss": "3.14.x",
"vue-template-compiler": "2.6.11",
"vuepress": "1.3.x"
"cross-env": "^7.0.1",
"eslint": "^6.8.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.3.1",
"eslint-plugin-standard": "^5.0.0",
"eslint-plugin-vue": "^9.9.0",
"express": "^4.18.2",
"hygen": "^6.2.11",
"imagemin-lint-staged": "^0.5.1",
"lint-staged": "^13.1.1",
"markdownlint-cli": "^0.33.0",
"npm-run-all": "^4.1.1",
"sass": "^1.58.0",
"sass-loader": "^8.0.2",
"stylelint": "^14.16.1",
"stylelint-config-css-modules": "^4.1.0",
"stylelint-config-prettier": "^9.0.4",
"stylelint-config-standard": "^29.0.0",
"stylelint-scss": "^4.3.0",
"vue-template-compiler": "^2.6.11",
"vuepress": "^1.9.8"
},
"resolutions": {
"@vue/cli-plugin-unit-jest/jest": "25.1.x",
"@vue/cli-plugin-unit-jest/babel-jest": "25.1.x"
},
"engines": {
"node": ">=10.13.3",
"yarn": ">=1.0.0"
"node": ">=16.0.0"
}
}

View File

@@ -7,6 +7,10 @@
<link rel="shortcut icon" href="<%= webpackConfig.output.publicPath %>hammond.png" />
<link rel="apple-touch-icon" href="<%= webpackConfig.output.publicPath %>touch-icon.png" />
<title><%= webpackConfig.name %></title>
<!-- Temporary until fontawesome 6 is supported in buefy (see issue: https://github.com/FortAwesome/Font-Awesome/issues/18663) -->
<style>
.icon svg { width: 1em; height: 1em; max-width: 80%; max-height: 80%; }
</style>
</head>
<body>
<!-- This is where our app will be mounted. -->

View File

@@ -95,11 +95,10 @@ export default {
</div>
<div class="column">
<b-button
tag="input"
tag="button"
native-type="submit"
:disabled="tryingToCreate"
type="is-primary"
:value="this.$t('uploadfile')"
class="control"
>
{{ $t('uploadfile') }}

View File

@@ -50,7 +50,7 @@ export default {
<b-select
v-if="unprocessedQuickEntries.length"
v-model="quickEntry"
:placeholder="this.$t('referquickentry')"
:placeholder="$t('referquickentry')"
expanded
@input="showQuickEntry($event)"
>

View File

@@ -92,7 +92,7 @@ export default {
<div class="box" style="max-width:600px">
<h1 class="subtitle">{{ $t('share') }} {{ vehicle.nickname }}</h1>
<section>
<div class="columns is-mobile" v-for="model in models" :key="model.id">
<div v-for="model in models" :key="model.id" class="columns is-mobile">
<div class="column is-one-third">
<b-field>
<b-switch v-model="model.isShared" :disabled="model.isOwner" @input="changeShareStatus(model)">

View File

@@ -10,10 +10,10 @@ $size-input-padding-vertical: 0.75em;
$size-input-padding-horizontal: 1em;
$size-input-padding: $size-input-padding-vertical $size-input-padding-horizontal;
$size-input-border: 1px;
$size-input-border-radius: (1em + $size-input-padding-vertical * 2) / 10;
$size-input-border-radius: calc((1em + $size-input-padding-vertical * 2) / 10);
// BUTTONS
$size-button-padding-vertical: $size-grid-padding / 2;
$size-button-padding-horizontal: $size-grid-padding / 1.5;
$size-button-padding-vertical: calc($size-grid-padding / 2);
$size-button-padding-horizontal: calc($size-grid-padding / 1.5);
$size-button-padding: $size-button-padding-vertical
$size-button-padding-horizontal;

View File

@@ -147,7 +147,7 @@
$max-screen,
$max-value
) {
$a: ($max-value - $min-value) / ($max-screen - $min-screen);
$a: calc(($max-value - $min-value) / ($max-screen - $min-screen));
$b: $min-value - $a * $min-screen;
$sign: '+';

View File

@@ -137,7 +137,7 @@
"dontimportagain": "Achte darauf, dass du die Datei nicht erneut importierst, da dies zu mehrfachen Einträgen führen würde.",
"checkpointsimportcsv": "Wenn du alle diese Punkte überprüft hast kannst du unten die CSV importieren.",
"importhintunits": "Vergewissere dich ebenfalls, dass die <u>Kraftstoffeinheit</u> und der <u>Kraftstofftyp</u> im Fahrzeug richtig eingestellt sind.",
"importhintcurrdist": "Stelle sicher, dass die <u>Währung</u> und die <u>Entfernungseinheit</u> in Hammond korrekt eingestellt sind. Der Import erkennt die Währung nicht automatisch aus der CSV-Datei, sondern verwendet die für den Benutzer eingestellte Währung.",
"importhintcurrdist": "Stelle sicher, dass die <u>Währung</u> und die <u>Entfernungseinheit</u> in Hammond korrekt eingestellt sind. Der Import erkennt die Währung nicht automatisch aus der datei, sondern verwendet die für den Benutzer eingestellte Währung.",
"importhintnickname": "Vergewissere dich, dass der Fahrzeugname in Hammond genau mit dem Namen in der Fuelly-CSV-Datei übereinstimmt, sonst funktioniert der Import nicht.",
"importhintvehiclecreated": "Vergewissere dich, dass du die Fahrzeuge bereits in Hammond erstellt hast.",
"importhintcreatecsv": "Exportiere deine Daten aus {name} im CSV-Format. Die Schritte dazu findest du",

View File

@@ -5,7 +5,7 @@
"thismonth": "This month",
"pastxdays": "Past one day | Past {count} days",
"pastxmonths": "Past one month | Past {count} months",
"thisyear": "This year",
"thisyear": "This year",
"alltime": "All Time",
"noattachments": "No Attachments so far",
"attachments": "Attachments",
@@ -43,15 +43,15 @@
"createnow": "Create Now",
"yourvehicles": "Your Vehicles",
"menu": {
"quickentries": "Quick Entries",
"logout": "Log out",
"import": "Import",
"home": "Home",
"settings": "Settings",
"admin": "Admin",
"sitesettings": "Site Settings",
"users": "Users",
"login": "Log in"
"quickentries": "Quick Entries",
"logout": "Log out",
"import": "Import",
"home": "Home",
"settings": "Settings",
"admin": "Admin",
"sitesettings": "Site Settings",
"users": "Users",
"login": "Log in"
},
"enterusername": "Enter your username",
"enterpassword": "Enter your password",
@@ -81,34 +81,34 @@
"quantity": "Quantity",
"gasstation": "Gas Station",
"fuel": {
"petrol": "Petrol",
"diesel": "Diesel",
"cng": "CNG",
"lpg": "LPG",
"electric": "Electric",
"ethanol": "Ethanol"
"petrol": "Petrol",
"diesel": "Diesel",
"cng": "CNG",
"lpg": "LPG",
"electric": "Electric",
"ethanol": "Ethanol"
},
"unit": {
"long": {
"litre": "Litre",
"gallon": "Gallon",
"kilowatthour": "Kilowatt Hour",
"kilogram": "Kilogram",
"usgallon": "US Gallon",
"minutes": "Minutes",
"kilometers": "Kilometers",
"miles": "Miles"
},
"short": {
"litre": "Lt",
"gallon": "Gal",
"kilowatthour": "KwH",
"kilogram": "Kg",
"usgallon": "US Gal",
"minutes": "Mins",
"kilometers": "Km",
"miles": "Mi"
}
"long": {
"litre": "Litre",
"gallon": "Gallon",
"kilowatthour": "Kilowatt Hour",
"kilogram": "Kilogram",
"usgallon": "US Gallon",
"minutes": "Minutes",
"kilometers": "Kilometers",
"miles": "Miles"
},
"short": {
"litre": "Lt",
"gallon": "Gal",
"kilowatthour": "KwH",
"kilogram": "Kg",
"usgallon": "US Gal",
"minutes": "Mins",
"kilometers": "Km",
"miles": "Mi"
}
},
"avgfillupqty": "Avg Fillup Qty",
"avgfillupexpense": "Avg Fillup Expense",
@@ -117,7 +117,9 @@
"price": "Price",
"total": "Total",
"fulltank": "Tank Full",
"partialfillup": "Partial Fillup",
"getafulltank": "Did you get a full tank?",
"tankpartialfull": "Which do you track?",
"by": "By",
"expenses": "Expenses",
"expensetype": "Expense Type",
@@ -130,6 +132,8 @@
"importdatadesc": "Choose from the following options to import data into Hammond",
"import": "Import",
"importcsv": "If you have been using {name} to store your vehicle data, export the CSV file from {name} and click here to import.",
"importgeneric": "Generic Fillups Import",
"importgenericdesc": "Fillups CSV import.",
"choosecsv": "Choose CSV",
"choosephoto": "Choose Photo",
"importsuccessfull": "Data Imported Successfully",
@@ -137,13 +141,15 @@
"importfrom": "Import from {0}",
"stepstoimport": "Steps to import data from {name}",
"choosecsvimport": "Choose the {name} CSV and press the import button.",
"choosedatafile": "Choose the CSV file and then press the import button.",
"dontimportagain": "Make sure that you do not import the file again because that will create repeat entries.",
"checkpointsimportcsv": "Once you have checked all these points, just import the CSV below.",
"importhintunits": "Similiarly, make sure that the <u>Fuel Unit</u> and <u>Fuel Type</u> are correctly set in the Vehicle.",
"importhintcurrdist": "Make sure that the <u>Currency</u> and <u>Distance Unit</u> are set correctly in Hammond. Import will not autodetect Currency from the CSV but use the one set for the user.",
"importhintcurrdist": "Make sure that the <u>Currency</u> and <u>Distance Unit</u> are set correctly in Hammond. Import will not autodetect Currency from the file but use the one set for the user.",
"importhintnickname": "Make sure that the Vehicle nickname in Hammond is exactly the same as the name on Fuelly CSV or the import will not work.",
"importhintvehiclecreated": "Make sure that you have already created the vehicles in Hammond platform.",
"importhintcreatecsv": "Export your data from {name} in the CSV format. Steps to do that can be found",
"importgenerichintdata": "Data must be in CSV format.",
"here": "here",
"unprocessedquickentries": "You have one quick entry to be processed. | You have {0} quick entries pending to be processed.",
"show": "Show",
@@ -178,6 +184,7 @@
"fillingstation": "Filling Station Name",
"comments": "Comments",
"missfillupbefore": "Did you miss the fillup entry before this one?",
"missedfillup": "Missed Fillup",
"fillupdate": "Fillup Date",
"fillupsavedsuccessfully": "Fillup Saved Successfully",
"expensesavedsuccessfully": "Expense Saved Successfully",
@@ -195,25 +202,25 @@
"testconn": "Test Connection",
"migrate": "Migrate",
"init": {
"migrateclarkson": "Migrate from Clarkson",
"migrateclarksondesc": "If you have an existing Clarkson deployment and you want to migrate your data from that, press the following button.",
"freshinstall": "Fresh Install",
"freshinstalldesc": "If you want a fresh install of Hammond, press the following button.",
"clarkson": {
"desc": "<p>You need to make sure that this deployment of Hammond can access the MySQL database used by Clarkson.</p><p>If that is not directly possible, you can make a copy of that database somewhere accessible from this instance.</p><p>Once that is done, enter the connection string to the MySQL instance in the following format.</p><p>All the users imported from Clarkson will have their username as their email in Clarkson database and pasword set to<span class='' style='font-weight:bold'>hammond</span></p><code>user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local</code><br/><br/>",
"success": "We have successfully migrated the data from Clarkson. You will be redirected to the login screen shortly where you can login using your existing email and password : hammond"
},
"fresh": {
"setupadminuser": "Setup Admin Users",
"yourpassword": "Your Password",
"youremail": "Your Email",
"yourname": "Your Name",
"success": "You have been registered successfully. You will be redirected to the login screen shortly where you can login and start using the system."
}
"migrateclarkson": "Migrate from Clarkson",
"migrateclarksondesc": "If you have an existing Clarkson deployment and you want to migrate your data from that, press the following button.",
"freshinstall": "Fresh Install",
"freshinstalldesc": "If you want a fresh install of Hammond, press the following button.",
"clarkson": {
"desc": "<p>You need to make sure that this deployment of Hammond can access the MySQL database used by Clarkson.</p><p>If that is not directly possible, you can make a copy of that database somewhere accessible from this instance.</p><p>Once that is done, enter the connection string to the MySQL instance in the following format.</p><p>All the users imported from Clarkson will have their username as their email in Clarkson database and pasword set to<span class='' style='font-weight:bold'>hammond</span></p><code>user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local</code><br/><br/>",
"success": "We have successfully migrated the data from Clarkson. You will be redirected to the login screen shortly where you can login using your existing email and password : hammond"
},
"fresh": {
"setupadminuser": "Setup Admin Users",
"yourpassword": "Your Password",
"youremail": "Your Email",
"yourname": "Your Name",
"success": "You have been registered successfully. You will be redirected to the login screen shortly where you can login and start using the system."
}
},
"roles": {
"ADMIN": "ADMIN",
"USER": "USER"
"ADMIN": "ADMIN",
"USER": "USER"
},
"profile": "Profile",
"processedon": "Processed on",
@@ -221,4 +228,4 @@
"disable": "Disable",
"confirm": "Go Ahead",
"labelforfile": "Label for this file"
}
}

View File

@@ -7,6 +7,7 @@ import {
faCheck,
faTimes,
faArrowUp,
faArrowRotateLeft,
faAngleLeft,
faAngleRight,
faCalendar,
@@ -34,11 +35,11 @@ import '@components/_globals'
import 'buefy/dist/buefy.css'
import 'nprogress/nprogress.css'
Vue.component('vue-fontawesome', FontAwesomeIcon)
library.add(
faCheck,
faTimes,
faArrowUp,
faArrowRotateLeft,
faAngleLeft,
faAngleRight,
faCalendar,
@@ -54,7 +55,8 @@ library.add(
faShare,
faUserFriends,
faTimesCircle
)
);
Vue.component('VueFontawesome', FontAwesomeIcon)
Vue.use(Buefy, {
defaultIconComponent: 'vue-fontawesome',

View File

@@ -419,6 +419,15 @@ export default [
},
props: (route) => ({ user: store.state.auth.currentUser || {} }),
},
{
path: '/import/generic',
name: 'import-generic',
component: () => lazyLoadView(import('@views/import-generic.vue')),
meta: {
authRequired: true,
},
props: (route) => ({ user: store.state.auth.currentUser || {} }),
},
{
path: '/logout',
name: 'logout',

View File

@@ -162,41 +162,41 @@ export default {
</div>
</div>
<form @submit.prevent="createExpense">
<b-field :label="this.$t('selectvehicle')">
<b-select v-model="selectedVehicle" :placeholder="this.$t('vehicle')" required expanded :disabled="expense.id">
<b-field :label="$t('selectvehicle')">
<b-select v-model="selectedVehicle" :placeholder="$t('vehicle')" required expanded :disabled="expense.id">
<option v-for="option in myVehicles" :key="option.id" :value="option">
{{ option.nickname }}
</option>
</b-select>
</b-field>
<b-field :label="this.$t('expenseby')">
<b-select v-model="expenseModel.userId" :placeholder="this.$t('user')" required expanded :disabled="expense.id">
<b-field :label="$t('expenseby')">
<b-select v-model="expenseModel.userId" :placeholder="$t('user')" required expanded :disabled="expense.id">
<option v-for="option in users" :key="option.userId" :value="option.userId">
{{ option.name }}
</option>
</b-select>
</b-field>
<b-field :label="this.$t('expensedate')">
<b-field :label="$t('expensedate')">
<b-datepicker
v-model="expenseModel.date"
:date-formatter="formatDate"
:placeholder="this.$t('clicktoselect')"
:placeholder="$t('clicktoselect')"
icon="calendar"
:max-date="new Date()"
>
</b-datepicker>
</b-field>
<b-field :label="this.$t('expensetype') + `*`">
<b-field :label="$t('expensetype') + `*`">
<b-input v-model="expenseModel.expenseType" expanded required></b-input>
</b-field>
<b-field :label="this.$t('totalamountpaid')">
<b-field :label="$t('totalamountpaid')">
<p class="control">
<span class="button is-static">{{ me.currency }}</span>
</p>
<b-input v-model.number="expenseModel.amount" type="number" min="0" expanded step=".001" required></b-input>
</b-field>
<b-field :label="this.$t('odometer')">
<b-field :label="$t('odometer')">
<p class="control">
<span class="button is-static">{{ $t('unit.short.' + me.distanceUnitDetail.key) }}</span>
</p>
@@ -207,7 +207,7 @@ export default {
<b-switch v-model="showMore">{{ $t('fillmoredetails') }}</b-switch>
</b-field>
<fieldset v-if="showMore">
<b-field :label="this.$t('details')">
<b-field :label="$t('details')">
<b-input v-model="expenseModel.comments" type="textarea" expanded></b-input>
</b-field>
</fieldset>
@@ -216,7 +216,7 @@ export default {
</b-field>
<br />
<b-field>
<b-button tag="input" native-type="submit" :value="this.$t('save')" :disabled="tryingToCreate" type="is-primary" label="Create Expense" expanded> </b-button>
<b-button tag="button" native-type="submit" :value="$t('save')" :disabled="tryingToCreate" type="is-primary" label="Create Expense" expanded/>
</b-field>
</form>
</Layout>

View File

@@ -76,6 +76,9 @@ export default {
this.fetchVehicleFuelSubTypes()
if (!this.fillup.id) {
this.fillupModel = this.getEmptyFillup()
if (this.vehicle.fillups.length > 0) {
this.fillupModel.odoReading = this.vehicle.fillups[0].odoReading
}
this.fillupModel.userId = this.me.id
}
},
@@ -193,21 +196,21 @@ export default {
</div>
</div>
<form class="" @submit.prevent="createFillup">
<b-field :label="this.$t('selectvehicle')">
<b-select v-model="selectedVehicle" :placeholder="this.$t('vehicle')" required expanded :disabled="fillup.id">
<b-field :label="$t('selectvehicle')">
<b-select v-model="selectedVehicle" :placeholder="$t('vehicle')" required expanded :disabled="fillup.id">
<option v-for="option in myVehicles" :key="option.id" :value="option">
{{ option.nickname }}
</option>
</b-select>
</b-field>
<b-field :label="this.$t('expenseby')">
<b-select v-model="fillupModel.userId" :placeholder="this.$t('user')" required expanded :disabled="fillup.id">
<b-field :label="$t('expenseby')">
<b-select v-model="fillupModel.userId" :placeholder="$t('user')" required expanded :disabled="fillup.id">
<option v-for="option in users" :key="option.userId" :value="option.userId">
{{ option.name }}
</option>
</b-select>
</b-field>
<b-field :label="this.$t('fillupdate')">
<b-field :label="$t('fillupdate')">
<b-datepicker
v-model="fillupModel.date"
:date-formatter="formatDate"
@@ -218,7 +221,7 @@ export default {
>
</b-datepicker>
</b-field>
<b-field :label="this.$t('fuelsubtype')">
<b-field :label="$t('fuelsubtype')">
<b-autocomplete
v-model="fillupModel.fuelSubType"
:data="filteredFuelSubtypes"
@@ -229,27 +232,27 @@ export default {
>
</b-autocomplete>
</b-field>
<b-field :label="this.$t('quantity') + `*`" addons>
<b-field :label="$t('quantity') + `*`" addons>
<b-input v-model.number="fillupModel.fuelQuantity" type="number" step=".001" min="0" expanded required></b-input>
<b-select v-model="fillupModel.fuelUnit" :placeholder="this.$t('fuelunit')" required>
<b-select v-model="fillupModel.fuelUnit" :placeholder="$t('fuelunit')" required>
<option v-for="(option, key) in fuelUnitMasters" :key="key" :value="key">
{{ $t('unit.long.' + option.key) }}
</option>
</b-select>
</b-field>
<b-field :label="this.$t('per', { '0': this.$t('price'), '1': $t('unit.short.' + vehicle.fuelUnitDetail.key) })"
<b-field :label="$t('per', { '0': $t('price'), '1': $t('unit.short.' + vehicle.fuelUnitDetail.key) })"
><p class="control">
<span class="button is-static">{{ me.currency }}</span>
</p>
<b-input v-model.number="fillupModel.perUnitPrice" type="number" min="0" step=".001" expanded required></b-input>
</b-field>
<b-field :label="this.$t('totalamountpaid')">
<b-field :label="$t('totalamountpaid')">
<p class="control">
<span class="button is-static">{{ me.currency }}</span>
</p>
<b-input v-model.number="fillupModel.totalAmount" type="number" min="0" step=".001" expanded required></b-input>
</b-field>
<b-field :label="this.$t('odometer')">
<b-field :label="$t('odometer')">
<p class="control">
<span class="button is-static">{{ $t('unit.short.' + me.distanceUnitDetail.key) }}</span>
</p>
@@ -265,10 +268,10 @@ export default {
<b-switch v-model="showMore">{{ $t('fillmoredetails') }}</b-switch>
</b-field>
<fieldset v-if="showMore">
<b-field :label="this.$t('fillingstation')">
<b-field :label="$t('fillingstation')">
<b-input v-model="fillupModel.fillingStation" type="text" expanded></b-input>
</b-field>
<b-field :label="this.$t('comments')">
<b-field :label="$t('comments')">
<b-input v-model="fillupModel.comments" type="textarea" expanded></b-input>
</b-field>
</fieldset>
@@ -277,7 +280,15 @@ export default {
</b-field>
<br />
<b-field>
<b-button tag="input" native-type="submit" :disabled="tryingToCreate" type="is-primary" :value="this.$t('save')" :label="this.$t('createfillup')" expanded> </b-button>
<b-button
tag="button"
native-type="submit"
:disabled="tryingToCreate"
type="is-primary"
:value="$t('save')"
:label="$t('createfillup')"
expanded
/>
<p v-if="authError">
There was an error logging in to your account.
</p>

View File

@@ -134,47 +134,55 @@ export default {
</div>
</div>
<form @submit.prevent="createVehicle">
<b-field :label="this.$t('nickname') + `*`">
<b-field :label="$t('nickname') + `*`">
<b-input v-model="vehicleModel.nickname" type="text" expanded required></b-input>
</b-field>
<b-field :label="this.$t('registration') + `*`">
<b-field :label="$t('registration') + `*`">
<b-input v-model="vehicleModel.registration" type="text" expanded required></b-input>
</b-field>
<b-field label="VIN">
<b-field label="VIN">
<b-input v-model="vehicleModel.vin" type="text" expanded></b-input>
</b-field>
<b-field :label="this.$t('fueltype') + `*`">
<b-select v-model.number="vehicleModel.fuelType" :placeholder="this.$t('fueltype')" required expanded>
<b-field :label="$t('fueltype') + `*`">
<b-select v-model.number="vehicleModel.fuelType" :placeholder="$t('fueltype')" required expanded>
<option v-for="(option, key) in fuelTypeMasters" :key="key" :value="key">
{{ $t('fuel.' + option.key) }}
</option>
</b-select>
</b-field>
<b-field :label="this.$t('fuelunit') + `*`">
<b-select v-model.number="vehicleModel.fuelUnit" :placeholder="this.$t('fuelunit')" required expanded>
<b-field :label="$t('fuelunit') + `*`">
<b-select v-model.number="vehicleModel.fuelUnit" :placeholder="$t('fuelunit')" required expanded>
<option v-for="(option, key) in fuelUnitMasters" :key="key" :value="key">
{{ $t('unit.long.' + option.key) }}
</option>
</b-select>
</b-field>
<b-field :label="this.$t('make') + `*`">
<b-field :label="$t('make') + `*`">
<b-input v-model="vehicleModel.make" type="text" required expanded></b-input>
</b-field>
<b-field :label="this.$t('model') + `*`">
<b-field :label="$t('model') + `*`">
<b-input v-model="vehicleModel.model" type="text" required expanded></b-input>
</b-field>
<b-field :label="this.$t('yearmanufacture') + `*`">
<b-field :label="$t('yearmanufacture') + `*`">
<b-input v-model.number="vehicleModel.yearOfManufacture" type="number" expanded number></b-input>
</b-field>
<b-field :label="this.$t('yearmanufacture')">
<b-field :label="$t('enginesize')">
<b-input v-model.number="vehicleModel.engineSize" type="number" expanded number></b-input>
</b-field>
<br />
<b-field>
<b-button tag="input" native-type="submit" :disabled="tryingToCreate" type="is-primary" :value="this.$t('save')" :label="this.$t('createvehicle')" expanded>
<b-button
tag="button"
native-type="submit"
:disabled="tryingToCreate"
type="is-primary"
:value="$t('save')"
:label="$t('createvehicle')"
expanded
>
<BaseIcon v-if="tryingToCreate" name="sync" spin />
</b-button>
<p v-if="authError">

View File

@@ -154,7 +154,7 @@ export default {
</b-field>
</div>
<div class="column">
<b-button tag="input" native-type="submit" :disabled="tryingToCreate" type="is-primary" value="Upload File" class="control">
<b-button tag="button" native-type="submit" :disabled="tryingToCreate" type="is-primary" class="control">
Import
</b-button>
</div></div

View File

@@ -9,6 +9,19 @@ export default {
meta: [{ name: 'description', content: 'The Import Fuelly page.' }],
},
components: { Layout },
props: {
user: {
type: Object,
required: true,
},
},
data: function() {
return {
file: null,
tryingToCreate: false,
errors: [],
}
},
computed: {
...mapState('utils', ['isMobile']),
uploadButtonLabel() {
@@ -27,19 +40,6 @@ export default {
}
},
},
props: {
user: {
type: Object,
required: true,
},
},
data: function() {
return {
file: null,
tryingToCreate: false,
errors: [],
}
},
methods: {
importFuelly() {
if (this.file == null) {
@@ -121,7 +121,7 @@ export default {
</b-field>
</div>
<div class="column">
<b-button tag="input" native-type="submit" :disabled="tryingToCreate" type="is-primary" :value="this.$t('uploadfile')" class="control">
<b-button tag="button" native-type="submit" :disabled="tryingToCreate" type="is-primary" class="control">
{{ $t('import') }}
</b-button>
</div></div

View File

@@ -0,0 +1,7 @@
import ImportGeneric from './import-generic'
describe('@views/import-generic', () => {
it('is a valid view', () => {
expect(ImportGeneric).toBeAViewComponent()
})
})

View File

@@ -0,0 +1,411 @@
<script>
import Layout from '@layouts/main.vue'
import { mapState } from 'vuex'
import axios from 'axios'
import Papa from 'papaparse'
export default {
page: {
title: 'Generic Import',
meta: [{ name: 'description', content: 'The Generic Import page.' }],
},
components: { Layout },
props: {
user: {
type: Object,
required: true,
},
},
data: function () {
return {
file: null,
tryingToCreate: false,
errors: [],
papaConfig: { dynamicTyping: true, skipEmptyLines: true, complete: this.assignResults },
fileData: null,
fileHeadings: null,
myVehicles: [],
selectedVehicle: null,
invertFullTank: null,
filledValueString: '',
notFilledValueString: '',
isFullTankString: false,
fileHeadingMap: {
fuelQuantity: null,
perUnitPrice: null,
totalAmount: null,
odoReading: null,
isTankFull: null,
hasMissedFillup: null,
comments: [], // [int]
fillingStation: null,
date: null,
fuelSubType: null,
},
}
},
computed: {
...mapState('utils', ['isMobile']),
...mapState('vehicles', ['vehicles']),
uploadButtonLabel() {
if (this.isMobile) {
if (this.file == null) {
return this.$t('choosephoto')
} else {
return ''
}
} else {
if (this.file == null) {
return this.$t('choosefile')
} else {
return ''
}
}
},
},
mounted() {
this.myVehicles = this.vehicles
},
methods: {
assignResults(results, file) {
this.fileData = results.data
this.fileHeadings = results.data[0]
},
parseCSV() {
if (this.file == null) {
return
}
this.errorMessage = ''
Papa.parse(this.file, this.papaConfig)
},
getUsedHeadings() {
return Object.keys(this.fileHeadingMap).filter((k) => this.fileHeadingMap[k] != null) // filter non-null properties
},
getTimezone() {
return Intl.DateTimeFormat().resolvedOptions().timeZone
},
csvToJson() {
const data = []
const headings = this.getUsedHeadings().reduce((a, k) => ({ ...a, [k]: this.fileHeadingMap[k] }), {}) // create new object from filter
const comments = (row) => {
return this.fileHeadingMap.comments.reduce((a, fi) => {
// TODO: sanitize to prevent XSS
return `${a}${this.fileHeadings[fi]}: ${row[fi]}\n`
}, '')
}
const calculateTotal = (row) => {
return this.fileHeadingMap.totalAmount === -1
? row[this.fileHeadingMap.fuelQuantity] * row[this.fileHeadingMap.perUnitPrice]
: row[this.fileHeadingMap.totalAmount]
}
const setFullTank = (row) => {
if (row[this.fileHeadingMap.isTankFull].toLowerCase() === this.filledValueString.toLowerCase()) {
return true
} else if (row[this.fileHeadingMap.isTankFull].toLowerCase() === this.notFilledValueString.toLowerCase()) {
return false
} else {
// TODO: need to handle errors better
throw Error
}
}
for (let r = 1; r < this.fileData.length; r++) {
const row = this.fileData[r]
const item = {}
Object.keys(headings).forEach((k) => {
if (k === 'comments') {
item[k] = comments(row)
} else if (k === 'totalAmount') {
item[k] = calculateTotal(row)
} else if (k === 'isTankFull') {
if (this.isFullTankString) {
item[k] = setFullTank(row)
} else {
if (this.invertFullTank) {
item[k] = Boolean(!row[headings[k]])
} else {
item[k] = Boolean(row[headings[k]])
}
}
} else if (k === 'hasMissedFillup') {
// TODO: need to account for this field being a string
item[k] = Boolean(row[headings[k]])
} else if (k === 'date') {
item[k] = new Date(row[headings[k]]).toISOString()
} else {
item[k] = row[headings[k]]
}
})
data.push(item)
}
return data
},
importData() {
if (this.errors.length === 0) {
try {
const content = {
data: this.csvToJson(),
vehicleId: this.selectedVehicle.id,
timezone: this.getTimezone(),
}
axios
.post('/api/import/generic', content)
.then((data) => {
this.$buefy.toast.open({
message: this.$t('importsuccessfull'),
type: 'is-success',
duration: 3000,
})
setTimeout(() => this.$router.push({ name: 'home' }), 1000)
})
.catch((ex) => {
this.$buefy.toast.open({
duration: 5000,
message: this.$t('importerror'),
position: 'is-bottom',
type: 'is-danger',
})
console.log(ex)
if (ex.response && ex.response.data.error) {
this.errors.push(ex.response.data.error)
}
})
} catch (e) {
// TODO: handle error
this.errors.push(e)
}
} else {
this.errors.push('fix errors')
}
},
checkFieldString() {
const tankFull = this.fileData[1][this.fileHeadingMap.isTankFull]
if (typeof tankFull !== 'boolean' && typeof tankFull === 'string') {
this.isFullTankString = true
}
},
clearHeadingProperty(property) {
if (property === 'comments') {
this.fileHeadingMap[property] = []
} else {
this.fileHeadingMap[property] = null
}
},
},
}
</script>
<template>
<Layout>
<div class="columns box">
<div class="column">
<h1 class="title">{{ $t('importgeneric') }}</h1>
</div>
</div>
<br />
<div v-if="fileData === null" class="columns">
<div class="column">
<p class="subtitle"> {{ $t('stepstoimport', { name: 'CSV' }) }}</p>
<ol>
<!-- <li>{{ $t('importhintcreatecsv', { 'name': 'Fuelly' }) }} <a href="http://docs.fuelly.com/acar-import-export-center" target="_nofollow">{{ $t('here') }}</a>.</li> -->
<li>{{ $t('importgenerichintdata') }}</li>
<li>{{ $t('importhintvehiclecreated') }}</li>
<li v-html="$t('importhintcurrdist')"></li>
<li v-html="$t('importhintunits')"></li>
<li>
<b>{{ $t('dontimportagain') }}</b>
</li>
</ol>
</div>
</div>
<div v-if="fileData === null" class="section box">
<div class="columns">
<div class="column is-two-thirds">
<p class="subtitle">{{ $t('choosedatafile') }}</p>
</div>
<div class="column is-one-third is-flex is-align-content-center">
<form @submit.prevent="parseCSV">
<div class="columns">
<div class="column">
<b-field class="file is-primary" :class="{ 'has-name': !!file }">
<b-upload v-model="file" class="file-label" accept=".csv" required>
<span class="file-cta">
<b-icon class="file-icon" icon="upload"></b-icon>
<span class="file-label">{{ uploadButtonLabel }}</span>
</span>
<span v-if="file" class="file-name" :class="isMobile ? 'file-name-mobile' : 'file-name-desktop'">
{{ file.name }}
</span>
</b-upload>
</b-field>
</div>
<div class="column">
<b-button tag="button" native-type="submit" type="is-primary" class="control">
{{ $t('import') }}
</b-button>
</div>
</div>
</form>
</div>
</div>
</div>
<div v-else class="columns">
<div class="column">
<p class="subtitle">Map Fields</p>
<form class="" @submit.prevent="importData">
<b-field :label="$t('selectvehicle')">
<b-select v-model="selectedVehicle" :placeholder="$t('vehicle')" required expanded>
<option v-for="option in myVehicles" :key="option.id" :value="option">
{{ option.nickname }}
</option>
</b-select>
</b-field>
<span v-if="selectedVehicle !== null">
<b-field :label="$t('fillupdate')">
<b-select v-model="fileHeadingMap.date" required expanded>
<option v-for="(option, index) in fileHeadings" :key="index" :value="index">
{{ option }}
</option>
</b-select>
</b-field>
<b-field>
<template v-slot:label>
{{ $t('fuelsubtype') }}
<b-tooltip type="is-dark" label="Clear selection">
<b-button
type="is-ghost"
size="is-small"
icon-pack="fas"
icon-right="arrow-rotate-left"
@click="clearHeadingProperty('fuelSubType')"
></b-button>
</b-tooltip>
</template>
<b-select v-model="fileHeadingMap.fuelSubType" expanded>
<option v-for="(option, index) in fileHeadings" :key="index" :value="index">
{{ option }}
</option>
</b-select>
</b-field>
<b-field :label="$t('quantity')">
<b-select v-model="fileHeadingMap.fuelQuantity" expanded required>
<option v-for="(option, index) in fileHeadings" :key="index" :value="index">
{{ option }}
</option>
</b-select>
</b-field>
<b-field :label="$t('per', { '0': $t('price'), '1': $t('unit.short.' + selectedVehicle.fuelUnitDetail.key) })">
<b-select v-model.number="fileHeadingMap.perUnitPrice" type="number" min="0" step=".001" expanded required>
<option v-for="(option, index) in fileHeadings" :key="index" :value="index">
{{ option }}
</option>
</b-select>
</b-field>
<b-field :label="$t('totalamountpaid')">
<b-select v-model.number="fileHeadingMap.totalAmount" expanded required>
<option value="-1">Calculated</option>
<option v-for="(option, index) in fileHeadings" :key="index" :value="index">
{{ option }}
</option>
</b-select>
</b-field>
<b-field :label="$t('odometer')">
<b-select v-model.number="fileHeadingMap.odoReading" expanded required>
<option v-for="(option, index) in fileHeadings" :key="index" :value="index">
{{ option }}
</option>
</b-select>
</b-field>
<b-field :label="$t('tankpartialfull')">
<b-radio-button v-model="invertFullTank" native-value="false">{{ $t('fulltank') }}</b-radio-button>
<b-radio-button v-model="invertFullTank" native-value="true">{{ $t('partialfillup') }}</b-radio-button>
</b-field>
<b-field>
<b-select v-model="fileHeadingMap.isTankFull" required @input="checkFieldString">
<option v-for="(option, index) in fileHeadings" :key="index" :value="index">
{{ option }}
</option>
</b-select>
</b-field>
<span v-if="isFullTankString === true" required>
<b-field label="Value when tank is filled">
<b-input v-model="filledValueString"></b-input>
</b-field>
<b-field label="Value when tank was not completely filled">
<b-input v-model="notFilledValueString"></b-input>
</b-field>
</span>
<b-field>
<template v-slot:label>
{{ $t('missedfillup') }}
<b-tooltip type="is-dark" label="Clear selection">
<b-button
type="is-ghost"
size="is-small"
icon-pack="fas"
icon-right="arrow-rotate-left"
@click="clearHeadingProperty('hasMissedFillup')"
></b-button>
</b-tooltip>
</template>
<b-select v-model="fileHeadingMap.hasMissedFillup">
<option v-for="(option, index) in fileHeadings" :key="index" :value="index">
{{ option }}
</option>
</b-select>
</b-field>
<b-field>
<template v-slot:label>
{{ $t('fillingstation') }}
<b-tooltip type="is-dark" label="Clear selection">
<b-button
type="is-ghost"
size="is-small"
icon-pack="fas"
icon-right="arrow-rotate-left"
@click="clearHeadingProperty('fillingStation')"
></b-button>
</b-tooltip>
</template>
<b-select v-model="fileHeadingMap.fillingStation">
<option v-for="(option, index) in fileHeadings" :key="index" :value="index">
{{ option }}
</option>
</b-select>
</b-field>
<b-field>
<template v-slot:label>
{{ $t('comments') }}
<b-tooltip type="is-dark" label="Clear selection">
<b-button
type="is-ghost"
size="is-small"
icon-pack="fas"
icon-right="arrow-rotate-left"
@click="clearHeadingProperty('comments')"
></b-button>
</b-tooltip>
</template>
<b-select v-model="fileHeadingMap.comments" type="textarea" multiple expanded>
<option v-for="(option, index) in fileHeadings" :key="index" :value="index">
{{ option }}
</option>
</b-select>
</b-field>
<br />
<b-field>
<b-button tag="button" native-type="submit" type="is-primary" :value="$t('save')" :label="$t('import')" expanded />
<p v-if="authError"> There was an error logging in to your account. </p>
</b-field>
</span>
</form>
</div>
</div>
<b-message v-if="errors.length" type="is-danger">
<ul>
<li v-for="error in errors" :key="error">{{ error }}</li>
</ul>
</b-message>
</Layout>
</template>

View File

@@ -18,18 +18,19 @@ export default {
<template>
<Layout>
<div class="columns box"
><div class="column">
<div class="columns box">
<div class="column">
<h1 class="title">{{ $t('importdata') }}</h1>
<p class="subtitle">{{ $t('importdatadesc') }}</p>
</div></div
>
</div>
</div>
<br />
<div class="columns">
<div class="column is-one-third">
<div class="box">
<h1 class="title">Fuelly</h1>
<p>If you have been using Fuelly to store your vehicle data, export the CSV file from Fuelly and click here to import.</p>
<p>If you have been using Fuelly to store your vehicle data, export the CSV file from Fuelly and click here to
import.</p>
<br />
<b-button type="is-primary" tag="router-link" to="/import/fuelly">{{ $t('import') }}</b-button>
</div>
@@ -43,6 +44,16 @@ export default {
<b-button type="is-primary" tag="router-link" to="/import/drivvo">{{ $t('import') }}</b-button>
</div>
</div>
<div class="column is-one-third" to="/import-generic">
<div class="box">
<h1 class="title">{{ $t('importgeneric') }}</h1>
<p>{{ $t('importgenericdesc') }}</p>
<br />
<b-button type="is-primary" tag="router-link" to="/import/generic">{{ $t('import') }}</b-button>
</div>
</div>
</div>
</Layout>
</template>

View File

@@ -187,7 +187,7 @@ export default {
{{ connectionError }}
</b-notification>
<b-field addons :label="this.$t('mysqlconnstr')">
<b-field addons :label="$t('mysqlconnstr')">
<b-input v-model="url" required></b-input>
</b-field>
@@ -200,20 +200,20 @@ export default {
<div v-if="migrationMode === 'fresh'" class="box content">
<h1 class="title">{{ $t('init.fresh.setupadminuser') }}</h1>
<form @submit.prevent="register">
<b-field :label="this.$t('init.fresh.yourname')">
<b-field :label="$t('init.fresh.yourname')">
<b-input v-model="registerModel.name" required></b-input>
</b-field>
<b-field :label="this.$t('init.fresh.youremail')">
<b-field :label="$t('init.fresh.youremail')">
<b-input v-model="registerModel.email" type="email" required></b-input>
</b-field>
<b-field :label="this.$t('init.fresh.yourpassword')">
<b-field :label="$t('init.fresh.yourpassword')">
<b-input v-model="registerModel.password" type="password" required minlength="8" password-reveal></b-input>
</b-field>
<b-field :label="this.$t('currency')">
<b-field :label="$t('currency')">
<b-autocomplete
v-model="registerModel.currency"
:custom-formatter="formatCurrency"
:placeholder="this.$t('currency')"
:placeholder="$t('currency')"
:data="filteredCurrencyMasters"
:keep-first="true"
:open-on-focus="true"
@@ -221,8 +221,8 @@ export default {
@select="(option) => (selected = option)"
></b-autocomplete>
</b-field>
<b-field :label="this.$t('distanceunit')">
<b-select v-model.number="registerModel.distanceUnit" :placeholder="this.$t('distanceunit')" required expanded>
<b-field :label="$t('distanceunit')">
<b-select v-model.number="registerModel.distanceUnit" :placeholder="$t('distanceunit')" required expanded>
<option v-for="(option, key) in distanceUnitMasters" :key="key" :value="key">
{{ `${$t('unit.long.' + option.key)} (${$t('unit.short.' + option.key)})` }}
</option>
@@ -230,7 +230,7 @@ export default {
</b-field>
<br />
<div class="buttons">
<b-button type="is-primary" native-type="submit" tag="input" :value="this.$t('save')"></b-button>
<b-button type="is-primary" native-type="submit" tag="button" :value="$t('save')"></b-button>
<b-button type="is-danger is-light" @click="resetMigrationMode">{{ $t('cancel') }}</b-button>
</div>

View File

@@ -71,7 +71,7 @@ export default {
<b-field :label="$t('password')">
<b-input v-model="password" tag="b-input" name="password" type="password" :placeholder="placeholders.password" />
</b-field>
<b-button tag="input" native-type="submit" :value="$t('login')" :disabled="tryingToLogIn" type="is-primary">
<b-button tag="button" native-type="submit" :disabled="tryingToLogIn" type="is-primary">
<BaseIcon v-if="tryingToLogIn" name="sync" spin />
<span v-else>
{{ $t('login') }}

View File

@@ -167,7 +167,7 @@ export default {
</b-field>
<br />
<b-field>
<b-button tag="input" native-type="submit" :disabled="tryingToSave" type="is-primary" :value="$t('save')" expanded> </b-button>
<b-button tag="button" native-type="submit" :disabled="tryingToSave" type="is-primary" expanded> {{ $t('save') }} </b-button>
</b-field>
</form>
</div>
@@ -185,7 +185,8 @@ export default {
</b-field>
<p v-if="!passwordValid" class="help is-danger">{{ $t('passworddontmatch') }}</p>
<b-field>
<b-button tag="input" native-type="submit" :disabled="!passwordValid" type="is-primary" :value="$t('changepassword')" expanded>
<b-button tag="button" native-type="submit" :disabled="!passwordValid" type="is-primary" expanded>
{{ $t('changepassword') }}
</b-button>
</b-field>
</form>

View File

@@ -72,15 +72,15 @@ export default {
</div>
<br />
<form class="" @submit.prevent="saveSettings">
<b-field :label="this.$t('currency')">
<b-select v-model="settingsModel.currency" :placeholder="this.$t('currency')" required expanded>
<b-field :label="$t('currency')">
<b-select v-model="settingsModel.currency" :placeholder="$t('currency')" required expanded>
<option v-for="option in currencyMasters" :key="option.code" :value="option.code">
{{ `${option.namePlural} (${option.code})` }}
</option>
</b-select>
</b-field>
<b-field :label="this.$t('distanceunit')">
<b-select v-model.number="settingsModel.distanceUnit" :placeholder="this.$t('distanceunit')" required expanded>
<b-field :label="$t('distanceunit')">
<b-select v-model.number="settingsModel.distanceUnit" :placeholder="$t('distanceunit')" required expanded>
<option v-for="(option, key) in distanceUnitMasters" :key="key" :value="key">
{{ `${$t('unit.long.' + option.key)} (${$t('unit.short.' + option.key)})` }}
</option>
@@ -88,7 +88,7 @@ export default {
</b-field>
<br />
<b-field>
<b-button tag="input" native-type="submit" :disabled="tryingToSave" type="is-primary" :value="this.$t('save')" expanded> </b-button>
<b-button tag="button" native-type="submit" :disabled="tryingToSave" type="is-primary" expanded> {{ $t('save') }}</b-button>
</b-field>
</form>
</Layout>

View File

@@ -138,13 +138,13 @@ export default {
<div v-if="showUserForm" class="box content">
<h1 class="title">{{ $t('createnewuser') }}</h1>
<form @submit.prevent="register">
<b-field :label="this.$t('name')">
<b-field :label="$t('name')">
<b-input v-model="registerModel.name" required></b-input>
</b-field>
<b-field :label="this.$t('email')">
<b-field :label="$t('email')">
<b-input v-model="registerModel.email" type="email" required></b-input>
</b-field>
<b-field :label="this.$t('password')">
<b-field :label="$t('password')">
<b-input
v-model="registerModel.password"
type="password"
@@ -153,24 +153,24 @@ export default {
password-reveal
></b-input>
</b-field>
<b-field :label="this.$t('role')">
<b-select v-model.number="registerModel.role" :placeholder="this.$t('placeholder')" required expanded>
<b-field :label="$t('role')">
<b-select v-model.number="registerModel.role" :placeholder="$t('role')" required expanded>
<option v-for="(option, key) in roleMasters" :key="key" :value="key">
{{ `test` }}
{{ `${option.key}` }}
</option>
</b-select>
</b-field>
<b-field :label="this.$t('currency')">
<b-select v-model="registerModel.currency" :placeholder="this.$t('currency')" required expanded>
<b-field :label="$t('currency')">
<b-select v-model="registerModel.currency" :placeholder="$t('currency')" required expanded>
<option v-for="option in currencyMasters" :key="option.code" :value="option.code">
{{ `${option.namePlural} (${option.code})` }}
</option>
</b-select>
</b-field>
<b-field :label="this.$t('distanceunit')">
<b-field :label="$t('distanceunit')">
<b-select
v-model.number="registerModel.distanceUnit"
:placeholder="this.$t('distanceunit')"
:placeholder="$t('distanceunit')"
required
expanded
>
@@ -181,28 +181,28 @@ export default {
</b-field>
<br />
<div class="buttons">
<b-button type="is-primary" native-type="submit" tag="input" :value="this.$t('save')"></b-button>
<b-button type="is-primary" native-type="submit" tag="button">{{ $t('save') }}</b-button>
<b-button type="is-danger is-light" @click="resetUserForm">{{ $t('cancel') }}</b-button>
</div>
</form>
</div>
<b-table :data="users" hoverable mobile-cards detail-key="id" paginated per-page="10" :row-class="(row, index) => row.isDisabled && 'is-disabled'">
<b-table-column v-slot="props" field="name" :label="this.$t('name')">
<b-table-column v-slot="props" field="name" :label="$t('name')">
{{ `${props.row.name}` }} <template v-if="props.row.id === user.id">({{ $t('you') }})</template>
</b-table-column>
<b-table-column v-slot="props" field="email" :label="this.$t('email')">
<b-table-column v-slot="props" field="email" :label="$t('email')">
{{ `${props.row.email}` }}
</b-table-column>
<b-table-column v-slot="props" field="role" :label="this.$t('role')">
<b-table-column v-slot="props" field="role" :label="$t('role')">
{{ `${$t('roles.' + props.row.roleDetail.key)}` }}
</b-table-column>
<b-table-column v-slot="props" field="createdAt" :label="this.$t('created')" sortable date>
<b-table-column v-slot="props" field="createdAt" :label="$t('created')" sortable date>
{{ formatDate(props.row.createdAt) }}
</b-table-column>
<b-table-column v-slot="props">
<b-button type="is-success" v-if="props.row.isDisabled && props.row.roleDetail.key === 'USER'" @click="changeDisabledStatus(props.row.id, false)">{{ $t('enable') }}</b-button>
<b-button type="is-danger" v-if="!props.row.isDisabled && props.row.roleDetail.key === 'USER'" @click="changeDisabledStatus(props.row.id, true)">{{ $t('disable') }}</b-button>
<b-button v-if="props.row.isDisabled && props.row.roleDetail.key === 'USER'" type="is-success" @click="changeDisabledStatus(props.row.id, false)">{{ $t('enable') }}</b-button>
<b-button v-if="!props.row.isDisabled && props.row.roleDetail.key === 'USER'" type="is-danger" @click="changeDisabledStatus(props.row.id, true)">{{ $t('disable') }}</b-button>
</b-table-column>
</b-table>
</div>

View File

@@ -92,7 +92,10 @@ export default {
},
{
label: this.$t('avgfuelcost'),
value: this.$t('per', {'0': this.formatCurrency(x.avgFuelPrice, x.currency), '1': this.$t('unit.short.' + this.vehicle.fuelUnitDetail.key)}),
value: this.$t('per', {
0: this.formatCurrency(x.avgFuelPrice, x.currency),
1: this.$t('unit.short.' + this.vehicle.fuelUnitDetail.key),
}),
},
]
})
@@ -309,15 +312,15 @@ export default {
<div class="column is-one-half" :class="isMobile ? 'has-text-centered' : ''">
<p class="title">{{ vehicle.nickname }} - {{ vehicle.registration }}</p>
<p class="subtitle">
{{ [vehicle.make, vehicle.model, this.$t('fuel.' + vehicle.fuelTypeDetail.key)].join(' | ') }}
{{ [vehicle.make, vehicle.model, $t('fuel.' + vehicle.fuelTypeDetail.key)].join(' | ') }}
<template v-if="users.length > 1">
| {{ $t("sharedwith") }} :
| {{ $t('sharedwith') }} :
{{
users
.map((x) => {
if (x.userId === me.id) {
return this.$t('you')
return $t('you')
} else {
return x.name
}
@@ -328,8 +331,8 @@ export default {
</p>
</div>
<div :class="(!isMobile ? 'has-text-right ' : '') + 'column is-one-half buttons'">
<b-button type="is-primary" tag="router-link" :to="`/vehicles/${vehicle.id}/fillup`">{{ this.$t('addfillup') }}</b-button>
<b-button type="is-primary" tag="router-link" :to="`/vehicles/${vehicle.id}/expense`">{{ this.$t('addexpense') }}</b-button>
<b-button type="is-primary" tag="router-link" :to="`/vehicles/${vehicle.id}/fillup`">{{ $t('addfillup') }}</b-button>
<b-button type="is-primary" tag="router-link" :to="`/vehicles/${vehicle.id}/expense`">{{ $t('addexpense') }}</b-button>
<b-button
v-if="vehicle.isOwner"
tag="router-link"
@@ -339,9 +342,8 @@ export default {
props: { vehicle: vehicle },
params: { id: vehicle.id },
}"
>
<b-icon pack="fas" icon="edit" type="is-info"> </b-icon
></b-button>
><b-icon pack="fas" icon="edit" type="is-info"> </b-icon>
</b-button>
<b-button v-if="vehicle.isOwner" :title="$t('sharevehicle')" @click="showShareVehicleModal">
<b-icon pack="fas" icon="user-friends" type="is-info"> </b-icon
></b-button>
@@ -362,42 +364,42 @@ export default {
<h1 class="title is-4">{{ $t('pastfillups') }}</h1>
<b-table :data="fillups" hoverable mobile-cards :detailed="isMobile" detail-key="id" paginated per-page="10">
<b-table-column v-slot="props" field="date" :label="this.$t('date')" :td-attrs="columnTdAttrs" sortable date>
<b-table-column v-slot="props" field="date" :label="$t('date')" :td-attrs="columnTdAttrs" sortable date>
{{ formatDate(props.row.date) }}
</b-table-column>
<b-table-column v-slot="props" field="fuelSubType" :label="this.$t('fuelsubtype')" :td-attrs="columnTdAttrs">
<b-table-column v-slot="props" field="fuelSubType" :label="$t('fuelsubtype')" :td-attrs="columnTdAttrs">
{{ props.row.fuelSubType }}
</b-table-column>
<b-table-column v-slot="props" field="fuelQuantity" :label="this.$t('quantity')" :td-attrs="hiddenMobile" numeric>
<b-table-column v-slot="props" field="fuelQuantity" :label="$t('quantity')" :td-attrs="hiddenMobile" numeric>
{{ `${props.row.fuelQuantity} ${$t('unit.short.' + props.row.fuelUnitDetail.key)}` }}
</b-table-column>
<b-table-column
v-slot="props"
field="perUnitPrice"
:label="this.$t('per', { '0': this.$t('price'), '1': this.$t('unit.short.' + vehicle.fuelUnitDetail.key) })"
:label="$t('per', { '0': $t('price'), '1': $t('unit.short.' + vehicle.fuelUnitDetail.key) })"
:td-attrs="hiddenMobile"
numeric
sortable
>
{{ `${formatCurrency(props.row.perUnitPrice, props.row.currency)}` }}
</b-table-column>
<b-table-column v-if="isMobile" v-slot="props" field="totalAmount" :label="this.$t('total')" :td-attrs="hiddenDesktop" sortable numeric>
<b-table-column v-if="isMobile" v-slot="props" field="totalAmount" :label="$t('total')" :td-attrs="hiddenDesktop" sortable numeric>
{{ `${me.currency} ${props.row.totalAmount}` }} ({{ `${props.row.fuelQuantity} ${$t('unit.short.' + props.row.fuelUnitDetail.key)}` }} @
{{ `${me.currency} ${props.row.perUnitPrice}` }})
</b-table-column>
<b-table-column v-if="!isMobile" v-slot="props" field="totalAmount" :label="this.$t('total')" :td-attrs="hiddenMobile" sortable numeric>
<b-table-column v-if="!isMobile" v-slot="props" field="totalAmount" :label="$t('total')" :td-attrs="hiddenMobile" sortable numeric>
{{ `${formatCurrency(props.row.totalAmount, props.row.currency)}` }}
</b-table-column>
<b-table-column v-slot="props" width="20" field="isTankFull" :label="this.$t('fulltank')" :td-attrs="hiddenMobile">
<b-table-column v-slot="props" width="20" field="isTankFull" :label="$t('fulltank')" :td-attrs="hiddenMobile">
<b-icon pack="fas" :icon="props.row.isTankFull ? 'check' : 'times'" type="is-info"> </b-icon>
</b-table-column>
<b-table-column v-slot="props" field="odoReading" :label="this.$t('odometer')" :td-attrs="hiddenMobile" numeric>
<b-table-column v-slot="props" field="odoReading" :label="$t('odometer')" :td-attrs="hiddenMobile" numeric>
{{ `${props.row.odoReading} ${$t('unit.short.' + me.distanceUnitDetail.key)}` }}
</b-table-column>
<b-table-column v-slot="props" field="fillingStation" :label="this.$t('gasstation')" :td-attrs="hiddenMobile">
<b-table-column v-slot="props" field="fillingStation" :label="$t('gasstation')" :td-attrs="hiddenMobile">
{{ `${props.row.fillingStation}` }}
</b-table-column>
<b-table-column v-slot="props" field="userId" :label="this.$t('by')" :td-attrs="hiddenMobile">
<b-table-column v-slot="props" field="userId" :label="$t('by')" :td-attrs="hiddenMobile">
{{ `${props.row.user.name}` }}
</b-table-column>
<b-table-column v-slot="props">
@@ -412,7 +414,10 @@ export default {
>
<b-icon pack="fas" icon="edit" type="is-info"> </b-icon
></b-button>
<b-button type="is-ghost" :title="$t('deletefillup')" @click="deleteFillup(props.row.id)">
<b-button
type="is-ghost"
:title="$t('deletefillup')"
@click="deleteFillup(props.row.id)">
<b-icon pack="fas" icon="trash" type="is-danger"> </b-icon
></b-button>
</b-table-column>
@@ -427,22 +432,22 @@ export default {
<h1 class="title is-4">{{ $t('expenses') }}</h1>
<b-table :data="expenses" hoverable mobile-cards paginated per-page="10">
<b-table-column v-slot="props" field="date" :label="this.$t('date')" :td-attrs="columnTdAttrs" date>
<b-table-column v-slot="props" field="date" :label="$t('date')" :td-attrs="columnTdAttrs" date>
{{ formatDate(props.row.date) }}
</b-table-column>
<b-table-column v-slot="props" field="expenseType" :label="this.$t('expensetype')">
<b-table-column v-slot="props" field="expenseType" :label="$t('expensetype')">
{{ `${props.row.expenseType}` }}
</b-table-column>
<b-table-column v-slot="props" field="amount" :label="this.$t('total')" :td-attrs="hiddenMobile" sortable numeric>
<b-table-column v-slot="props" field="amount" :label="$t('total')" :td-attrs="hiddenMobile" sortable numeric>
{{ `${formatCurrency(props.row.amount, props.row.currency)}` }}
</b-table-column>
<b-table-column v-slot="props" field="odoReading" :label="this.$t('odometer')" :td-attrs="columnTdAttrs" numeric>
<b-table-column v-slot="props" field="odoReading" :label="$t('odometer')" :td-attrs="columnTdAttrs" numeric>
{{ `${props.row.odoReading} ${$t('unit.short.' + me.distanceUnitDetail.key)}` }}
</b-table-column>
<b-table-column v-slot="props" field="userId" :label="this.$t('by')" :td-attrs="columnTdAttrs">
<b-table-column v-slot="props" field="userId" :label="$t('by')" :td-attrs="columnTdAttrs">
{{ `${props.row.user.name}` }}
</b-table-column>
<b-table-column v-slot="props">
@@ -467,7 +472,9 @@ export default {
<br />
<div class="box">
<div class="columns">
<div class="column is-three-quarters"> <h1 class="title is-4">{{ $t('attachments') }}</h1></div>
<div class="column is-three-quarters">
<h1 class="title is-4">{{ $t('attachments') }}</h1></div
>
<div class="column buttons">
<b-button type="is-primary" @click="showAttachmentForm = true">
{{ $t('addattachment') }}
@@ -492,18 +499,18 @@ export default {
</b-upload>
</b-field>
<b-field>
<b-input v-model="title" required :placeholder="this.$t('labelforfile')"></b-input>
<b-input v-model="title" required :placeholder="$t('labelforfile')"></b-input>
</b-field>
<b-field class="buttons">
<b-button tag="input" native-type="submit" :disabled="tryingToUpload" type="is-primary" label="Upload File" value="Upload File">
<b-button tag="button" native-type="submit" :disabled="tryingToUpload" type="is-primary" label="Upload File" value="Upload File">
</b-button>
<b-button
tag="input"
tag="button"
native-type="submit"
:disabled="tryingToUpload"
type="is-danger"
label="Upload File"
label="Cancel"
value="Cancel"
@click="showAttachmentForm = false"
>
@@ -516,14 +523,14 @@ export default {
</div>
<b-table :data="attachments" hoverable mobile-cards>
<b-table-column v-slot="props" field="title" :label="this.$t('title')" :td-attrs="columnTdAttrs">
<b-table-column v-slot="props" field="title" :label="$t('title')" :td-attrs="columnTdAttrs">
{{ `${props.row.title}` }}
</b-table-column>
<b-table-column v-slot="props" field="originalName" :label="this.$t('name')" :td-attrs="columnTdAttrs">
<b-table-column v-slot="props" field="originalName" :label="$t('name')" :td-attrs="columnTdAttrs">
{{ `${props.row.originalName}` }}
</b-table-column>
<b-table-column v-slot="props" field="id" :label="this.$t('download')" :td-attrs="columnTdAttrs">
<b-table-column v-slot="props" field="id" :label="$t('download')" :td-attrs="columnTdAttrs">
<b-button tag="a" :href="`/api/attachments/${props.row.id}/file?access_token=${currentUser.token}`" :download="props.row.originalName">
<b-icon type="is-primary" icon="download"></b-icon>
</b-button>
@@ -533,7 +540,9 @@ export default {
</div>
<div class="box">
<div class="columns">
<div class="column" :class="isMobile ? 'has-text-centered' : ''"> <h1 class="title">{{ $t('statistics') }}</h1></div>
<div class="column" :class="isMobile ? 'has-text-centered' : ''">
<h1 class="title">{{ $t('statistics') }}</h1></div
>
<div class="column">
<div class="columns is-pulled-right is-medium">
<div class="column">

14736
ui/yarn.lock

File diff suppressed because it is too large Load Diff