Compare commits

...

150 Commits

Author SHA1 Message Date
Alf Sebastian Houge
d5dfe430a3 Merge pull request #103 from mate1213/master
Add Hungarian translation
2024-04-08 12:09:04 +02:00
Máté Zsólya
331d98af0e finish hungarian translation 2024-03-10 12:15:26 +01:00
Máté Zsólya
8e70b0d8ba translate dns until ln 143 2024-03-01 13:23:01 +01:00
Máté Zsólya
c8b7e3b3a1 Translate done until ln 115 2024-02-29 10:11:23 +01:00
alienp4nda
8b4f773fe0 Merge pull request #93 from thehijacker/master 2024-01-12 00:25:03 -07:00
Alf Sebastian Houge
79a9357a62 Merge pull request #89 from alienp4nda/bugs/small-mistakes
missed commas, button missing text
2024-01-11 20:54:42 +01:00
Andrej Kralj
9e34b6c6f8 Add Slovenian translation
Add file sl.json with Slovenian strings
2024-01-06 19:33:33 +01:00
Alex Hess
c4389f6a80 missed commas, button missing text 2024-01-05 03:05:38 -07:00
Alf Sebastian Houge
dd1d646a8a Merge pull request #88 from alienp4nda/master 2024-01-04 08:36:18 +01:00
Alex Hess
bd4b389139 apline image needed tzdata installed 2024-01-03 12:17:27 -07:00
Alf Sebastian Houge
996b146d2c Merge pull request #59 from ressel87/patch-1
danish translation
2023-08-22 13:57:59 +02:00
Alf Sebastian Houge
c5fdfd4ca9 Merge pull request #79 from Krafting/master
Create fr.json - Add French language
2023-08-22 13:57:27 +02:00
Krafting
6804be4f7a Fixed spelling 2023-07-29 01:43:21 +02:00
Krafting
79e4f6ab4d Final translations.
Should be finished and done.
2023-07-29 01:42:20 +02:00
Krafting
5cecc82d95 2am translations 2023-07-27 02:18:05 +02:00
Krafting
298f58a738 Workity work 2023-07-27 02:06:22 +02:00
Krafting
793bac40a3 More work. 2023-07-27 01:49:24 +02:00
Krafting
9cb65e4cda more translation 2023-07-24 18:32:31 +02:00
Krafting
a67c460434 Create fr.json
First translation. Not finished
2023-07-24 14:47:06 +02:00
Alf Sebastian Houge
355739a966 Merge pull request #78 from AlfHou/AlfHou-patch-1
Update Dockerfile
2023-07-21 15:50:03 +02:00
Alf Sebastian Houge
71bbd6b443 Update Dockerfile 2023-07-21 15:49:55 +02:00
Alf Sebastian Houge
6e59156c24 Merge pull request #77 from AlfHou/AlfHou-patch-1
Remove commit linters from package.json
2023-07-21 15:41:27 +02:00
Alf Sebastian Houge
d786e1ad08 Remove commit linters from package.json
Remove these commit linters as they are giving us problems with the docker build. Might reintroduce once we get the dependencies more up to date
2023-07-21 15:41:15 +02:00
Alf Sebastian Houge
bb0dc22630 Merge pull request #76 from AlfHou/AlfHou-patch-1
Update Dockerfile
2023-07-21 14:19:41 +02:00
Alf Sebastian Houge
d81701c4fa Update Dockerfile 2023-07-21 14:19:31 +02:00
Alf Sebastian Houge
c4f5d93b8b Merge pull request #75 from AlfHou/AlfHou-patch-1
Update Dockerfile
2023-07-21 13:14:20 +02:00
Alf Sebastian Houge
6132aa36af Update Dockerfile 2023-07-21 13:13:57 +02:00
Alf Sebastian Houge
49cc485866 Merge pull request #74 from AlfHou/AlfHou-patch-1
Update Dockerfile
2023-07-20 14:39:20 +02:00
Alf Sebastian Houge
4ed813cd2e Update Dockerfile 2023-07-20 14:39:11 +02:00
Alf Sebastian Houge
9bcc2cc2a0 Merge pull request #73 from AlfHou/AlfHou-patch-1
Update Dockerfile
2023-07-20 14:05:20 +02:00
Alf Sebastian Houge
d3d8cc268d Update Dockerfile 2023-07-20 14:05:07 +02:00
Alf Sebastian Houge
62dcf663ff Merge pull request #72 from AlfHou/AlfHou-patch-1
Update Dockerfile
2023-07-20 13:23:48 +02:00
Alf Sebastian Houge
b016dadb9d Update Dockerfile 2023-07-20 13:23:38 +02:00
Alf Sebastian Houge
b962565ed6 Merge pull request #71 from AlfHou/AlfHou-patch-1
Update Dockerfile
2023-07-20 13:13:27 +02:00
Alf Sebastian Houge
20b0e246fd Update Dockerfile 2023-07-20 13:13:11 +02:00
Alf Sebastian Houge
f1a7a053f4 Merge pull request #70 from AlfHou/AlfHou-patch-1
Update Dockerfile
2023-07-20 12:59:11 +02:00
Alf Sebastian Houge
359f35c53f Update Dockerfile 2023-07-20 12:59:01 +02:00
Alf Sebastian Houge
ea3423d32a Merge pull request #69 from AlfHou/AlfHou-patch-1
Update Dockerfile
2023-07-20 12:14:16 +02:00
Alf Sebastian Houge
6542a3bb28 Update Dockerfile 2023-07-20 12:14:05 +02:00
Alf Sebastian Houge
35d2f1ca0b Merge pull request #68 from AlfHou/AlfHou-patch-1 2023-07-20 01:15:31 +02:00
Alf Sebastian Houge
565d5701be Update Dockerfile to use Debian instead of alpine 2023-07-20 01:15:09 +02:00
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
ressel87
32a5d932c6 danish translation 2023-07-19 13:31:03 +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
e66e5b7724 Merge pull request #18 from solluh/master
Update de.json
2023-01-28 22:53:50 +01:00
Alf Sebastian Houge
adfd70fe98 Merge pull request #20 from AlfHou/feat/drivvo-import
Add importer for Drivvo
2023-01-28 22:51:32 +01:00
Alf Sebastian Houge
ebebcacdc9 Fix pricePerUnit parsing 2023-01-28 22:49:38 +01:00
Alf Sebastian Houge
3299c13181 Merge branch 'master' into feat/drivvo-import 2023-01-28 22:22:24 +01:00
Alf Sebastian Houge
2661f8ae36 Merge pull request #19 from AlfHou/feat/state_mileage_option
Add option in vehicle stats to choose what mileage metric to show
2023-01-28 22:20:21 +01:00
Alf Sebastian Houge
091cfdcc99 Merge branch 'master' into feat/state_mileage_option 2023-01-28 22:19:02 +01:00
solluh
9771dc4c25 Update de.json
Einige kleine Fehler korrigiert
2023-01-27 09:03:38 +01:00
Alf Sebastian Houge
c0db2c5c1e Calculate mileage based on mileageOption 2022-10-15 20:18:26 +02:00
Alf Sebastian Houge
2ecb113918 Add mileage options to frontend 2022-10-15 20:17:41 +02:00
Alf Sebastian Houge
966cac280f Add utils for converting imperial and metric 2022-10-15 20:17:19 +02:00
Alf Sebastian Houge
2749707546 Change logic in how indexes of sections in csv work
Change the logic in how indexes of the different sections in drivvos csv work
2022-04-06 17:56:58 +02:00
Alf Sebastian Houge
f1bf36bcb9 Separate imports into provider specific files 2022-04-06 17:56:58 +02:00
Alf Sebastian Houge
3322e2f6bd Don't redirect from import page on erro 2022-04-06 17:56:58 +02:00
Alf Sebastian Houge
9ef929dbd5 Redirect to home after importing 2022-04-06 17:56:58 +02:00
Alf Sebastian Houge
dc33aaad49 Add option for not importing location 2022-04-06 17:56:58 +02:00
Alf Sebastian Houge
15cf09f326 Add notice about what fields aren't imported from drivvo 2022-04-06 17:56:58 +02:00
Alf Sebastian Houge
1e099ec8b6 Create db.Expense and db.Fillup in one place
Create Expense and Fillup in one place instead of throughout function
2022-04-06 17:56:58 +02:00
Alf Sebastian Houge
e8f7815d8d Select vehicle when importing drivvo csv.
Select what vehicle to import for on frontend. Actually import the data to the db
2022-04-06 17:56:58 +02:00
Alf Sebastian Houge
bfaebf17d0 Add functions for parsing drivvo CSVs. 2022-04-06 17:56:58 +02:00
Alf Sebastian Houge
d314ed4a16 Add vscode specific files to gitignore 2022-03-05 19:21:10 +01:00
72 changed files with 22982 additions and 25866 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,11 @@ 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 build-base nasm libc6-compat python3 py3-pip make g++ libpng-dev zlib-dev pngquant
RUN npm install
COPY ./ui .
RUN npm run build
@@ -25,7 +27,7 @@ ENV UID=998
ENV PID=100
ENV GIN_MODE=release
VOLUME ["/config", "/assets"]
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*
RUN apk update && apk add ca-certificates tzdata && rm -rf /var/cache/apk/*
RUN mkdir -p /config; \
mkdir -p /assets; \
mkdir -p /api

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"
@@ -25,6 +26,33 @@ func RandString(n int) string {
return string(b)
}
// A helper to convert from litres to gallon
func LitreToGallon(litres float32) float32 {
gallonConversionFactor := 0.21997
return litres * float32(gallonConversionFactor);
}
// A helper to convert from gallon to litres
func GallonToLitre(gallons float32) float32 {
litreConversionFactor := 3.785412
return gallons * float32(litreConversionFactor);
}
// A helper to convert from km to miles
func KmToMiles(km float32) float32 {
kmConversionFactor := 0.62137119
return km * float32(kmConversionFactor);
}
// A helper to convert from miles to km
func MilesToKm(miles float32) float32 {
milesConversionFactor := 1.609344
return miles * float32(milesConversionFactor);
}
// A Util function to generate jwt_token which can be used in the request header
func GenToken(id string, role db.Role) (string, string) {
jwt_token := jwt.New(jwt.GetSigningMethod("HS256"))

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

@@ -2,13 +2,18 @@ package controllers
import (
"net/http"
"strconv"
"hammond/models"
"hammond/service"
"github.com/akhilrex/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) {
@@ -24,3 +29,46 @@ func fuellyImport(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{})
}
func drivvoImport(c *gin.Context) {
bytes, err := getFileBytes(c, "file")
if err != nil {
c.JSON(http.StatusUnprocessableEntity, err)
return
}
vehicleId := c.PostForm("vehicleID")
if vehicleId == "" {
c.JSON(http.StatusUnprocessableEntity, "Missing Vehicle ID")
return
}
importLocation, err := strconv.ParseBool(c.PostForm("importLocation"))
if err != nil {
c.JSON(http.StatusUnprocessableEntity, "Please include importLocation option.")
return
}
errors := service.DrivvoImport(bytes, c.MustGet("userId").(string), vehicleId, importLocation)
if len(errors) > 0 {
c.JSON(http.StatusUnprocessableEntity, gin.H{"errors": errors})
return
}
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"
)
@@ -25,7 +26,7 @@ func getMileageForVehicle(c *gin.Context) {
return
}
fillups, err := service.GetMileageByVehicleId(searchByIdQuery.Id, model.Since)
fillups, err := service.GetMileageByVehicleId(searchByIdQuery.Id, model.Since, model.MileageOption)
if err != nil {
c.JSON(http.StatusUnprocessableEntity, common.NewError("getMileageForVehicle", err))
return

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 {
@@ -14,7 +14,7 @@ type MileageModel struct {
FuelQuantity float32 `form:"fuelQuantity" json:"fuelQuantity" binding:"required"`
PerUnitPrice float32 `form:"perUnitPrice" json:"perUnitPrice" binding:"required"`
Currency string `json:"currency"`
DistanceUnit db.DistanceUnit `form:"distanceUnit" json:"distanceUnit"`
Mileage float32 `form:"mileage" json:"mileage" binding:"mileage"`
CostPerMile float32 `form:"costPerMile" json:"costPerMile" binding:"costPerMile"`
OdoReading int `form:"odoReading" json:"odoReading" binding:"odoReading"`
@@ -35,4 +35,5 @@ func (b *MileageModel) MarshalJSON() ([]byte, error) {
type MileageQueryModel struct {
Since time.Time `json:"since" query:"since" form:"since"`
MileageOption string `json:"mileageOption" query:"mileageOption" form:"mileageOption"`
}

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

@@ -0,0 +1,142 @@
package service
import (
"bytes"
"encoding/csv"
"fmt"
"strconv"
"strings"
"time"
"hammond/db"
)
func DrivvoParseExpenses(content []byte, user *db.User, vehicle *db.Vehicle) ([]db.Expense, []string) {
expenseReader := csv.NewReader(bytes.NewReader(content))
expenseReader.Comment = '#'
// Read headers (there is a trailing comma at the end, that's why we have to read the first line)
expenseReader.Read()
expenseReader.FieldsPerRecord = 6
expenseRecords, err := expenseReader.ReadAll()
var errors []string
if err != nil {
errors = append(errors, err.Error())
println(err.Error())
return nil, errors
}
var expenses []db.Expense
for index, record := range expenseRecords {
date, err := time.Parse("2006-01-02 15:04:05", record[1])
if err != nil {
errors = append(errors, "Found an invalid date/time at service/expense row "+strconv.Itoa(index+1))
}
totalCost, err := strconv.ParseFloat(record[2], 32)
if err != nil {
errors = append(errors, "Found and invalid total cost at service/expense row "+strconv.Itoa(index+1))
}
odometer, err := strconv.Atoi(record[0])
if err != nil {
errors = append(errors, "Found an invalid odometer reading at service/expense row "+strconv.Itoa(index+1))
}
notes := fmt.Sprintf("Location: %s\nNotes: %s\n", record[4], record[5])
expenses = append(expenses, db.Expense{
UserID: user.ID,
VehicleID: vehicle.ID,
Date: date,
OdoReading: odometer,
Amount: float32(totalCost),
ExpenseType: record[3],
Currency: user.Currency,
DistanceUnit: user.DistanceUnit,
Comments: notes,
Source: "Drivvo",
})
}
return expenses, errors
}
func DrivvoParseRefuelings(content []byte, user *db.User, vehicle *db.Vehicle, importLocation bool) ([]db.Fillup, []string) {
refuelingReader := csv.NewReader(bytes.NewReader(content))
refuelingReader.Comment = '#'
refuelingRecords, err := refuelingReader.ReadAll()
var errors []string
if err != nil {
errors = append(errors, err.Error())
println(err.Error())
return nil, errors
}
var fillups []db.Fillup
for index, record := range refuelingRecords {
// Skip column titles
if index == 0 {
continue
}
date, err := time.Parse("2006-01-02 15:04:05", record[1])
if err != nil {
errors = append(errors, "Found an invalid date/time at refuel row "+strconv.Itoa(index+1))
}
totalCost, err := strconv.ParseFloat(record[4], 32)
if err != nil {
errors = append(errors, "Found and invalid total cost at refuel row "+strconv.Itoa(index+1))
}
odometer, err := strconv.Atoi(record[0])
if err != nil {
errors = append(errors, "Found an invalid odometer reading at refuel row "+strconv.Itoa(index+1))
}
location := ""
if importLocation {
location = record[17]
}
pricePerUnit, err := strconv.ParseFloat(record[3], 32)
if err != nil {
unit := strings.ToLower(db.FuelUnitDetails[vehicle.FuelUnit].Key)
errors = append(errors, fmt.Sprintf("Found an invalid cost per %s at refuel row %d", unit, index+1))
}
quantity, err := strconv.ParseFloat(record[5], 32)
if err != nil {
errors = append(errors, "Found an invalid quantity at refuel row "+strconv.Itoa(index+1))
}
isTankFull := record[6] == "Yes"
// Unfortunatly, drivvo doesn't expose this info in their export
fal := false
notes := fmt.Sprintf("Reason: %s\nNotes: %s\nFuel: %s\n", record[18], record[19], record[2])
fillups = append(fillups, db.Fillup{
VehicleID: vehicle.ID,
UserID: user.ID,
Date: date,
HasMissedFillup: &fal,
IsTankFull: &isTankFull,
FuelQuantity: float32(quantity),
PerUnitPrice: float32(pricePerUnit),
FillingStation: location,
OdoReading: odometer,
TotalAmount: float32(totalCost),
FuelUnit: vehicle.FuelUnit,
Currency: user.Currency,
DistanceUnit: user.DistanceUnit,
Comments: notes,
Source: "Drivvo",
})
}
return fillups, errors
}

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

@@ -0,0 +1,141 @@
package service
import (
"bytes"
"encoding/csv"
"fmt"
"strconv"
"time"
"hammond/db"
"github.com/leekchan/accounting"
)
func FuellyParseAll(content []byte, userId string) ([]db.Fillup, []db.Expense, []string) {
stream := bytes.NewReader(content)
reader := csv.NewReader(stream)
records, err := reader.ReadAll()
var errors []string
user, err := GetUserById(userId)
if err != nil {
errors = append(errors, err.Error())
return nil, nil, errors
}
vehicles, err := GetUserVehicles(userId)
if err != nil {
errors = append(errors, err.Error())
return nil, nil, errors
}
if err != nil {
errors = append(errors, err.Error())
return nil, nil, errors
}
var vehicleMap map[string]db.Vehicle = make(map[string]db.Vehicle)
for _, vehicle := range *vehicles {
vehicleMap[vehicle.Nickname] = vehicle
}
var fillups []db.Fillup
var expenses []db.Expense
layout := "2006-01-02 15:04"
altLayout := "2006-01-02 3:04 PM"
for index, record := range records {
if index == 0 {
continue
}
var vehicle db.Vehicle
var ok bool
if vehicle, ok = vehicleMap[record[4]]; !ok {
errors = append(errors, "Found an unmapped vehicle entry at row "+strconv.Itoa(index+1))
}
dateStr := record[2] + " " + record[3]
date, err := time.Parse(layout, dateStr)
if err != nil {
date, err = time.Parse(altLayout, dateStr)
}
if err != nil {
errors = append(errors, "Found an invalid date/time at row "+strconv.Itoa(index+1))
}
totalCostStr := accounting.UnformatNumber(record[9], 3, user.Currency)
totalCost64, err := strconv.ParseFloat(totalCostStr, 32)
if err != nil {
errors = append(errors, "Found an invalid total cost at row "+strconv.Itoa(index+1))
}
totalCost := float32(totalCost64)
odoStr := accounting.UnformatNumber(record[5], 0, user.Currency)
odoreading, err := strconv.Atoi(odoStr)
if err != nil {
errors = append(errors, "Found an invalid odo reading at row "+strconv.Itoa(index+1))
}
location := record[12]
//Create Fillup
if record[0] == "Gas" {
rateStr := accounting.UnformatNumber(record[7], 3, user.Currency)
ratet64, err := strconv.ParseFloat(rateStr, 32)
if err != nil {
errors = append(errors, "Found an invalid cost per gallon at row "+strconv.Itoa(index+1))
}
rate := float32(ratet64)
quantity64, err := strconv.ParseFloat(record[8], 32)
if err != nil {
errors = append(errors, "Found an invalid quantity at row "+strconv.Itoa(index+1))
}
quantity := float32(quantity64)
notes := fmt.Sprintf("Octane:%s\nGas Brand:%s\nLocation%s\nTags:%s\nPayment Type:%s\nTire Pressure:%s\nNotes:%s\nMPG:%s",
record[10], record[11], record[12], record[13], record[14], record[15], record[16], record[1],
)
isTankFull := record[6] == "Full"
fal := false
fillups = append(fillups, db.Fillup{
VehicleID: vehicle.ID,
FuelUnit: vehicle.FuelUnit,
FuelQuantity: quantity,
PerUnitPrice: rate,
TotalAmount: totalCost,
OdoReading: odoreading,
IsTankFull: &isTankFull,
Comments: notes,
FillingStation: location,
HasMissedFillup: &fal,
UserID: userId,
Date: date,
Currency: user.Currency,
DistanceUnit: user.DistanceUnit,
Source: "Fuelly",
})
}
if record[0] == "Service" {
notes := fmt.Sprintf("Tags:%s\nPayment Type:%s\nNotes:%s",
record[13], record[14], record[16],
)
expenses = append(expenses, db.Expense{
VehicleID: vehicle.ID,
Amount: totalCost,
OdoReading: odoreading,
Comments: notes,
ExpenseType: record[17],
UserID: userId,
Currency: user.Currency,
Date: date,
DistanceUnit: user.DistanceUnit,
Source: "Fuelly",
})
}
}
return fillups, expenses, errors
}

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

@@ -2,144 +2,13 @@ package service
import (
"bytes"
"encoding/csv"
"fmt"
"strconv"
"time"
"github.com/akhilrex/hammond/db"
"github.com/leekchan/accounting"
"hammond/db"
"hammond/models"
)
func FuellyImport(content []byte, userId string) []string {
stream := bytes.NewReader(content)
reader := csv.NewReader(stream)
records, err := reader.ReadAll()
func WriteToDB(fillups []db.Fillup, expenses []db.Expense) []string {
var errors []string
if err != nil {
errors = append(errors, err.Error())
return errors
}
vehicles, err := GetUserVehicles(userId)
if err != nil {
errors = append(errors, err.Error())
return errors
}
user, err := GetUserById(userId)
if err != nil {
errors = append(errors, err.Error())
return errors
}
var vehicleMap map[string]db.Vehicle = make(map[string]db.Vehicle)
for _, vehicle := range *vehicles {
vehicleMap[vehicle.Nickname] = vehicle
}
var fillups []db.Fillup
var expenses []db.Expense
layout := "2006-01-02 15:04"
altLayout := "2006-01-02 3:04 PM"
for index, record := range records {
if index == 0 {
continue
}
var vehicle db.Vehicle
var ok bool
if vehicle, ok = vehicleMap[record[4]]; !ok {
errors = append(errors, "Found an unmapped vehicle entry at row "+strconv.Itoa(index+1))
}
dateStr := record[2] + " " + record[3]
date, err := time.Parse(layout, dateStr)
if err != nil {
date, err = time.Parse(altLayout, dateStr)
}
if err != nil {
errors = append(errors, "Found an invalid date/time at row "+strconv.Itoa(index+1))
}
totalCostStr := accounting.UnformatNumber(record[9], 3, user.Currency)
totalCost64, err := strconv.ParseFloat(totalCostStr, 32)
if err != nil {
errors = append(errors, "Found an invalid total cost at row "+strconv.Itoa(index+1))
}
totalCost := float32(totalCost64)
odoStr := accounting.UnformatNumber(record[5], 0, user.Currency)
odoreading, err := strconv.Atoi(odoStr)
if err != nil {
errors = append(errors, "Found an invalid odo reading at row "+strconv.Itoa(index+1))
}
location := record[12]
//Create Fillup
if record[0] == "Gas" {
rateStr := accounting.UnformatNumber(record[7], 3, user.Currency)
ratet64, err := strconv.ParseFloat(rateStr, 32)
if err != nil {
errors = append(errors, "Found an invalid cost per gallon at row "+strconv.Itoa(index+1))
}
rate := float32(ratet64)
quantity64, err := strconv.ParseFloat(record[8], 32)
if err != nil {
errors = append(errors, "Found an invalid quantity at row "+strconv.Itoa(index+1))
}
quantity := float32(quantity64)
notes := fmt.Sprintf("Octane:%s\nGas Brand:%s\nLocation%s\nTags:%s\nPayment Type:%s\nTire Pressure:%s\nNotes:%s\nMPG:%s",
record[10], record[11], record[12], record[13], record[14], record[15], record[16], record[1],
)
isTankFull := record[6] == "Full"
fal := false
fillups = append(fillups, db.Fillup{
VehicleID: vehicle.ID,
FuelUnit: vehicle.FuelUnit,
FuelQuantity: quantity,
PerUnitPrice: rate,
TotalAmount: totalCost,
OdoReading: odoreading,
IsTankFull: &isTankFull,
Comments: notes,
FillingStation: location,
HasMissedFillup: &fal,
UserID: userId,
Date: date,
Currency: user.Currency,
DistanceUnit: user.DistanceUnit,
Source: "Fuelly",
})
}
if record[0] == "Service" {
notes := fmt.Sprintf("Tags:%s\nPayment Type:%s\nNotes:%s",
record[13], record[14], record[16],
)
expenses = append(expenses, db.Expense{
VehicleID: vehicle.ID,
Amount: totalCost,
OdoReading: odoreading,
Comments: notes,
ExpenseType: record[17],
UserID: userId,
Currency: user.Currency,
Date: date,
DistanceUnit: user.DistanceUnit,
Source: "Fuelly",
})
}
}
if len(errors) != 0 {
return errors
}
tx := db.DB.Begin()
defer func() {
if r := recover(); r != nil {
@@ -150,19 +19,114 @@ func FuellyImport(content []byte, userId string) []string {
errors = append(errors, err.Error())
return errors
}
if err := tx.Create(&fillups).Error; err != nil {
tx.Rollback()
errors = append(errors, err.Error())
return errors
if fillups != nil {
if err := tx.Create(&fillups).Error; err != nil {
tx.Rollback()
errors = append(errors, err.Error())
return errors
}
}
if err := tx.Create(&expenses).Error; err != nil {
tx.Rollback()
errors = append(errors, err.Error())
return errors
if expenses != nil {
if err := tx.Create(&expenses).Error; err != nil {
tx.Rollback()
errors = append(errors, err.Error())
return errors
}
}
err = tx.Commit().Error
err := tx.Commit().Error
if err != nil {
errors = append(errors, err.Error())
}
return errors
}
func DrivvoImport(content []byte, userId string, vehicleId string, importLocation bool) []string {
var errors []string
user, err := GetUserById(userId)
if err != nil {
errors = append(errors, err.Error())
return errors
}
vehicle, err := GetVehicleById(vehicleId)
if err != nil {
errors = append(errors, err.Error())
return errors
}
endParseIndex := bytes.Index(content, []byte("#Income"))
if endParseIndex == -1 {
endParseIndex = bytes.Index(content, []byte("#Route"))
if endParseIndex == -1 {
endParseIndex = len(content)
}
}
serviceEndIndex := bytes.Index(content, []byte("#Expense"))
if serviceEndIndex == -1 {
serviceEndIndex = endParseIndex
}
refuelEndIndex := bytes.Index(content, []byte("#Service"))
if refuelEndIndex == -1 {
refuelEndIndex = serviceEndIndex
}
var fillups []db.Fillup
fillups, errors = DrivvoParseRefuelings(content[:refuelEndIndex], user, vehicle, importLocation)
var allExpenses []db.Expense
services, parseErrors := DrivvoParseExpenses(content[refuelEndIndex:serviceEndIndex], user, vehicle)
if parseErrors != nil {
errors = append(errors, parseErrors...)
}
allExpenses = append(allExpenses, services...)
expenses, parseErrors := DrivvoParseExpenses(content[serviceEndIndex:endParseIndex], user, vehicle)
if parseErrors != nil {
errors = append(errors, parseErrors...)
}
allExpenses = append(allExpenses, expenses...)
if len(errors) != 0 {
return errors
}
return WriteToDB(fillups, allExpenses)
}
func FuellyImport(content []byte, userId string) []string {
fillups, expenses, errors := FuellyParseAll(content, userId)
if len(errors) != 0 {
return errors
}
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,11 +4,12 @@ import (
"sort"
"time"
"github.com/akhilrex/hammond/db"
"github.com/akhilrex/hammond/models"
"hammond/common"
"hammond/db"
"hammond/models"
)
func GetMileageByVehicleId(vehicleId string, since time.Time) (mileage []models.MileageModel, err error) {
func GetMileageByVehicleId(vehicleId string, since time.Time, mileageOption string) (mileage []models.MileageModel, err error) {
data, err := db.GetFillupsByVehicleIdSince(vehicleId, since)
if err != nil {
return nil, err
@@ -36,14 +37,48 @@ func GetMileageByVehicleId(vehicleId string, since time.Time) (mileage []models.
PerUnitPrice: currentFillup.PerUnitPrice,
OdoReading: currentFillup.OdoReading,
Currency: currentFillup.Currency,
DistanceUnit: currentFillup.DistanceUnit,
Mileage: 0,
CostPerMile: 0,
}
if currentFillup.IsTankFull != nil && *currentFillup.IsTankFull && (currentFillup.HasMissedFillup == nil || !(*currentFillup.HasMissedFillup)) {
distance := float32(currentFillup.OdoReading - lastFillup.OdoReading)
mileage.Mileage = distance / currentFillup.FuelQuantity
mileage.CostPerMile = distance / currentFillup.TotalAmount
currentOdoReading := float32(currentFillup.OdoReading);
lastFillupOdoReading := float32(lastFillup.OdoReading);
currentFuelQuantity := float32(currentFillup.FuelQuantity);
// If miles per gallon option and distanceUnit is km, convert from km to miles
// then check if fuel unit is litres. If it is, convert to gallons
if (mileageOption == "mpg" && mileage.DistanceUnit == db.KILOMETERS) {
currentOdoReading = common.KmToMiles(currentOdoReading);
lastFillupOdoReading = common.KmToMiles(lastFillupOdoReading);
if (mileage.FuelUnit == db.LITRE) {
currentFuelQuantity = common.LitreToGallon(currentFuelQuantity);
}
}
// If km_litre option or litre per 100km and distanceUnit is miles, convert from miles to km
// then check if fuel unit is not litres. If it isn't, convert to litres
if ((mileageOption == "km_litre" || mileageOption == "litre_100km") && mileage.DistanceUnit == db.MILES) {
currentOdoReading = common.MilesToKm(currentOdoReading);
lastFillupOdoReading = common.MilesToKm(lastFillupOdoReading);
if (mileage.FuelUnit == db.US_GALLON) {
currentFuelQuantity = common.GallonToLitre(currentFuelQuantity);
}
}
distance := float32(currentOdoReading - lastFillupOdoReading);
if (mileageOption == "litre_100km") {
mileage.Mileage = currentFuelQuantity / distance * 100;
} else {
mileage.Mileage = distance / currentFuelQuantity;
}
mileage.CostPerMile = distance / currentFillup.TotalAmount;
}

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,80 @@
"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",
"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

@@ -3,9 +3,20 @@ import { Line } from 'vue-chartjs'
import axios from 'axios'
import { mapState } from 'vuex'
import { string } from 'yargs'
export default {
extends: Line,
props: { vehicle: { type: Object, required: true }, since: { type: Date, default: '' }, user: { type: Object, required: true } },
props: {
vehicle: { type: Object, required: true },
since: { type: Date, default: '' },
user: { type: Object, required: true },
mileageOption: { type: string, default: 'litre_100km' },
},
data: function() {
return {
chartData: [],
}
},
computed: {
...mapState('utils', ['isMobile']),
},
@@ -17,20 +28,28 @@ export default {
this.fetchMileage()
},
},
data: function() {
return {
chartData: [],
}
},
mounted() {
this.fetchMileage()
},
methods: {
showChart() {
let mileageLabel = ''
switch (this.mileageOption) {
case 'litre_100km':
mileageLabel = 'L/100km'
break
case 'km_litre':
mileageLabel = 'km/L'
break
case 'mpg':
mileageLabel = 'mpg'
break
}
var labels = this.chartData.map((x) => x.date.substr(0, 10))
var dataset = {
steppedLine: true,
label: `${this.$t('odometer')} (${this.$t('unit.short.' + this.user.distanceUnitDetail.key)}/${this.$t('unit.short.' + this.vehicle.fuelUnitDetail.key)})`,
label: `Mileage (${mileageLabel})`,
fill: true,
data: this.chartData.map((x) => x.mileage),
}
@@ -41,6 +60,7 @@ export default {
.get(`/api/vehicles/${this.vehicle.id}/mileage`, {
params: {
since: this.since,
mileageOption: this.mileageOption,
},
})
.then((response) => {

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: '+';

231
ui/src/locales/da.json Normal file
View File

@@ -0,0 +1,231 @@
{
"quickentry": "Ingen hurtige indtastninger | Hurtig indtastning | Hurtige indtastninger",
"statistics": "Statistik",
"thisweek": "Denne uge",
"thismonth": "Denne måned",
"pastxdays": "Forrige en dag | Forrige {count} dage",
"pastxmonths": "Forrige en måned | Forrige {count} måneder",
"thisyear": "Dette år",
"alltime": "Hele tiden",
"noattachments": "Ingen vedhæftede filer indtil videre",
"attachments": "Vedhæftede filer",
"choosefile": "Vælg fil",
"addattachment": "Tilføj vedhæftning",
"sharedwith": "Delt med",
"share": "Del",
"you": "Du",
"addfillup": "Tilføj påfyldning",
"createfillup": "Opret påfyldning",
"deletefillup": "Slet denne påfyldning",
"addexpense": "Tilføj udgift",
"createexpense": "Opret udgift",
"deleteexpense": "Slet denne udgift",
"nofillups": "Ingen påfyldninger indtil videre",
"transfervehicle": "Overfør køretøj",
"settingssaved": "Indstillingerne er gemt",
"yoursettings": "Dine indstillinger",
"settings": "Indstillinger",
"changepassword": "Skift adgangskode",
"oldpassword": "Gammel adgangskode",
"newpassword": "Ny adgangskode",
"repeatnewpassword": "Gentag ny adgangskode",
"passworddontmatch": "Adgangskoderne stemmer ikke overens",
"save": "Gem",
"supportthedeveloper": "Støt udvikleren",
"buyhimabeer": "Køb ham en øl!",
"featurerequest": "Ønske om funktion",
"foundabug": "Fundet en fejl",
"currentversion": "Nuværende version",
"moreinfo": "Mere info",
"currency": "Valuta",
"distanceunit": "Afstandsenhed",
"dateformat": "Datoformat",
"createnow": "Opret nu",
"yourvehicles": "Dine køretøjer",
"menu": {
"quickentries": "Hurtige indtastninger",
"logout": "Log ud",
"import": "Import",
"home": "Hjem",
"settings": "Indstillinger",
"admin": "Admin",
"sitesettings": "Webstedsindstillinger",
"users": "Brugere",
"login": "Log ind"
},
"enterusername": "Indtast dit brugernavn",
"enterpassword": "Indtast din adgangskode",
"email": "E-mail",
"password": "Adgangskode",
"login": "Log ind",
"totalexpenses": "Samlede udgifter",
"fillupcost": "Tankningsomkostninger",
"otherexpenses": "Andre udgifter",
"addvehicle": "Tilføj køretøj",
"editvehicle": "Rediger køretøj",
"deletevehicle": "Slet køretøj",
"sharevehicle": "Del køretøj",
"makeowner": "Gør til ejer",
"lastfillup": "Seneste tankning",
"quickentrydesc": "Tag et billede af fakturaen eller brændstofpumpens display for at foretage en indtastning senere.",
"quickentrycreatedsuccessfully": "Hurtig indtastning oprettet med succes",
"uploadfile": "Upload fil",
"uploadphoto": "Upload foto",
"details": "Detaljer",
"odometer": "Kilometertæller",
"language": "Sprog",
"date": "Dato",
"pastfillups": "Tidligere tankninger",
"fuelsubtype": "Brændstoftype",
"fueltype": "Brændstoftype",
"quantity": "Mængde",
"gasstation": "Tankstation",
"fuel": {
"petrol": "Benzin",
"diesel": "Diesel",
"cng": "CNG",
"lpg": "LPG",
"electric": "Elektrisk",
"ethanol": "Ethanol"
},
"unit": {
"long": {
"litre": "Liter",
"gallon": "Gallon",
"kilowatthour": "Kilowatt-time",
"kilogram": "Kilogram",
"usgallon": "US Gallon",
"minutes": "Minutter",
"kilometers": "Kilometer",
"miles": "Miles"
},
"short": {
"litre": "L",
"gallon": "Gal",
"kilowatthour": "KwH",
"kilogram": "Kg",
"usgallon": "US Gal",
"minutes": "Min",
"kilometers": "Km",
"miles": "Mi"
}
},
"avgfillupqty": "Gns. påfyldningsmængde",
"avgfillupexpense": "Gns. påfyldningsudgift",
"avgfuelcost": "Gns. brændstofpris",
"per": "{0} per {1}",
"price": "Pris",
"total": "Total",
"fulltank": "Tank fuld",
"partialfillup": "Delvis påfyldning",
"getafulltank": "Fik du en fuld tank?",
"tankpartialfull": "Hvad sporer du?",
"by": "Ved",
"expenses": "Udgifter",
"expensetype": "Udgiftstype",
"noexpenses": "Ingen udgifter indtil videre",
"download": "Download",
"title": "Titel",
"name": "Navn",
"delete": "Slet",
"importdata": "Importer data til Hammond",
"importdatadesc": "Vælg en af følgende muligheder for at importere data til Hammond",
"import": "Importer",
"importcsv": "Hvis du har brugt {name} til at gemme dine køretøjsdata, skal du eksportere CSV-filen fra {name} og klikke her for at importere.",
"importgeneric": "Generisk påfyldningsimport",
"importgenericdesc": "CSV-import af påfyldninger.",
"choosecsv": "Vælg CSV",
"choosephoto": "Vælg foto",
"importsuccessfull": "Data importeret med succes",
"importerror": "Der opstod et problem med at importere filen. Kontrollér fejlmeddelelsen",
"importfrom": "Importer fra {0}",
"stepstoimport": "Trin til import af data fra {name}",
"choosecsvimport": "Vælg {name} CSV-filen og klik på importknappen.",
"choosedatafile": "Vælg CSV-filen og klik derefter på importknappen.",
"dontimportagain": "Sørg for ikke at importere filen igen, da det vil skabe gentagne indtastninger.",
"checkpointsimportcsv": "Når du har kontrolleret alle disse punkter, kan du blot importere CSV'en nedenfor.",
"importhintunits": "På samme måde skal du sørge for, at <u>Brændstofenhed</u> og <u>Brændstoftype</u> er korrekt indstillet for køretøjet.",
"importhintcurrdist": "Sørg for, at <u>Valuta</u> og <u>Afstandsenhed</u> er korrekt indstillet i Hammond. Importen vil ikke automatisk registrere valutaen fra filen, men bruge den valuta, der er indstillet for brugeren.",
"importhintnickname": "Sørg for, at køretøjets kaldenavn i Hammond er præcis det samme som navnet i Fuelly CSV-filen, ellers vil importen ikke fungere.",
"importhintvehiclecreated": "Sørg for, at du allerede har oprettet køretøjerne i Hammond-platformen.",
"importhintcreatecsv": "Eksportér dine data fra {name} i CSV-format. Instruktioner til at gøre dette kan findes",
"importgenerichintdata": "Data skal være i CSV-format.",
"here": "her",
"unprocessedquickentries": "Du har en hurtig indtastning, der skal behandles. | Du har {0} hurtige indtastninger, der venter på at blive behandlet.",
"show": "Vis",
"loginerror": "Der opstod en fejl ved login til din konto. {msg}",
"showunprocessed": "Vis kun ubehandlede",
"unprocessed": "ubehandlede",
"sitesettingdesc": "Opdater indstillinger på webstedsniveau. Disse vil blive brugt som standardværdier for nye brugere.",
"settingdesc": "Disse vil blive brugt som standardværdier, når du opretter en ny påfyldning eller udgift.",
"areyousure": "Er du sikker på, at du vil fortsætte med dette?",
"adduser": "Tilføj bruger",
"usercreatedsuccessfully": "Bruger oprettet med succes",
"userdisabledsuccessfully": "Bruger deaktiveret med succes",
"userenabledsuccessfully": "Bruger aktiveret med succes",
"role": "Rolle",
"created": "Oprettet",
"createnewuser": "Opret ny bruger",
"cancel": "Annuller",
"novehicles": "Det ser ud til, at du endnu ikke har oprettet et køretøj i systemet. Begynd med at oprette en indtastning for et af de køretøjer, du gerne vil spore.",
"processed": "Marker som behandlet",
"notfound": "Ikke fundet",
"timeout": "Siden fik en timeout under indlæsningen. Er du stadig forbundet til internettet?",
"clicktoselect": "Klik for at vælge...",
"expenseby": "Udgift af",
"selectvehicle": "Vælg et køretøj",
"expensedate": "Udgiftsdato",
"totalamountpaid": "Samlet betalt beløb",
"fillmoredetails": "Udfyld flere oplysninger",
"markquickentryprocessed": "Marker valgt hurtig indtastning som behandlet",
"referquickentry": "Henvis hurtig indtastning",
"deletequickentry": "Dette vil slette denne hurtige indtastning. Dette trin kan ikke fortrydes. Er du sikker?",
"fuelunit": "Brændstofenhed",
"fillingstation": "Navn på tankstation",
"comments": "Kommentarer",
"missfillupbefore": "Glemte du at indtaste påfyldningen før denne?",
"missedfillup": "Glemt påfyldning",
"fillupdate": "Påfyldningsdato",
"fillupsavedsuccessfully": "Påfyldning gemt med succes",
"expensesavedsuccessfully": "Udgift gemt med succes",
"vehiclesavedsuccessfully": "Køretøj gemt med succes",
"settingssavedsuccessfully": "Indstillinger gemt med succes",
"back": "Tilbage",
"nickname": "Kælenavn",
"registration": "Registrering",
"createvehicle": "Opret køretøj",
"make": "Mærke / Firma",
"model": "Model",
"yearmanufacture": "Produktionsår",
"enginesize": "Motorsize (i cc)",
"mysqlconnstr": "MySQL-forbindelsesstreng",
"testconn": "Test forbindelse",
"migrate": "Migrer",
"init": {
"migrateclarkson": "Migrer fra Clarkson",
"migrateclarksondesc": "Hvis du har en eksisterende Clarkson-installation, og du vil migrere dine data fra den, skal du trykke på følgende knap.",
"freshinstall": "Ny installation",
"freshinstalldesc": "Hvis du vil have en ny installation af Hammond, skal du trykke på følgende knap.",
"clarkson": {
"desc": "<p>Du skal sørge for, at denne installation af Hammond kan få adgang til MySQL-databasen, der bruges af Clarkson.</p><p>Hvis det ikke er direkte muligt, kan du lave en kopi af den database et sted, der er tilgængeligt fra denne instans.</p><p>Når det er gjort, skal du indtaste forbindelsesstrengen til MySQL-instansen i følgende format.</p><p>Alle brugerne importeret fra Clarkson vil have deres brugernavn som deres e-mail i Clarkson-databasen, og adgangskoden er indstillet til<span class='' style='font-weight:bold'>hammond</span></p><code>bruger:adgangskode@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local</code><br/><br/>",
"success": "Vi har migreret dataene fra Clarkson med succes. Du vil snart blive omdirigeret til login-siden, hvor du kan logge ind med din eksisterende e-mail og adgangskode: hammond"
},
"fresh": {
"setupadminuser": "Opsætning af administratorbrugere",
"yourpassword": "Din adgangskode",
"youremail": "Din e-mail",
"yourname": "Dit navn",
"success": "Du er blevet registreret med succes. Du vil snart blive omdirigeret til login-siden, hvor du kan logge ind og begynde at bruge systemet."
}
},
"roles": {
"ADMIN": "ADMIN",
"USER": "BRUGER"
},
"profile": "Profil",
"processedon": "Behandlet den",
"enable": "Aktivér",
"disable": "Deaktiver",
"confirm": "Fortsæt",
"labelforfile": "Mærke for denne fil"
}

View File

@@ -13,7 +13,7 @@
"addattachment": "Anhang hinzufügen",
"sharedwith": "Geteilt mit",
"share": "Teile",
"you": "Sie",
"you": "du",
"addfillup": "Tankfüllung erfassen",
"createfillup": "Erfasse Tankfüllung",
"deletefillup": "Lösche diese Tankfüllung",
@@ -28,7 +28,7 @@
"changepassword": "Passwort ändern",
"oldpassword": "Bisheriges Passwort",
"newpassword": "Neues Passwort",
"repeatnewpassword": "Neues Passwort wiederhiolen",
"repeatnewpassword": "Neues Passwort wiederholen",
"passworddontmatch": "Passwörter stimmen nicht überein",
"save": "Speichern",
"supportthedeveloper": "Unterstütze den Entwickler",
@@ -56,7 +56,7 @@
"password": "Passwort",
"login": "Anmelden",
"totalexpenses": "Gesamtausgaben",
"fillupcost": "Tank Ausgaben",
"fillupcost": "Tank-Ausgaben",
"otherexpenses": "Andere Ausgaben",
"addvehicle": "Fahrzeug hinzufügen",
"editvehicle": "Fahrzeug bearbeiten",
@@ -124,20 +124,20 @@
"name": "Name",
"delete": "Löschen",
"importdata": "Importiere Daten in Hammond",
"importdatadesc": "Wähle eine der folgenden Optionen um Daten in Hammond zu importieren",
"importdatadesc": "Wähle eine der folgenden Optionen, um Daten in Hammond zu importieren",
"import": "Importieren",
"importcsv": "Wenn du {name} nutzt um deine Fahrzeugdaten zu verwalten, exportiere die CSV Datei aus {name} und klicke hier zum importieren.",
"importcsv": "Wenn du {name} nutzt, um deine Fahrzeugdaten zu verwalten, exportiere die CSV Datei aus {name} und klicke hier, um zu importieren.",
"choosecsv": "CSV auswählen",
"choosephoto": "Foto auswählen",
"importsuccessfull": "Daten erfolgreich importiert",
"importerror": "Beim importieren der Datei ist ein Fehler aufgetreten. Details findest du in der Fehlermeldung",
"importerror": "Beim Importieren der Datei ist ein Fehler aufgetreten. Details findest du in der Fehlermeldung",
"importfrom": "Importiere von {name}",
"stepstoimport": "Schritte um Daten aus {name} zu importieren",
"choosecsvimport": "Wähle die {name} CSV aus und klicke den Button zum importieren.",
"dontimportagain": "Achte darauf, dass du die Datei nicht erneut importierst, da dies zu wiederholten Einträgen führen würde.",
"stepstoimport": "Schritte, um Daten aus {name} zu importieren",
"choosecsvimport": "Wähle die {name} CSV aus und klicke den Button, um zu importieren.",
"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",
@@ -156,11 +156,11 @@
"created": "Erstellt",
"createnewuser": "Erstelle neuen Benutzer",
"cancel": "Abbrechen",
"novehicles": "Du hast noch kein Fahrzeug erstellt. Lege jetzt einen Eintrag für das zu Verwaltende Auto an.",
"novehicles": "Du hast noch kein Fahrzeug erstellt. Lege jetzt einen Eintrag für das zu verwaltende Fahrzeug an.",
"processed": "Bearbeitet",
"notfound": "Nicht gefunden",
"timeout": "Das Laden der Seite hat eine Zeitüberschreitung verursacht. Bist du sicher, dass du noch mit dem Internet verbunden bist?",
"clicktoselect": "Klicke zum auswählen...",
"clicktoselect": "Klicke, um auszuwählen...",
"expenseby": "Ausgabe von",
"selectvehicle": "Wähle ein Fahrzeug aus",
"expensedate": "Datum der Ausgabe",
@@ -168,11 +168,11 @@
"fillmoredetails": "Weitere Details ausfüllen",
"markquickentryprocessed": "Markiere gewählten Schnelleintrag als bearbeitet",
"referquickentry": "Wähle Schnelleintrag",
"deletequickentry": "Willst du diesen Schnelleintrag wirklcih Löschen? Diese Aktion kann nicht rückgängig gemacht werden.",
"deletequickentry": "Willst du diesen Schnelleintrag wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden!",
"fuelunit": "Kraftstoffeinheit",
"fillingstation": "Tankstelle",
"comments": "Kommentare",
"missfillupbefore": "Hast du vergessen die vorherige Tankfüllung zu erfassen?",
"missfillupbefore": "Hast du vergessen, die vorherige Tankfüllung zu erfassen?",
"fillupdate": "Tankdatum",
"fillupsavedsuccessfully": "Tankfüllung erfolgreich gespeichert",
"expensesavedsuccessfully": "Ausgabe erfolgreich gespeichert",
@@ -184,7 +184,7 @@
"make": "Marke",
"model": "Modell",
"yearmanufacture": "Jahr der Erstzulassung",
"enginesize": "Hubraum (in cc)",
"enginesize": "Hubraum (in ccm)",
"testconn": "Teste Verbindung",
"migrate": "Migrieren",
"init": {
@@ -199,7 +199,7 @@
"fresh": {
"setupadminuser": "Erstelle einen Administrator",
"yourpassword": "Dein Passwort",
"youremail": "Deine E-Mail Adresse",
"youremail": "Deine E-Mail-Adresse",
"yourname": "Dein Name",
"success": "Du hast dich erfolgreich registriert. Du wirst in kürze zur Anmeldung weitergeleitet und kannst Anfangen Hammond zu verwenden."
}
@@ -214,4 +214,4 @@
"disable": "Sperren",
"confirm": "Bestätigen",
"labelforfile": "Bezeichnung für diese Datei"
}
}

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"
}
}

231
ui/src/locales/fr.json Normal file
View File

@@ -0,0 +1,231 @@
{
"quickentry": "Pas d'entrée rapide | Entrée rapide | Entrées rapides",
"statistics": "Statistiques",
"thisweek": "Cette semaine",
"thismonth": "Ce mois",
"pastxdays": "Dernier jour | Derniers {count} jours",
"pastxmonths": "Dernier mois | Derniers {count} mois",
"thisyear": "Cette année",
"alltime": "Tout le temps",
"noattachments": "Pas de piece jointe",
"attachments": "Pièces jointes",
"choosefile": "Choose File",
"addattachment": "Ajouter une pièce jointe",
"sharedwith": "Partager avec",
"share": "Partager",
"you": "Vous",
"addfillup": "Ajouter un plein",
"createfillup": "Créer un plein",
"deletefillup": "Supprimer ce plein",
"addexpense": "Ajouter une dépense",
"createexpense": "Créer une dépense",
"deleteexpense": "Supprimer cette dépense",
"nofillups": "Pas de plein",
"transfervehicle": "Transferer le Véhicule",
"settingssaved": "Paramètres sauvegardés avec succès",
"yoursettings": "Vos paramètres",
"settings": "Paramètres",
"changepassword": "Changer votre mot de passe",
"oldpassword": "Ancien mot de passe",
"newpassword": "Nouveau mot de passe",
"repeatnewpassword": "Répéter votre nouveau mot de passe",
"passworddontmatch": "Les mots de passe ne correspondent pas",
"save": "Sauvegarder",
"supportthedeveloper": "Supporter le développeur",
"buyhimabeer": "Acheter lui un café!",
"featurerequest": "Demande de fonctionnalité",
"foundabug": "Trouvé un bug",
"currentversion": "Version actuelle",
"moreinfo": "Plus d'informations",
"currency": "Monnaie",
"distanceunit": "Unité de distance",
"dateformat": "Format de data",
"createnow": "Créer Maintenant",
"yourvehicles": "Vos Véhicules",
"menu": {
"quickentries": "Entrée rapide",
"logout": "Se déconnecter",
"import": "Importer",
"home": "Accueil",
"settings": "Paramètres",
"admin": "Admin",
"sitesettings": "Paramètres du site",
"users": "Utilisateurs",
"login": "Connexion"
},
"enterusername": "Entrez votre nom d'utilisateur",
"enterpassword": "Entrez votre mot de passe",
"email": "Email",
"password": "Mot de passe",
"login": "connexion",
"totalexpenses": "Dépenses totales",
"fillupcost": "Coût des pleins",
"otherexpenses": "Autres dépenses",
"addvehicle": "Ajouter un Véhicule",
"editvehicle": "Editer un Véhicule",
"deletevehicle": "Supprimer un Véhicule",
"sharevehicle": "Partager un Véhicule",
"makeowner": "Changer le propriétaire",
"lastfillup": "Dernier plein",
"quickentrydesc": "Prendre une photo de la facture ou de l'écran de la pompe à essence pour créer une entrée plus tard.",
"quickentrycreatedsuccessfully": "Entrée rapide créée avec succès",
"uploadfile": "Téléverser un fichier",
"uploadphoto": "Téléverser une photo",
"details": "Détails",
"odometer": "Odomètre",
"language": "Langue",
"date": "Date",
"pastfillups": "Derniers pleins",
"fuelsubtype": "Sous-type de combustible",
"fueltype": "Type de combustible",
"quantity": "Quantité",
"gasstation": "Station service",
"fuel": {
"petrol": "Pétrol",
"diesel": "Diesel",
"cng": "CNG",
"lpg": "LPG",
"electric": "Electrique",
"ethanol": "Éthanol"
},
"unit": {
"long": {
"litre": "Litre",
"gallon": "Gallon",
"kilowatthour": "Kilowatt Heure",
"kilogram": "Kilogram",
"usgallon": "US Gallon",
"minutes": "Minutes",
"kilometers": "Kilometres",
"miles": "Miles"
},
"short": {
"litre": "Lt",
"gallon": "Gal",
"kilowatthour": "KwH",
"kilogram": "Kg",
"usgallon": "US Gal",
"minutes": "Mins",
"kilometers": "Km",
"miles": "Mi"
}
},
"avgfillupqty": "Qté de plein moyen",
"avgfillupexpense": "Prix du plein moyen",
"avgfuelcost": "Prix de l'essence moyen",
"per": "{0} par {1}",
"price": "Prix",
"total": "Total",
"fulltank": "Reservoir complet",
"partialfillup": "Plein partiel",
"getafulltank": "Est-ce que vous avez rempli tout votre reservoir?",
"tankpartialfull": "Le quel traquez-vous?",
"by": "Par",
"expenses": "Dépenses",
"expensetype": "Type de dépense",
"noexpenses": "Pas de dépense",
"download": "Télécharger",
"title": "Titre",
"name": "Nom",
"delete": "Supprimer",
"importdata": "Importer des données dans Hammond",
"importdatadesc": "Choisissez une option pour importer des données dans Hammond",
"import": "Importer",
"importcsv": "Si vous utilisiez {name} pour stocker les données de vos véhicules, exportez les données en format CSV depuis {name} et cliquez ici pour importer.",
"importgeneric": "Importation de plein générique",
"importgenericdesc": "Importation de plein avec un SVC.",
"choosecsv": "Choisir un CSV",
"choosephoto": "Choisir une Photo",
"importsuccessfull": "Données importée avec succès",
"importerror": "Il y a eu un problème lors de l'importation. Veuillez regarder le message d'erreur",
"importfrom": "Importer depuis {0}",
"stepstoimport": "Étapes pour importer des données depuis {name}",
"choosecsvimport": "Choisissez le fichier CSV de {name} et appuyez sur le bouton pour importer.",
"choosedatafile": "Choisissez le fichier CSV et appuyez sur le bouton pour importer.",
"dontimportagain": "Faites attention à ne pas importer le fichier à nouveau car cela va créer des entrées dupliquées.",
"checkpointsimportcsv": "Dès que vous avez vérifié tous ces points, importez le CSV ci-dessous.",
"importhintunits": "De la même manière, make sure that the <u>Fuel Unit</u> and <u>Fuel Type</u> are correctly set in the Vehicle.",
"importhintcurrdist": "Soyez sûre que la <u>Monnaie</u> et l'<u>Unité de distance</u> sont mises correctement dans Hammond. L'importation ne detectera pas automatiquement la Monnaie du fichier mais utilisera les valeurs de l'utilisateur.",
"importhintnickname": "Soyez sûre que le nom du véhicule dans Hammon est exactement le même que le nom dans Fuelly, sinon, l'importation ne fonctionnera pas.",
"importhintvehiclecreated": "Soyez sûre d'avoir déjà créé le véhicule dans la plate-forme Hammond.",
"importhintcreatecsv": "Exportez vos données depuis {name} en format CSV. Les étapes pour faire ceci peuvent être trouvées",
"importgenerichintdata": "Les données doivent être au format CSV.",
"here": "ici",
"unprocessedquickentries": "Vous avez 1 entrée rapide en attente d'être traîtée. | Vous avez {0} entrée rapide en attente d'être traîtée.",
"show": "montrer",
"loginerror": "Il y a eu une erreur lors de la connexion a votre compte: {msg}",
"showunprocessed": "Montrer seulement les non-traîtées",
"unprocessed": "non-traîtée",
"sitesettingdesc": "Mettre à jour les paramètres du site. Ces valeurs seront utilisées par défaut pour les nouveaux utilisateurs.",
"settingdesc": "Ces valeurs seront utilisées par défaut lorsque vous créez un nouveau plein ou une nouvelle dépense.",
"areyousure": "Êtes-vous sûre de vouloir faire ceci?",
"adduser": "Ajouter un utilisateur",
"usercreatedsuccessfully": "Utilisateur créé avec succès",
"userdisabledsuccessfully": "Utilisateur désactivé avec succès",
"userenabledsuccessfully": "Utilisateur activé avec succès",
"role": "Rôle",
"created": "Créé",
"createnewuser": "Créer un nouvel utilisateur",
"cancel": "Annuler",
"novehicles": "Il semble que vous n'avez pas encore créé de véhicule dans le système pour le moment. Commencez par créer une entrée pour un des véhicule que vous voulez traquer.",
"processed": "Marquer en tant que traîté",
"notfound": "Non Trouvé",
"timeout": "La page a expiré lors du chargement. Êtes-vous sûre d'être toujours connecté à Internet?",
"clicktoselect": "Cliquer pour sélectionner...",
"expenseby": "Dépense par",
"selectvehicle": "Selectionner un véhicule",
"expensedate": "Date de la dépense",
"totalamountpaid": "Montant payé total",
"fillmoredetails": "Entrer plus de détails",
"markquickentryprocessed": "Marquer l'entrée rapide séléctionnée en tant que traîtée",
"referquickentry": "Faire référence à une entrée rapide",
"deletequickentry": "Ceci va supprimer l'entrée rapide. Cette action ne peut pas être annulée. Êtes-vous sûre?",
"fuelunit": "Unité de combustible",
"fillingstation": "Nom de la station service",
"comments": "Commentaires",
"missfillupbefore": "Est-ce que vous avez manqué un plein avant celui-ci?",
"missedfillup": "Plein manqué",
"fillupdate": "Date du plein",
"fillupsavedsuccessfully": "Plein sauvegardé avec succès",
"expensesavedsuccessfully": "Dépense sauvegardé avec succès",
"vehiclesavedsuccessfully": "Véhicule sauvegardé avec succès",
"settingssavedsuccessfully": "Paramètres sauvegardés avec succès",
"back": "Retour",
"nickname": "Surnom",
"registration": "Immatriculation",
"createvehicle": "Créer un Véhicule",
"make": "Marque",
"model": "Modèle",
"yearmanufacture": "Année de production",
"enginesize": "Taille du moteur (en chevaux)",
"mysqlconnstr": "Chaîne de caractère pour la connexion MySQL",
"testconn": "Tester la Connexion",
"migrate": "Migrer",
"init": {
"migrateclarkson": "Migrer depuis Clarkson",
"migrateclarksondesc": "Si vous avez un déploiement Clarkson existant et que vous souhaitez migrer vos données à partir de celui-ci, appuyez sur le bouton suivant.",
"freshinstall": "Nouvelle Installation",
"freshinstalldesc": "Si vous voulez une nouvelle installation de Hammond, appuyez sur le bouton suivant.",
"clarkson": {
"desc": "<p>Vous devez vous assurer que ce déploiement de Hammond peut accéder à la base de données MySQL utilisée par Clarkson.</p><p>Si ce n'est pas directement possible, vous pouvez faire une copie de cette base de données autre part qui est accessible à partir de cette instance.</p><p>Une fois cela fait, entrez la chaîne de connexion à l'instance MySQL au format suivant.</p><p>Tous les utilisateurs importés de Clarkson auront leur nom d'utilisateur comme e-mail dans la base de données Clarkson et le mot de passe défini sur <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": "Nous avons migré avec succès les données depuis Clarkson. Vous serez bientôt redirigé vers l'écran de connexion où vous pourrez vous connecter en utilisant votre adresse e-mail et votre mot de passe existants : hammond"
},
"fresh": {
"setupadminuser": "Configurer le compte administrateur",
"yourpassword": "Votre Mot de passe",
"youremail": "Votre Email",
"yourname": "Votre Nom",
"success": "Vous avez été inscrit avec succès. Vous allez être redirigé vers la page de connexion très bientôt, vous pourrez vous connecter et commencer à utiliser le système."
}
},
"roles": {
"ADMIN": "ADMIN",
"USER": "USER"
},
"profile": "Profile",
"processedon": "Traîté le",
"enable": "Activer",
"disable": "Désactiver",
"confirm": "Continuer",
"labelforfile": "Label pour ce fichier"
}

231
ui/src/locales/hu.json Normal file
View File

@@ -0,0 +1,231 @@
{
"quickentry": "Nincs gyors bejegyzés | Gyors bejegyzés | Gyors bejegyzések",
"statistics": "Statisztikák",
"thisweek": "Ez a hét",
"thismonth": "Ez a hónap",
"pastxdays": "Elmúlt 1 nap | Elmúlt {count} nap",
"pastxmonths": "Elmúlt 1 hónap | Past {count} hónap",
"thisyear": "Ez az év",
"alltime": "Összes",
"noattachments": "Nincs csatolmány eddig",
"attachments": "Csatolmányok",
"choosefile": "Válassz fájlt",
"addattachment": "Csatolmány hozzáadása",
"sharedwith": "Megosztva vele",
"share": "Megosztás",
"you": "Te",
"addfillup": "Tankolás hozzáadása",
"createfillup": "Tankolás létrehozása",
"deletefillup": "Tankolás törlése",
"addexpense": "Kiadás hozzáadása",
"createexpense": "Kiadás létrehozása",
"deleteexpense": "Kiads törlése",
"nofillups": "Nincs tankolás eddig",
"transfervehicle": "Jármű átruházása",
"settingssaved": "Beállítások sikeresen mentve",
"yoursettings": "Beállításaid",
"settings": "Beállítások",
"changepassword": "Jelszó megváltoztatása",
"oldpassword": "Régi jelszó",
"newpassword": "Új jelszó",
"repeatnewpassword": "Jelszó mégegyszer",
"passworddontmatch": "Jelszavak nem egyeznek",
"save": "Mentés",
"supportthedeveloper": "Támogsad a fejlesztőt",
"buyhimabeer": "Vegyél neki egy sört!",
"featurerequest": "Funkció kérése",
"foundabug": "Hibát találtam",
"currentversion": "Aktuális verzió",
"moreinfo": "Több információ",
"currency": "Pénznem",
"distanceunit": "Távolság mértékegysége",
"dateformat": "Dátum formátuma",
"createnow": "Létrehozás most",
"yourvehicles": "Járműveid",
"menu": {
"quickentries": "Gyors bejegyzések",
"logout": "Kijelentkezés",
"import": "Betöltés",
"home": "Kezdőlap",
"settings": "Beállítások",
"admin": "Adminisztrátor",
"sitesettings": "Oldal beállításai",
"users": "Felhasználók",
"login": "Bejelentkezés"
},
"enterusername": "Írd be a felhasználó neved",
"enterpassword": "Írd be a jelszavad",
"email": "Email",
"password": "Jelszó",
"login": "bejelentkezés",
"totalexpenses": "Összeskiadás",
"fillupcost": "Tankolás költsége",
"otherexpenses": "Egyéb kiadás",
"addvehicle": "Jármű hozzáadása",
"editvehicle": "Jármű szerkesztése",
"deletevehicle": "Jármű törlése",
"sharevehicle": "Jármű megosztása",
"makeowner": "Tulajdonosnak jelölés",
"lastfillup": "Utolsó tankolás",
"quickentrydesc": "Csinálj képet a blokkról/számláról vagy a tankoló mérőóráról hogy fel tudd vinni később az adatokat.",
"quickentrycreatedsuccessfully": "Gyors bejegyzés sikeresen létrehozva",
"uploadfile": "Fájl feltöltése",
"uploadphoto": "Fotó feltöltése",
"details": "Részletek",
"odometer": "Kilóméter óra",
"language": "Nyelv",
"date": "Dátum",
"pastfillups": "Előző tankolások",
"fuelsubtype": "Üzemanyag altípus",
"fueltype": "Üzemanyag típus",
"quantity": "Mennyiség",
"gasstation": "Benzinkút",
"fuel": {
"petrol": "Benzin",
"diesel": "Dízel",
"cng": "CNG",
"lpg": "LPG",
"electric": "Elektromos",
"ethanol": "Etanol"
},
"unit": {
"long": {
"litre": "Liter",
"gallon": "Gallon",
"kilowatthour": "Kilowattóra",
"kilogram": "Kilogram",
"usgallon": "US Gallon",
"minutes": "Perc",
"kilometers": "Kilóméter",
"miles": "Mérföld"
},
"short": {
"litre": "L",
"gallon": "Gal",
"kilowatthour": "KwH",
"kilogram": "Kg",
"usgallon": "US Gal",
"minutes": "P",
"kilometers": "Km",
"miles": "Mérf"
}
},
"avgfillupqty": "Átl. tankolási menny.",
"avgfillupexpense": "Átl. tankolási kiadás",
"avgfuelcost": "Átl. üzemanyag ár",
"per": "{0} per {1}",
"price": "Ár",
"total": "Összesen",
"fulltank": "Tele tank",
"partialfillup": "Részleges tankolás",
"getafulltank": "Teletankoltál?",
"tankpartialfull": "Which do you track?",
"by": "által",
"expenses": "Kiadások",
"expensetype": "Kiadás típusa",
"noexpenses": "Nincs kiadás eddig",
"download": "Letöltés",
"title": "Címe",
"name": "Neve",
"delete": "Törlés",
"importdata": "Adata betöltésa Hammondba",
"importdatadesc": "Válassz az alábbi lehetőségek közül, hogy betöltsd az adatokat",
"import": "Betöltés",
"importcsv": "Ha már használtad {name}-t hogy tárold a jármű adatait, exportáld CSV-be {name}-ból/ből majd kattints it a Betöltésre.",
"importgeneric": "Általános tanoklások betöltése",
"importgenericdesc": "Tankolás CSV betöltés.",
"choosecsv": "CSV választása",
"choosephoto": "Fotó választása",
"importsuccessfull": "Adat sikeresen betöltve",
"importerror": "Betöltés közben hiba lépett fel. Kérem ellenőrizze a hibaüzenetet.",
"importfrom": "Betöltés innen: {0}",
"stepstoimport": "Lépések az adat betöltéséhez {name}-ból/ből",
"choosecsvimport": "Válassza a/az {name} CSV-t és nyomja meg a betöltés gombot.",
"choosedatafile": "Válassza a CSV fájlt majd nyomja meg az importálás gombot.",
"dontimportagain": "Legyen biztos benne, hogy nem importálja újra ugyan azt a fájlt. Dublikált bejegyzéseket eredményez.",
"checkpointsimportcsv": "Amikor végzett az összes ponttal, töltse be a CSV-t lejjebb.",
"importhintunits": "Hasonlóan, legyen biztos benne, hogy az <u>Üzemanyag egysége</u> és az <u>Üzemanyag Típúsa</u> helyesek a Jármű beállításaiban.",
"importhintcurrdist": "Ellenőrizze, hogy a <u>Pénznem</u> és a <u>Távolság mértékegysége</u> be lettek állítva a Hammond-ban. A betöltés automatikusan nem ismeri fel a bejegyzések Pénznemét, helyette a felhaszáló alapértlemzett beállítása lesz alkalmazva.",
"importhintnickname": "Ellenőrizze, hogy a Jármű neve Hammond-ban megegyezik a Fuelly CSV-ben használttal vagy a betöltés nem fog működni.",
"importhintvehiclecreated": "Ellenőrizze, hogy a Járművek már léteznek a Hammond rendszerében.",
"importhintcreatecsv": "Mentse adatát {name}-ból/ből CSV formátumba. Steps to do that can be found",
"importgenerichintdata": "Az adatnak CSV formátumban kell lennie.",
"here": "itt",
"unprocessedquickentries": "Egy gyors bejegyzés feldolgozva. | {0} gyorsbejegyzés vár feldolgozásra.",
"show": "Mutasd",
"loginerror": "Hiba történt a bejelentkezés során. {msg}",
"showunprocessed": "Csak nem feldolgozottak",
"unprocessed": "feldolgozatlan",
"sitesettingdesc": "Oldal beállításainak módosítása. Ezek lesznek használva új felhazsnálók létrehozásakor.",
"settingdesc": "Ezek lesznak alkalmazva alapértelmezett értékként, tankolásnál vagy kiadásnál.",
"areyousure": "Biztos hogy ezt akarja?",
"adduser": "Felhasználó hozzáadása",
"usercreatedsuccessfully": "Felhasználó sikeresen létrehozva",
"userdisabledsuccessfully": "Felhasználó sikeresen letiltva",
"userenabledsuccessfully": "Felhasználó sikeresen engedélyezve",
"role": "Szerepkör",
"created": "Létrehozva",
"createnewuser": "Új felhasználó létrehozása",
"cancel": "Mégsem",
"novehicles": "Eddig nem hozott létre járművet a rendszerében. Hozzon létre egyet amelynek adatait követni szeretné.",
"processed": "Megjelölés feldolgozottnak",
"notfound": "Nem talált",
"timeout": "A lap túl sokáig nem töltött be. Biztos benne, hogy még mindig van \ninternet elérése?",
"clicktoselect": "Válasszon...",
"expenseby": "Kiadások ez szerint",
"selectvehicle": "Válassz járművet",
"expensedate": "Kiadás dátuma",
"totalamountpaid": "Összes kifizetés",
"fillmoredetails": "Több részlet kitöltése",
"markquickentryprocessed": "Kiválasztott gyorsbejegyzések megjelölése feldolgozottként",
"referquickentry": "Gzorsbejegzy;s referencia",
"deletequickentry": "Ez kitörli ezt a gyorsbejegyzést. Ez a lépés visszafordíthatatlan. Biztos benne?",
"fuelunit": "Üzemenyag egység",
"fillingstation": "Benzinkút neve",
"comments": "Komment",
"missfillupbefore": "Nem töltötte ki az előző tankolást?",
"missedfillup": "Kihagyott tankolás",
"fillupdate": "Tankolás dátuma",
"fillupsavedsuccessfully": "Tankolás sikeresen mentve",
"expensesavedsuccessfully": "Kiadás sikeresen mentve",
"vehiclesavedsuccessfully": "Jármű sikeresen mentve",
"settingssavedsuccessfully": "Beállítások sikeresen mentve",
"back": "Vissza",
"nickname": "Becenév",
"registration": "Rendszám",
"createvehicle": "Jármű létrehozása",
"make": "Gyártó",
"model": "Model",
"yearmanufacture": "Gyártás éve",
"enginesize": "Motorméret (cm3)",
"mysqlconnstr": "Mysql Connection String",
"testconn": "Kapcsolat tesztelése",
"migrate": "Migrálás",
"init": {
"migrateclarkson": "Migrálás Clarkson-ból",
"migrateclarksondesc": "Ha van egy meglévő Clarkson telepítése, és szeretné adatait átmozgatni onnan, nyomja meg a következő gombot.",
"freshinstall": "Tiszta telepítés",
"freshinstalldesc": "Ha szeretné a Hammond tiszta telepítését, nyomja meg a következő gombot.",
"clarkson": {
"desc": "<p>Biztosítania kell, hogy ez a Hammond telepítés el tudja érni a Clarkson által készített MySQL adatbázist.</p><p>Ha ez nem megoldható, készíthet egy másolatot az adatbázisról, és elhelyzheti olyan helyre amely elérhető innen.</p><p>Amikor ezzel kész van, Állítsa me a connection String-et a MySQL-hez a következő formátumban.</p><p>Az összes Clarkson-ból migrált felhasználónak az e-mail címük lesz a felhasználó nevük, és a jelszavuk <span class='' style='font-weight:bold'>hammond</span> lesz</p><code>user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local</code><br/><br/>",
"success": "Sikeresen migráltuk az adatot Clarkson-ból. Hamarosan át lesz irányítva a bejelentkezési oldalra, ahol használhatja a meglévő fiókját, és jelszavát: hammond"
},
"fresh": {
"setupadminuser": "Adminisztrátor felhasználó beállítása",
"yourpassword": "Ön jelszava",
"youremail": "Ön e-mailcíme",
"yourname": "Ön neve",
"success": "Ön sikeresen regisztrált. Át lesz irányítva a bejelentkezési képernyőre, ahol be tud jelentkezni és elkezdheti használni a rendszert."
}
},
"roles": {
"ADMIN": "ADMIN",
"USER": "FELHASZNÁLÓ"
},
"profile": "Profil",
"processedon": "Felfdolgozva ekkor",
"enable": "Engedélyezve",
"disable": "Letiltva",
"confirm": "Megerősítés",
"labelforfile": "Címke ehheza fájlhoz"
}

231
ui/src/locales/sl.json Normal file
View File

@@ -0,0 +1,231 @@
{
"quickentry": "Ni hitrih vnosov | Hiter vnos | Hitri vnosi",
"statistics": "Statistika",
"thisweek": "Ta teden",
"thismonth": "Ta mesec",
"pastxdays": "Zadnji dan | Zadnjih {count} dni",
"pastxmonths": "Zadnji mesec | Zadnji {count} mesecev",
"thisyear": "To leto",
"alltime": "Celoten čas",
"noattachments": "Zaenkrat še ni priponk",
"attachments": "Priloge",
"choosefile": "Izberi datoteko",
"addattachment": "Dodaj prilogo",
"sharedwith": "V skupni rabi z",
"share": "Deli",
"you": "Ti",
"addfillup": "Dodaj polnjenje",
"createfillup": "Ustvarite polnjenje",
"deletefillup": "Izbriši to polnjenje",
"addexpense": "Dodaj strošek",
"createexpense": "Ustvari strošek",
"deleteexpense": "Izbriši ta strošek",
"nofillups": "Zaenkrat še brez polnjenja",
"transfervehicle": "Prevozno sredstvo",
"settingssaved": "Nastavitve so uspešno shranjene",
"yoursettings": "Vaše nastavitve",
"settings": "Nastavitve",
"changepassword": "Spremeni geslo",
"oldpassword": "Staro geslo",
"newpassword": "Novo geslo",
"repeatnewpassword": "Ponovite novo geslo",
"passworddontmatch": "Vrednosti gesel se ne ujemajo",
"save": "Shrani",
"supportthedeveloper": "Podprite razvijalca",
"buyhimabeer": "Kupi mu pivo!",
"featurerequest": "Nova funkcionalnost",
"foundabug": "Našel sem hrošča",
"currentversion": "Trenutna verzija",
"moreinfo": "Več informacij",
"currency": "Valuta",
"distanceunit": "Enota razdalje",
"dateformat": "Format datuma",
"createnow": "Ustvari zdaj",
"yourvehicles": "Vaša vozila",
"menu": {
"quickentries": "Hitri vnosi",
"logout": "Odjava",
"import": "Uvoz",
"home": "Domov",
"settings": "Nastavitve",
"admin": "Skrbnik",
"sitesettings": "Nastavitve spletnega mesta",
"users": "Uporabniki",
"login": "Vpiši se"
},
"enterusername": "Vnesite svoje uporabniško ime",
"enterpassword": "Vnesite vaše geslo",
"email": "E-naslov",
"password": "Geslo",
"login": "Vpiši se",
"totalexpenses": "Skupni stroški",
"fillupcost": "Stroški polnjenja",
"otherexpenses": "Drugi stroški",
"addvehicle": "Dodaj vozilo",
"editvehicle": "Uredi vozilo",
"deletevehicle": "Izbriši vozilo",
"sharevehicle": "Deli vozilo",
"makeowner": "Postani lastnik",
"lastfillup": "Zadnje polnjenje",
"quickentrydesc": "Posnemite sliko računa ali zaslona črpalke za gorivo, da ju lahko vnesete pozneje.",
"quickentrycreatedsuccessfully": "Hitri vnos je bil uspešno ustvarjen",
"uploadfile": "Naloži datoteko",
"uploadphoto": "Naloži fotografijo",
"details": "Podrobnosti",
"odometer": "Odometer",
"language": "Jezik",
"date": "Datum",
"pastfillups": "Prejšnja polnjenja",
"fuelsubtype": "Podvrsta goriva",
"fueltype": "Vrsta goriva",
"quantity": "Količina",
"gasstation": "Bencinska črpalka",
"fuel": {
"petrol": "Bencin",
"diesel": "Dizelsko gorivo",
"cng": "CNG",
"lpg": "LPG",
"electric": "Elektrika",
"ethanol": "Etanol"
},
"unit": {
"long": {
"litre": "Liter",
"gallon": "Galon",
"kilowatthour": "Kilovatna ura",
"kilogram": "Kilogram",
"usgallon": "Ameriška galona",
"minutes": "Minute",
"kilometers": "Kilometri",
"miles": "Milje"
},
"short": {
"litre": "Lit",
"gallon": "Gal",
"kilowatthour": "KwH",
"kilogram": "Kg",
"usgallon": "US Gal",
"minutes": "Min",
"kilometers": "Km",
"miles": "Mi"
}
},
"avgfillupqty": "Povprečna količina polnjenja",
"avgfillupexpense": "Povprečni stroški polnjenja",
"avgfuelcost": "Povprečni strošek goriva",
"per": "{0} na {1}",
"price": "Cena",
"total": "Skupaj",
"fulltank": "Rezervoar poln",
"partialfillup": "Delno polnjenje",
"getafulltank": "Ste dobili poln rezervoar?",
"tankpartialfull": "Kateremu sledite?",
"by": "Od",
"expenses": "Stroški",
"expensetype": "Vrsta stroška",
"noexpenses": "Zaenkrat ni bilo stroškov",
"download": "Prenesi",
"title": "Naslov",
"name": "Ime",
"delete": "Izbriši",
"importdata": "Uvozite podatke v Hammond",
"importdatadesc": "Za uvoz podatkov v Hammond izberite med naslednjimi možnostmi",
"import": "Uvozi",
"importcsv": "Če ste za shranjevanje podatkov o vozilu uporabljali {name}, izvozite datoteko CSV iz {name} in kliknite tukaj za uvoz.",
"importgeneric": "Generični uvoz polnjenja",
"importgenericdesc": "CSV uvoz poljenja.",
"choosecsv": "Izberite CSV",
"choosephoto": "Izberite fotografijo",
"importsuccessfull": "Podatki so bili uspešno uvoženi",
"importerror": "Pri uvozu datoteke je prišlo do težave. ",
"importfrom": "Uvoz iz {0}",
"stepstoimport": "Koraki za uvoz podatkov iz {name}",
"choosecsvimport": "Izberite {name} CSV in pritisnite gumb za uvoz.",
"choosedatafile": "Izberite datoteko CSV in pritisnite gumb za uvoz.",
"dontimportagain": "Prepričajte se, da datoteke ne uvozite znova, ker boste s tem ustvarili ponavljajoče se vnose.",
"checkpointsimportcsv": "Ko preverite vse te točke, preprosto uvozite spodnji CSV.",
"importhintunits": "Podobno se prepričajte, da sta <u>Enota za gorivo</u> in <u>Vrsta goriva</u> pravilno nameščeni pod Vozilo.",
"importhintcurrdist": "Prepričajte se, da sta <u>Valuta</u> in <u>Enota razdalje</u> pri Hammondu uporabniku pravilno nastavljeni.",
"importhintnickname": "Prepričajte se, da je vzdevek vozila v Hammondu popolnoma enak imenu v CSV datoteki polnjenj, sicer uvoz ne bo deloval.",
"importhintvehiclecreated": "Prepričajte se, da ste že ustvarili vozila na platformi Hammond.",
"importhintcreatecsv": "Izvozite svoje podatke iz {name} v formatu CSV. ",
"importgenerichintdata": "Podatki morajo biti v formatu CSV.",
"here": "tukaj",
"unprocessedquickentries": "Za obdelavo imate en hiter vnos. | Za obdelavo imate {0} hitrih vnosov.",
"show": "Prikaži",
"loginerror": "Pri prijavi v vaš račun je prišlo do napake. ",
"showunprocessed": "Pokaži samo neobdelano",
"unprocessed": "neobdelano",
"sitesettingdesc": "Posodobite nastavitve na ravni aplikacije. Te bodo uporabljene kot privzete vrednosti za nove uporabnike.",
"settingdesc": "Te bodo uporabljene kot privzete vrednosti vsakič, ko ustvarite novo polnjenje ali strošek.",
"areyousure": "Ste prepričani, da želite to narediti?",
"adduser": "Dodaj uporabnika",
"usercreatedsuccessfully": "Uporabnik je bil uspešno ustvarjen",
"userdisabledsuccessfully": "Uporabnik uspešno onemogočen",
"userenabledsuccessfully": "Uporabnik je uspešno omogočen",
"role": "Vloga",
"created": "Ustvarjeno",
"createnewuser": "Ustvari novega uporabnika",
"cancel": "Prekliči",
"novehicles": "Videti je, da še niste ustvarili vozila v sistemu. ",
"processed": "Označi obdelano",
"notfound": "Ni najdeno",
"timeout": "Med nalaganjem strani je potekla časovna omejitev. ",
"clicktoselect": "Kliknite za izbiro ...",
"expenseby": "Stroški po",
"selectvehicle": "Izberite vozilo",
"expensedate": "Datum izdatka",
"totalamountpaid": "Skupni plačani znesek",
"fillmoredetails": "Izpolnite več podrobnosti",
"markquickentryprocessed": "Označi izbrani hitri vnos kot obdelan",
"referquickentry": "Oglejte si hiter vnos",
"deletequickentry": "S tem boste izbrisali ta hitri vnos. ",
"fuelunit": "Enota za gorivo",
"fillingstation": "Ime bencinske postaje",
"comments": "Komentarji",
"missfillupbefore": "Ste pred tem zamudili vnos zapolnitve?",
"missedfillup": "Zamujeno polnjenje",
"fillupdate": "Datum polnjenja",
"fillupsavedsuccessfully": "Polnjenje je bil uspešno shranjeno",
"expensesavedsuccessfully": "Stroški so uspešno shranjeni",
"vehiclesavedsuccessfully": "Vozilo je uspešno shranjeno",
"settingssavedsuccessfully": "Nastavitve so uspešno shranjene",
"back": "Nazaj",
"nickname": "Vzdevek",
"registration": "Registracija",
"createvehicle": "Ustvarite vozilo",
"make": "Znamka / Podjetje",
"model": "Model",
"yearmanufacture": "Leto izdelave",
"enginesize": "Prostornina motorja (v cc)",
"mysqlconnstr": "Niz povezave Mysql",
"testconn": "Testna povezava",
"migrate": "Preseli",
"init": {
"migrateclarkson": "Selitev iz Clarksona",
"migrateclarksondesc": "Če imate obstoječo Clarkson namestitev in želite iz nje preseliti svoje podatke, pritisnite naslednji gumb.",
"freshinstall": "Sveža namestitev",
"freshinstalldesc": "Če želite novo namestitev Hammonda, pritisnite naslednji gumb.",
"clarkson": {
"desc": "<p>Zagotoviti morate, da lahko ta uvedba Hammonda dostopa do baze podatkov MySQL, ki jo uporablja Clarkson.</p><p>Če to ni neposredno mogoče, lahko naredite kopijo te zbirke podatkov nekje, kjer je dostopna iz tega primerka.</p><p>Ko je to storjeno, vnesite povezovalni niz v primerek MySQL v naslednji obliki.</p><p>Vsi uporabniki, uvoženi iz Clarksona, bodo imeli svoje uporabniško ime kot e-pošto v bazi podatkov Clarkson in geslo nastavljeno na<span class='' style='font-weight:bold'>hammond</span></p><code>uporabnik:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4</code><br/><br/>",
"success": "Uspešno smo prenesli podatke iz Clarksona. "
},
"fresh": {
"setupadminuser": "Nastavitev skrbniških uporabnikov",
"yourpassword": "Vaše geslo",
"youremail": "Vaš e-poštni naslov",
"yourname": "Vaše ime",
"success": "Uspešno ste se registrirali. "
}
},
"roles": {
"ADMIN": "SKRBNIK",
"USER": "UPORABNIK"
},
"profile": "Profil",
"processedon": "Obdelano dne",
"enable": "Omogoči",
"disable": "Onemogoči",
"confirm": "Kar daj",
"labelforfile": "Oznaka za to datoteko"
}

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

@@ -410,6 +410,24 @@ export default [
},
props: (route) => ({ user: store.state.auth.currentUser || {} }),
},
{
path: '/import/drivvo',
name: 'import-drivvo',
component: () => lazyLoadView(import('@views/import-drivvo.vue')),
meta: {
authRequired: true,
},
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

@@ -0,0 +1,172 @@
<script>
import Layout from '@layouts/main.vue'
import { mapState } from 'vuex'
import axios from 'axios'
export default {
page: {
title: 'Import Drivvo',
meta: [{ name: 'description', content: 'The Import Drivvo page.' }],
},
components: { Layout },
props: {
user: {
type: Object,
required: true,
},
},
data: function() {
return {
myVehicles: [],
file: null,
selectedVehicle: null,
tryingToCreate: false,
errors: [],
importLocation: true,
}
},
computed: {
...mapState('utils', ['isMobile']),
...mapState('vehicles', ['vehicles']),
uploadButtonLabel() {
if (this.isMobile) {
if (this.file == null) {
return 'Choose Photo'
} else {
return ''
}
} else {
if (this.file == null) {
return 'Choose CSV'
} else {
return ''
}
}
},
},
mounted() {
this.myVehicles = this.vehicles
},
methods: {
importDrivvo() {
console.log('Import from drivvo')
if (this.file == null) {
return
}
this.tryingToCreate = true
this.errorMessage = ''
const formData = new FormData()
formData.append('vehicleID', this.selectedVehicle)
formData.append('importLocation', this.importLocation)
formData.append('file', this.file, this.file.name)
axios
.post(`/api/import/drivvo`, formData)
.then((data) => {
this.$buefy.toast.open({
message: 'Data Imported Successfully',
type: 'is-success',
duration: 3000,
})
this.file = null
setTimeout(() => this.$router.push({ name: 'home' }), 1000)
})
.catch((ex) => {
this.$buefy.toast.open({
duration: 5000,
message: 'There was some issue with importing the file. Please check the error message',
position: 'is-bottom',
type: 'is-danger',
})
if (ex.response && ex.response.data.errors) {
this.errors = ex.response.data.errors
}
})
.finally(() => {
this.tryingToCreate = false
})
},
},
}
</script>
<template>
<Layout>
<div class="columns box">
<div class="column">
<h1 class="title">Import from Drivvo</h1>
</div>
</div>
<br />
<div class="columns">
<div class="column">
<p class="subtitle"> Steps to import data from Drivvo</p>
<ol>
<li>Export your data from Drivvo in the CSV format.</li>
<li>Select the vehicle the exported data is for. You may need to create the vehicle in Hammond first if you haven't already done so</li>
<li
>Make sure that the <u>Currency</u> and <u>Distance Unit</u> are set correctly in Hammond. Drivvo does not include this information in
their export, instead Hammond will use the values set for the user.</li
>
<li>Similiarly, make sure that the <u>Fuel Unit</u> and <u>Fuel Type</u> are correctly set in the Vehicle.</li>
<li>Once you have checked all these points, select the vehicle and import the CSV below.</li>
<li><b>Make sure that you do not import the file again as that will create repeat entries.</b></li>
</ol>
</div>
</div>
<p
><b>PS:</b> If you have <em>'income'</em> and <em>'trips'</em> in your export, they will not be imported to Hammond. The fields
<em>'Second fuel'</em> and <em>'Third fuel'</em> are are are also ignored as the use case for these is not understood by us. If you have a use
case for this, please open a issue on
<a href="https://github.com/akhilrex/hammond/issues">issue tracker</a>
</p>
<div class="section box">
<div class="columns is-multiline">
<div class="column is-full"> <p class="subtitle">Choose the vehicle, then select the Drivvo CSV and press the import button.</p></div>
<div class="column is-full is-flex is-align-content-center">
<form @submit.prevent="importDrivvo">
<div class="columns">
<div class="column">
<b-field label="Vehicle" label-position="on-border">
<b-select v-model="selectedVehicle" placeholder="Select Vehicle" required>
<option v-for="vehicle in myVehicles" :key="vehicle.id" :value="vehicle.id">{{ vehicle.nickname }}</option>
</b-select>
</b-field>
</div>
<div class="column">
<b-field>
<b-tooltip label="Whether to import the location for fillups and services or not." multilined>
<b-checkbox v-model="importLocation">Import Location?</b-checkbox>
</b-tooltip>
</b-field>
</div>
<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" :disabled="tryingToCreate" type="is-primary" class="control">
Import
</b-button>
</div></div
>
</form>
</div>
</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

@@ -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) {
@@ -58,6 +58,7 @@ export default {
duration: 3000,
})
this.file = null
setTimeout(() => this.$router.push({ name: 'home' }), 1000)
})
.catch((ex) => {
this.$buefy.toast.open({
@@ -108,7 +109,7 @@ export default {
<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">
<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>
@@ -120,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,19 +18,41 @@ 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="box column is-one-third" to="/import-fuelly">
<h1 class="title">Fuelly</h1>
<p>{{ $t('importcsv', { 'name': 'Fuelly' }) }}</p>
<br />
<b-button type="is-primary" tag="router-link" to="/import/fuelly">{{ $t('import') }}</b-button>
<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>
<br />
<b-button type="is-primary" tag="router-link" to="/import/fuelly">{{ $t('import') }}</b-button>
</div>
</div>
<div class="column is-one-third" to="/import-fuelly">
<div class="box">
<h1 class="title">Drivvo</h1>
<p>{{ $t('importcsv', { 'name': 'Fuelly' }) }}</p>
<br />
<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>

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')">{{ $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

@@ -48,6 +48,12 @@ export default {
{ label: this.$t('alltime'), value: 'all_time' },
],
dateRangeOption: 'past_30_days',
mileageOptions: [
{ label: 'L/100km', value: 'litre_100km' },
{ label: 'km/L', value: 'km_litre' },
{ label: 'mpg', value: 'mpg' },
],
mileageOption: 'litre_100km',
}
},
computed: {
@@ -86,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),
}),
},
]
})
@@ -303,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
}
@@ -322,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"
@@ -333,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>
@@ -356,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">
@@ -406,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>
@@ -421,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">
@@ -461,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') }}
@@ -486,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"
>
@@ -510,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>
@@ -527,16 +540,29 @@ 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">
<b-select v-model="dateRangeOption" class="is-pulled-right is-medium">
<option v-for="option in dateRangeOptions" :key="option.value" :value="option.value">
{{ option.label }}
</option>
</b-select></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">
<b-select v-model="mileageOption">
<option v-for="option in mileageOptions" :key="option.value" :value="option.value">
{{ option.label }}
</option>
</b-select>
</div>
<div class="column">
<b-select v-model="dateRangeOption">
<option v-for="option in dateRangeOptions" :key="option.value" :value="option.value">
{{ option.label }}
</option>
</b-select>
</div>
</div>
</div>
</div>
<MileageChart :vehicle="vehicle" :since="getStartDate()" :user="me" :height="300" />
<MileageChart :vehicle="vehicle" :since="getStartDate()" :user="me" :height="300" :mileage-option="mileageOption" />
</div>
</Layout>
</template>

14736
ui/yarn.lock

File diff suppressed because it is too large Load Diff