Compare commits

..

84 Commits

Author SHA1 Message Date
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
29 changed files with 1377 additions and 206 deletions

View File

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

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 FROM golang:${GO_VERSION}-alpine AS builder
RUN apk update && apk add alpine-sdk git && rm -rf /var/cache/apk/* RUN apk update && apk add alpine-sdk git && rm -rf /var/cache/apk/*
RUN mkdir -p /api RUN mkdir -p /api
@@ -9,9 +9,11 @@ RUN go mod download
COPY ./server . COPY ./server .
RUN go build -o ./app ./main.go RUN go build -o ./app ./main.go
FROM node:14 as build-stage FROM node:16-alpine as build-stage
WORKDIR /app WORKDIR /app
COPY ./ui/package*.json ./ 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 RUN npm install
COPY ./ui . COPY ./ui .
RUN npm run build RUN npm run build
@@ -25,7 +27,7 @@ ENV UID=998
ENV PID=100 ENV PID=100
ENV GIN_MODE=release ENV GIN_MODE=release
VOLUME ["/config", "/assets"] 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; \ RUN mkdir -p /config; \
mkdir -p /assets; \ mkdir -p /assets; \
mkdir -p /api mkdir -p /api

View File

@@ -1,6 +1,5 @@
<p align="center"> <p align="center">
<h1 align="center" style="margin-bottom:0">Hammond</h1> <h1 align="center" style="margin-bottom:0">Hammond</h1>
<p align="center">Current Version - 2022.07.06</p>
<p align="center"> <p align="center">
A self-hosted vehicle expense tracking system with support for multiple users. A self-hosted vehicle expense tracking system with support for multiple users.

View File

@@ -4,6 +4,7 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"hammond/models"
"hammond/service" "hammond/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -12,6 +13,7 @@ import (
func RegisteImportController(router *gin.RouterGroup) { func RegisteImportController(router *gin.RouterGroup) {
router.POST("/import/fuelly", fuellyImport) router.POST("/import/fuelly", fuellyImport)
router.POST("/import/drivvo", drivvoImport) router.POST("/import/drivvo", drivvoImport)
router.POST("/import/generic", genericImport)
} }
func fuellyImport(c *gin.Context) { func fuellyImport(c *gin.Context) {
@@ -52,3 +54,21 @@ func drivvoImport(c *gin.Context) {
} }
c.JSON(http.StatusOK, gin.H{}) 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

@@ -19,7 +19,6 @@ func RegisterAnonMasterConroller(router *gin.RouterGroup) {
"distanceUnits": db.DistanceUnitDetails, "distanceUnits": db.DistanceUnitDetails,
"roles": db.RoleDetails, "roles": db.RoleDetails,
"currencies": models.GetCurrencyMasterList(), "currencies": models.GetCurrencyMasterList(),
"languages": models.GetLanguageMastersList(),
}) })
}) })
} }
@@ -53,7 +52,7 @@ func udpateSettings(c *gin.Context) {
func udpateMySettings(c *gin.Context) { func udpateMySettings(c *gin.Context) {
var model models.UpdateSettingModel var model models.UpdateSettingModel
if err := c.ShouldBind(&model); err == nil { if err := c.ShouldBind(&model); err == nil {
err := service.UpdateUserSettings(c.MustGet("userId").(string), model.Currency, *model.DistanceUnit, model.DateFormat, model.Language) err := service.UpdateUserSettings(c.MustGet("userId").(string), model.Currency, *model.DistanceUnit, model.DateFormat)
if err != nil { if err != nil {
c.JSON(http.StatusUnprocessableEntity, common.NewError("udpateMySettings", err)) c.JSON(http.StatusUnprocessableEntity, common.NewError("udpateMySettings", err))
return return

View File

@@ -19,7 +19,6 @@ type User struct {
Name string `json:"name"` Name string `json:"name"`
Vehicles []Vehicle `gorm:"many2many:user_vehicles;" json:"vehicles"` Vehicles []Vehicle `gorm:"many2many:user_vehicles;" json:"vehicles"`
IsDisabled bool `json:"isDisabled"` IsDisabled bool `json:"isDisabled"`
Language string `json:"language"`
} }
func (b *User) MarshalJSON() ([]byte, error) { func (b *User) MarshalJSON() ([]byte, error) {

View File

@@ -27,10 +27,6 @@ var migrations = []localMigration{
Name: "2022_03_08_13_16_AddVIN", Name: "2022_03_08_13_16_AddVIN",
Query: "ALTER TABLE vehicles ADD COLUMN vin text", Query: "ALTER TABLE vehicles ADD COLUMN vin text",
}, },
{
Name: "2023_02_26_13_42_AddLanguage",
Query: "ALTER TABLE users ADD COLUMN language text default 'en'",
},
} }
func RunMigrations() { func RunMigrations() {

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,24 +0,0 @@
package models
type LanguageModel struct {
Emoji string `json:"emoji"`
Name string `json:"name"`
NameNative string `json:"nameNative"`
Shorthand string `json:"shorthand"`
}
func GetLanguageMastersList() []LanguageModel {
return []LanguageModel{
{
Emoji: "🇬🇧",
Name: "English",
NameNative: "English",
Shorthand: "en",
}, {
Emoji: "🇩🇪",
Name: "German",
NameNative: "Deutsch",
Shorthand: "de",
},
}
}

View File

@@ -6,7 +6,6 @@ type UpdateSettingModel struct {
Currency string `json:"currency" form:"currency" query:"currency"` Currency string `json:"currency" form:"currency" query:"currency"`
DateFormat string `json:"dateFormat" form:"dateFormat" query:"dateFormat"` DateFormat string `json:"dateFormat" form:"dateFormat" query:"dateFormat"`
DistanceUnit *db.DistanceUnit `json:"distanceUnit" form:"distanceUnit" query:"distanceUnit" ` DistanceUnit *db.DistanceUnit `json:"distanceUnit" form:"distanceUnit" query:"distanceUnit" `
Language string `json:"language" form:"language" query:"language"`
} }
type ClarksonMigrationModel struct { type ClarksonMigrationModel struct {

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

@@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"hammond/db" "hammond/db"
"hammond/models"
) )
func WriteToDB(fillups []db.Fillup, expenses []db.Expense) []string { func WriteToDB(fillups []db.Fillup, expenses []db.Expense) []string {
@@ -105,3 +106,27 @@ func FuellyImport(content []byte, userId string) []string {
return WriteToDB(fillups, expenses) 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,9 +1,7 @@
package service package service
import ( import (
"errors"
"hammond/db" "hammond/db"
"hammond/models"
) )
func CanInitializeSystem() (bool, error) { func CanInitializeSystem() (bool, error) {
@@ -16,30 +14,15 @@ func UpdateSettings(currency string, distanceUnit db.DistanceUnit) error {
setting.DistanceUnit = distanceUnit setting.DistanceUnit = distanceUnit
return db.UpdateSettings(setting) return db.UpdateSettings(setting)
} }
func UpdateUserSettings(userId, currency string, distanceUnit db.DistanceUnit, dateFormat string, language string) error { func UpdateUserSettings(userId, currency string, distanceUnit db.DistanceUnit, dateFormat string) error {
user, err := db.GetUserById(userId) user, err := db.GetUserById(userId)
if err != nil { if err != nil {
return err return err
} }
// TODO: Pull into function
languageExists := false
languages := models.GetLanguageMastersList();
for _, lang := range languages {
if (language == lang.Shorthand){
languageExists = true
}
}
if (!languageExists) {
return errors.New("Language not in masters list")
}
user.Currency = currency user.Currency = currency
user.DistanceUnit = distanceUnit user.DistanceUnit = distanceUnit
user.DateFormat = dateFormat user.DateFormat = dateFormat
user.Language = language
return db.UpdateUser(user) return db.UpdateUser(user)
} }

View File

@@ -36,6 +36,7 @@
"node-gyp": "^9.3.1", "node-gyp": "^9.3.1",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"papaparse": "^5.4.1",
"vue": "^2.6.11", "vue": "^2.6.11",
"vue-chartjs": "^3.5.1", "vue-chartjs": "^3.5.1",
"vue-i18n": "^8.28.2", "vue-i18n": "^8.28.2",
@@ -62,8 +63,6 @@
"eslint-plugin-vue": "^9.9.0", "eslint-plugin-vue": "^9.9.0",
"express": "^4.18.2", "express": "^4.18.2",
"hygen": "^6.2.11", "hygen": "^6.2.11",
"imagemin-lint-staged": "^0.5.1",
"lint-staged": "^13.1.1",
"markdownlint-cli": "^0.33.0", "markdownlint-cli": "^0.33.0",
"npm-run-all": "^4.1.1", "npm-run-all": "^4.1.1",
"sass": "^1.58.0", "sass": "^1.58.0",

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

@@ -137,7 +137,7 @@
"dontimportagain": "Achte darauf, dass du die Datei nicht erneut importierst, da dies zu mehrfachen Einträgen führen würde.", "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.", "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.", "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.", "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.", "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", "importhintcreatecsv": "Exportiere deine Daten aus {name} im CSV-Format. Die Schritte dazu findest du",

View File

@@ -43,15 +43,15 @@
"createnow": "Create Now", "createnow": "Create Now",
"yourvehicles": "Your Vehicles", "yourvehicles": "Your Vehicles",
"menu": { "menu": {
"quickentries": "Quick Entries", "quickentries": "Quick Entries",
"logout": "Log out", "logout": "Log out",
"import": "Import", "import": "Import",
"home": "Home", "home": "Home",
"settings": "Settings", "settings": "Settings",
"admin": "Admin", "admin": "Admin",
"sitesettings": "Site Settings", "sitesettings": "Site Settings",
"users": "Users", "users": "Users",
"login": "Log in" "login": "Log in"
}, },
"enterusername": "Enter your username", "enterusername": "Enter your username",
"enterpassword": "Enter your password", "enterpassword": "Enter your password",
@@ -81,34 +81,34 @@
"quantity": "Quantity", "quantity": "Quantity",
"gasstation": "Gas Station", "gasstation": "Gas Station",
"fuel": { "fuel": {
"petrol": "Petrol", "petrol": "Petrol",
"diesel": "Diesel", "diesel": "Diesel",
"cng": "CNG", "cng": "CNG",
"lpg": "LPG", "lpg": "LPG",
"electric": "Electric", "electric": "Electric",
"ethanol": "Ethanol" "ethanol": "Ethanol"
}, },
"unit": { "unit": {
"long": { "long": {
"litre": "Litre", "litre": "Litre",
"gallon": "Gallon", "gallon": "Gallon",
"kilowatthour": "Kilowatt Hour", "kilowatthour": "Kilowatt Hour",
"kilogram": "Kilogram", "kilogram": "Kilogram",
"usgallon": "US Gallon", "usgallon": "US Gallon",
"minutes": "Minutes", "minutes": "Minutes",
"kilometers": "Kilometers", "kilometers": "Kilometers",
"miles": "Miles" "miles": "Miles"
}, },
"short": { "short": {
"litre": "Lt", "litre": "Lt",
"gallon": "Gal", "gallon": "Gal",
"kilowatthour": "KwH", "kilowatthour": "KwH",
"kilogram": "Kg", "kilogram": "Kg",
"usgallon": "US Gal", "usgallon": "US Gal",
"minutes": "Mins", "minutes": "Mins",
"kilometers": "Km", "kilometers": "Km",
"miles": "Mi" "miles": "Mi"
} }
}, },
"avgfillupqty": "Avg Fillup Qty", "avgfillupqty": "Avg Fillup Qty",
"avgfillupexpense": "Avg Fillup Expense", "avgfillupexpense": "Avg Fillup Expense",
@@ -117,7 +117,9 @@
"price": "Price", "price": "Price",
"total": "Total", "total": "Total",
"fulltank": "Tank Full", "fulltank": "Tank Full",
"partialfillup": "Partial Fillup",
"getafulltank": "Did you get a full tank?", "getafulltank": "Did you get a full tank?",
"tankpartialfull": "Which do you track?",
"by": "By", "by": "By",
"expenses": "Expenses", "expenses": "Expenses",
"expensetype": "Expense Type", "expensetype": "Expense Type",
@@ -130,6 +132,8 @@
"importdatadesc": "Choose from the following options to import data into Hammond", "importdatadesc": "Choose from the following options to import data into Hammond",
"import": "Import", "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.", "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", "choosecsv": "Choose CSV",
"choosephoto": "Choose Photo", "choosephoto": "Choose Photo",
"importsuccessfull": "Data Imported Successfully", "importsuccessfull": "Data Imported Successfully",
@@ -137,13 +141,15 @@
"importfrom": "Import from {0}", "importfrom": "Import from {0}",
"stepstoimport": "Steps to import data from {name}", "stepstoimport": "Steps to import data from {name}",
"choosecsvimport": "Choose the {name} CSV and press the import button.", "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.", "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.", "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.", "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.", "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.", "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", "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", "here": "here",
"unprocessedquickentries": "You have one quick entry to be processed. | You have {0} quick entries pending to be processed.", "unprocessedquickentries": "You have one quick entry to be processed. | You have {0} quick entries pending to be processed.",
"show": "Show", "show": "Show",
@@ -178,6 +184,7 @@
"fillingstation": "Filling Station Name", "fillingstation": "Filling Station Name",
"comments": "Comments", "comments": "Comments",
"missfillupbefore": "Did you miss the fillup entry before this one?", "missfillupbefore": "Did you miss the fillup entry before this one?",
"missedfillup": "Missed Fillup",
"fillupdate": "Fillup Date", "fillupdate": "Fillup Date",
"fillupsavedsuccessfully": "Fillup Saved Successfully", "fillupsavedsuccessfully": "Fillup Saved Successfully",
"expensesavedsuccessfully": "Expense Saved Successfully", "expensesavedsuccessfully": "Expense Saved Successfully",
@@ -195,25 +202,25 @@
"testconn": "Test Connection", "testconn": "Test Connection",
"migrate": "Migrate", "migrate": "Migrate",
"init": { "init": {
"migrateclarkson": "Migrate from Clarkson", "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.", "migrateclarksondesc": "If you have an existing Clarkson deployment and you want to migrate your data from that, press the following button.",
"freshinstall": "Fresh Install", "freshinstall": "Fresh Install",
"freshinstalldesc": "If you want a fresh install of Hammond, press the following button.", "freshinstalldesc": "If you want a fresh install of Hammond, press the following button.",
"clarkson": { "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/>", "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" "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": { "fresh": {
"setupadminuser": "Setup Admin Users", "setupadminuser": "Setup Admin Users",
"yourpassword": "Your Password", "yourpassword": "Your Password",
"youremail": "Your Email", "youremail": "Your Email",
"yourname": "Your Name", "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." "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": { "roles": {
"ADMIN": "ADMIN", "ADMIN": "ADMIN",
"USER": "USER" "USER": "USER"
}, },
"profile": "Profile", "profile": "Profile",
"processedon": "Processed on", "processedon": "Processed on",
@@ -221,4 +228,4 @@
"disable": "Disable", "disable": "Disable",
"confirm": "Go Ahead", "confirm": "Go Ahead",
"labelforfile": "Label for this file" "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/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, faCheck,
faTimes, faTimes,
faArrowUp, faArrowUp,
faArrowRotateLeft,
faAngleLeft, faAngleLeft,
faAngleRight, faAngleRight,
faCalendar, faCalendar,
@@ -38,6 +39,7 @@ library.add(
faCheck, faCheck,
faTimes, faTimes,
faArrowUp, faArrowUp,
faArrowRotateLeft,
faAngleLeft, faAngleLeft,
faAngleRight, faAngleRight,
faCalendar, faCalendar,

View File

@@ -419,6 +419,15 @@ export default [
}, },
props: (route) => ({ user: store.state.auth.currentUser || {} }), 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', path: '/logout',
name: 'logout', name: 'logout',

View File

@@ -76,6 +76,9 @@ export default {
this.fetchVehicleFuelSubTypes() this.fetchVehicleFuelSubTypes()
if (!this.fillup.id) { if (!this.fillup.id) {
this.fillupModel = this.getEmptyFillup() this.fillupModel = this.getEmptyFillup()
if (this.vehicle.fillups.length > 0) {
this.fillupModel.odoReading = this.vehicle.fillups[0].odoReading
}
this.fillupModel.userId = this.me.id this.fillupModel.userId = this.me.id
} }
}, },
@@ -277,7 +280,15 @@ export default {
</b-field> </b-field>
<br /> <br />
<b-field> <b-field>
<b-button tag="button" native-type="submit" :disabled="tryingToCreate" type="is-primary" :value="$t('save')" :label="$t('createfillup')" expanded/> <b-button
tag="button"
native-type="submit"
:disabled="tryingToCreate"
type="is-primary"
:value="$t('save')"
:label="$t('createfillup')"
expanded
/>
<p v-if="authError"> <p v-if="authError">
There was an error logging in to your account. There was an error logging in to your account.
</p> </p>

View File

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

View File

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

View File

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

View File

@@ -230,7 +230,7 @@ export default {
</b-field> </b-field>
<br /> <br />
<div class="buttons"> <div class="buttons">
<b-button type="is-primary" native-type="submit" tag="button" :value="$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> <b-button type="is-danger is-light" @click="resetMigrationMode">{{ $t('cancel') }}</b-button>
</div> </div>

View File

@@ -22,13 +22,11 @@ export default {
data: function() { data: function() {
return { return {
settingsModel: { settingsModel: {
language: this.me.language,
currency: this.me.currency, currency: this.me.currency,
distanceUnit: this.me.distanceUnit, distanceUnit: this.me.distanceUnit,
dateFormat: this.me.dateFormat, dateFormat: this.me.dateFormat,
}, },
tryingToSave: false, tryingToSave: false,
selectedLanguage: "",
changePassModel: { changePassModel: {
old: '', old: '',
new: '', new: '',
@@ -38,7 +36,7 @@ export default {
} }
}, },
computed: { computed: {
...mapState('masters', ['currencyMasters', 'languageMasters', 'distanceUnitMasters']), ...mapState('vehicles', ['currencyMasters', 'distanceUnitMasters']),
passwordValid() { passwordValid() {
if (this.changePassModel.new === '' || this.changePassModel.renew === '') { if (this.changePassModel.new === '' || this.changePassModel.renew === '') {
return true return true
@@ -61,9 +59,6 @@ export default {
}) })
}, },
}, },
mounted() {
this.selectedLanguage = this.formatLanguage(this.languageMasters.filter(x => x.shorthand === this.me.language)[0])
},
methods: { methods: {
changePassword() { changePassword() {
if (!this.passwordValid) { if (!this.passwordValid) {
@@ -115,7 +110,6 @@ export default {
type: 'is-success', type: 'is-success',
duration: 3000, duration: 3000,
}) })
this.$i18n.locale = this.settingsModel.language
}) })
.catch((ex) => { .catch((ex) => {
this.$buefy.toast.open({ this.$buefy.toast.open({
@@ -132,9 +126,6 @@ export default {
formatCurrency(option) { formatCurrency(option) {
return `${option.namePlural} (${option.code})` return `${option.namePlural} (${option.code})`
}, },
formatLanguage(option) {
return `${option.nameNative} ${option.emoji}`
},
}, },
} }
</script> </script>
@@ -145,18 +136,9 @@ export default {
<div class="columns" <div class="columns"
><div class="column"> ><div class="column">
<form class="box " @submit.prevent="saveSettings"> <form class="box " @submit.prevent="saveSettings">
<b-field :label="$t('language')"> <h1 class="subtitle">
<b-autocomplete {{ $t('settingdesc') }}
v-model="selectedLanguage" </h1>
:placeholder="$t('language')"
:keep-first="true"
:custom-formatter="formatLanguage"
:data="languageMasters"
:open-on-focus="true"
required
@select="(option) => (settingsModel.language = option.shorthand)"
/>
</b-field>
<b-field :label="$t('currency')"> <b-field :label="$t('currency')">
<b-autocomplete <b-autocomplete
v-model="settingsModel.currency" v-model="settingsModel.currency"

View File

@@ -1,52 +0,0 @@
import axios from 'axios'
export const state = {
languageMasters: [],
fuelUnitMasters: [],
distanceUnitMasters: [],
currencyMasters: [],
fuelTypeMasters: [],
roleMasters: [],
}
export const mutations = {
CACHE_LANGUAGE_MASTERS(state, masters) {
state.languageMasters = masters
},
CACHE_FUEL_UNIT_MASTERS(state, masters) {
state.fuelUnitMasters = masters
},
CACHE_DISTANCE_UNIT_MASTERS(state, masters) {
state.distanceUnitMasters = masters
},
CACHE_FUEL_TYPE_MASTERS(state, masters) {
state.fuelTypeMasters = masters
},
CACHE_CURRENCY_MASTERS(state, masters) {
state.currencyMasters = masters
},
CACHE_ROLE_MASTERS(state, roles) {
state.roleMasters = roles
},
}
export const getters = {}
export const actions = {
init({ dispatch, rootState }) {
const { currentUser } = rootState.auth
if (currentUser) {
dispatch('fetchMasters')
}
},
fetchMasters({ commit, state, rootState }) {
return axios.get('/api/masters').then((response) => {
commit('CACHE_LANGUAGE_MASTERS', response.data.languages)
commit('CACHE_FUEL_UNIT_MASTERS', response.data.fuelUnits)
commit('CACHE_FUEL_TYPE_MASTERS', response.data.fuelTypes)
commit('CACHE_CURRENCY_MASTERS', response.data.currencies)
commit('CACHE_DISTANCE_UNIT_MASTERS', response.data.distanceUnits)
commit('CACHE_ROLE_MASTERS', response.data.roles)
return response.data
})
},
}

View File

@@ -4,6 +4,11 @@ import { filter } from 'lodash'
import parseISO from 'date-fns/parseISO' import parseISO from 'date-fns/parseISO'
export const state = { export const state = {
vehicles: [], vehicles: [],
roleMasters: [],
fuelUnitMasters: [],
distanceUnitMasters: [],
currencyMasters: [],
fuelTypeMasters: [],
quickEntries: [], quickEntries: [],
vehicleStats: new Map(), vehicleStats: new Map(),
} }
@@ -24,9 +29,24 @@ export const mutations = {
CACHE_VEHICLE_STATS(state, stats) { CACHE_VEHICLE_STATS(state, stats) {
state.vehicleStats.set(stats.vehicleId, stats) state.vehicleStats.set(stats.vehicleId, stats)
}, },
CACHE_FUEL_UNIT_MASTERS(state, masters) {
state.fuelUnitMasters = masters
},
CACHE_DISTANCE_UNIT_MASTERS(state, masters) {
state.distanceUnitMasters = masters
},
CACHE_FUEL_TYPE_MASTERS(state, masters) {
state.fuelTypeMasters = masters
},
CACHE_CURRENCY_MASTERS(state, masters) {
state.currencyMasters = masters
},
CACHE_QUICK_ENTRIES(state, entries) { CACHE_QUICK_ENTRIES(state, entries) {
state.quickEntries = entries state.quickEntries = entries
}, },
CACHE_ROLE_MASTERS(state, roles) {
state.roleMasters = roles
},
} }
export const actions = { export const actions = {
@@ -34,9 +54,22 @@ export const actions = {
const { currentUser } = rootState.auth const { currentUser } = rootState.auth
if (currentUser) { if (currentUser) {
dispatch('fetchVehicles') dispatch('fetchVehicles')
dispatch('fetchMasters')
dispatch('fetchQuickEntries', { force: true }) dispatch('fetchQuickEntries', { force: true })
} }
}, },
fetchMasters({ commit, state, rootState }) {
return axios.get('/api/masters').then((response) => {
const fuelUnitMasters = response.data.fuelUnits
const fuelTypeMasters = response.data.fuelTypes
commit('CACHE_FUEL_UNIT_MASTERS', fuelUnitMasters)
commit('CACHE_FUEL_TYPE_MASTERS', fuelTypeMasters)
commit('CACHE_CURRENCY_MASTERS', response.data.currencies)
commit('CACHE_DISTANCE_UNIT_MASTERS', response.data.distanceUnits)
commit('CACHE_ROLE_MASTERS', response.data.roles)
return response.data
})
},
fetchVehicles({ commit, state, rootState }) { fetchVehicles({ commit, state, rootState }) {
return axios.get('/api/me/vehicles').then((response) => { return axios.get('/api/me/vehicles').then((response) => {
const data = response.data const data = response.data