mirror of
https://github.com/grocy/grocy.git
synced 2025-09-20 03:17:52 +00:00
Compare commits
67 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
756ec319cc | ||
|
ba2d32be60 | ||
|
7cc09cec67 | ||
|
8b815fce93 | ||
|
f1c78659be | ||
|
5c79a80f7a | ||
|
f451e65278 | ||
|
176333df5b | ||
|
d4227d2e41 | ||
|
0bbd2d9880 | ||
|
b81316bd60 | ||
|
d11dcb38fe | ||
|
77d82f22dc | ||
|
be326a5211 | ||
|
83624eaf27 | ||
|
055619d275 | ||
|
cda3dde120 | ||
|
5a0b862d22 | ||
|
bb5fd8360b | ||
|
d7180bd7b2 | ||
|
8c9b0dedb2 | ||
|
9c2c2c1fa2 | ||
|
596dc9e36d | ||
|
b2019ba42d | ||
|
003d4a567a | ||
|
5112e0f551 | ||
|
8008fcdc65 | ||
|
8d41dcc650 | ||
|
037d024862 | ||
|
03ca5cd45b | ||
|
60d47bef84 | ||
|
98a7bcb044 | ||
|
7401971884 | ||
|
067a10e1b2 | ||
|
ddfe33fab6 | ||
|
2a0ec30bb0 | ||
|
8540fc44f3 | ||
|
66095738e3 | ||
|
e472711d23 | ||
|
8e054a4981 | ||
|
feb28211d8 | ||
|
06f25b7006 | ||
|
f85a67a1ff | ||
|
6fe0100927 | ||
|
bcb359e317 | ||
|
4075067a10 | ||
|
bd3c63218b | ||
|
27daf384da | ||
|
905fc0f357 | ||
|
9cd0e4ab2d | ||
|
6b38cd450f | ||
|
bb60f5f043 | ||
|
e777be4d3b | ||
|
8a71d55f0f | ||
|
b01b49d10c | ||
|
496594d898 | ||
|
1d5e82c341 | ||
|
a9b696f41c | ||
|
e50b1eb359 | ||
|
92e0245387 | ||
|
67d0d3c3d6 | ||
|
23bcbc23e9 | ||
|
085d9a0bc7 | ||
|
368df142cf | ||
|
d38edabb14 | ||
|
4426a10e2e | ||
|
931dc9d243 |
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
.git
|
||||||
|
.vscode
|
||||||
|
.gitignore
|
||||||
|
build.bat
|
||||||
|
Dockerfile
|
||||||
|
.DS_store
|
58
Dockerfile-grocy
Normal file
58
Dockerfile-grocy
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
FROM php:7.2-fpm-alpine
|
||||||
|
MAINTAINER Talmai Oliveira <to@talm.ai>
|
||||||
|
|
||||||
|
RUN apk update && \
|
||||||
|
apk upgrade && \
|
||||||
|
apk add --update yarn git &&\
|
||||||
|
mkdir -p /www && \
|
||||||
|
# Set environments
|
||||||
|
sed -i "s|;*daemonize\s*=\s*yes|daemonize = no|g" /usr/local/etc/php-fpm.conf && \
|
||||||
|
sed -i "s|;*listen\s*=\s*127.0.0.1:9000|listen = 9000|g" /usr/local/etc/php-fpm.conf && \
|
||||||
|
sed -i "s|;*listen\s*=\s*/||g" /usr/local/etc/php-fpm.conf && \
|
||||||
|
# sed -i "s|;*log_level\s*=\s*notice|log_level = debug|g" /usr/local/etc/php-fpm.conf && \
|
||||||
|
sed -i "s|;*chdir\s*=\s*/var/www|chdir = /www|g" /usr/local/etc/php-fpm.d/www.conf && \
|
||||||
|
# sed -i "s|;*access.log\s*=\s*log/\$pool.access.log|access.log = \$pool.access.log|g" /usr/local/etc/php-fpm.d/www.conf && \
|
||||||
|
# sed -i "s|;*pm.status_path\s*=\s*/status|pm.status_path = /status|g" /usr/local/etc/php-fpm.d/www.conf && \
|
||||||
|
# sed -i "s|;*memory_limit =.*|memory_limit = ${PHP_MEMORY_LIMIT}|i" /usr/local/etc/php.ini && \
|
||||||
|
# sed -i "s|;*upload_max_filesize =.*|upload_max_filesize = ${MAX_UPLOAD}|i" /usr/local/etc/php.ini && \
|
||||||
|
# sed -i "s|;*max_file_uploads =.*|max_file_uploads = ${PHP_MAX_FILE_UPLOAD}|i" /usr/local/etc/php.ini && \
|
||||||
|
# sed -i "s|;*post_max_size =.*|post_max_size = ${PHP_MAX_POST}|i" /usr/local/etc/php.ini && \
|
||||||
|
# sed -i "s|;*cgi.fix_pathinfo=.*|cgi.fix_pathinfo= 0|i" /usr/local/etc/php.ini && \
|
||||||
|
wget https://raw.githubusercontent.com/composer/getcomposer.org/1b137f8bf6db3e79a38a5bc45324414a6b1f9df2/web/installer -O - -q | php -- --quiet && \
|
||||||
|
# Cleaning up
|
||||||
|
rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
|
COPY public /www/public
|
||||||
|
COPY info.php /www/public
|
||||||
|
COPY controllers /www/controllers
|
||||||
|
COPY data /www/data
|
||||||
|
COPY helpers /www/helpers
|
||||||
|
COPY localization/ /www/localization
|
||||||
|
COPY middleware/ /www/middleware
|
||||||
|
COPY migrations/ /www/migrations
|
||||||
|
COPY publication_assets/ /www/publication_assets
|
||||||
|
COPY services/ /www/services
|
||||||
|
COPY views/ /www/views
|
||||||
|
COPY .yarnrc /www/
|
||||||
|
COPY *.php /www/
|
||||||
|
COPY *.json /www/
|
||||||
|
COPY composer.* /root/.composer/
|
||||||
|
COPY *yarn* /www/
|
||||||
|
COPY *.sh /www/
|
||||||
|
|
||||||
|
# run php composer.phar with -vvv for extra debug information
|
||||||
|
RUN cd /var/www/html && \
|
||||||
|
php composer.phar --working-dir=/www/ -n install && \
|
||||||
|
cp /www/config-dist.php /www/data/config.php && \
|
||||||
|
cd /www && \
|
||||||
|
yarn install && \
|
||||||
|
chown www-data:www-data -R /www/
|
||||||
|
|
||||||
|
# Set Workdir
|
||||||
|
WORKDIR /www/public
|
||||||
|
|
||||||
|
# Expose volumes
|
||||||
|
VOLUME ["/www"]
|
||||||
|
|
||||||
|
# Expose ports
|
||||||
|
EXPOSE 9000
|
32
Dockerfile-grocy-nginx
Normal file
32
Dockerfile-grocy-nginx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
FROM alpine:latest
|
||||||
|
MAINTAINER Talmai Oliveira <to@talm.ai>
|
||||||
|
|
||||||
|
RUN apk update && \
|
||||||
|
apk upgrade && \
|
||||||
|
apk add --update openssl nginx && \
|
||||||
|
mkdir -p /etc/nginx/certificates && \
|
||||||
|
mkdir -p /var/run/nginx && \
|
||||||
|
mkdir -p /usr/share/nginx/html && \
|
||||||
|
openssl req \
|
||||||
|
-x509 \
|
||||||
|
-newkey rsa:2048 \
|
||||||
|
-keyout /etc/nginx/certificates/key.pem \
|
||||||
|
-out /etc/nginx/certificates/cert.pem \
|
||||||
|
-days 365 \
|
||||||
|
-nodes \
|
||||||
|
-subj /CN=localhost && \
|
||||||
|
rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
|
COPY docker_nginx/nginx.conf /etc/nginx/nginx.conf
|
||||||
|
COPY docker_nginx/common.conf /etc/nginx/common.conf
|
||||||
|
COPY docker_nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf
|
||||||
|
COPY docker_nginx/conf.d/ssl.conf /etc/nginx/conf.d/ssl.conf
|
||||||
|
|
||||||
|
# Expose volumes
|
||||||
|
VOLUME ["/etc/nginx/conf.d", "/var/log/nginx"]
|
||||||
|
|
||||||
|
# Expose ports
|
||||||
|
EXPOSE 80 443
|
||||||
|
|
||||||
|
# Entry point
|
||||||
|
ENTRYPOINT ["/usr/sbin/nginx", "-g", "daemon off;"]
|
15
README.md
15
README.md
@@ -2,10 +2,11 @@
|
|||||||
ERP beyond your fridge
|
ERP beyond your fridge
|
||||||
|
|
||||||
## Give it a try
|
## Give it a try
|
||||||
Public demo of the latest version → [https://demo.grocy.info](https://demo.grocy.info)
|
- Public demo of the latest stable version → [https://demo.grocy.info](https://demo.grocy.info)
|
||||||
|
- Public demo of the latest pre-release version (current master branch) → [https://demo-prerelease.grocy.info](https://demo-prerelease.grocy.info)
|
||||||
|
|
||||||
## Motivation
|
## Motivation
|
||||||
A household needs to be managed. I did this so far (almost 10 years) with my first self written software (a C# windows forms application) and with a bunch of Excel sheets. The software is a pain to use and Excel is Excel. So I searched for and tried different things for a (very) long time, nothing 100 % fitted, so this is my aim for a "complete houshold management"-thing. ERP your fridge!
|
A household needs to be managed. I did this so far (almost 10 years) with my first self written software (a C# windows forms application) and with a bunch of Excel sheets. The software is a pain to use and Excel is Excel. So I searched for and tried different things for a (very) long time, nothing 100 % fitted, so this is my aim for a "complete household management"-thing. ERP your fridge!
|
||||||
|
|
||||||
## How to install
|
## How to install
|
||||||
> **NEW**
|
> **NEW**
|
||||||
@@ -22,6 +23,16 @@ If you use nginx as your webserver, please include `try_files $uri /index.php;`
|
|||||||
|
|
||||||
If, however, your webserver does not support URL rewriting, set `DISABLE_URL_REWRITING` in `data/config.php` (`Setting('DISABLE_URL_REWRITING', true);`).
|
If, however, your webserver does not support URL rewriting, set `DISABLE_URL_REWRITING` in `data/config.php` (`Setting('DISABLE_URL_REWRITING', true);`).
|
||||||
|
|
||||||
|
## How to run using Docker
|
||||||
|
|
||||||
|
The docker images build are based on [Alpine](https://hub.docker.com/_/alpine/), with an extremelly low footprint (less than 10 MB for nginx, and less than 70MB for grocy with php-fm. That number is eventually bumped up to 353MB after all the dependencies are downloaded, however). Anyhow, to run using docker just do the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
> docker-compose up
|
||||||
|
```
|
||||||
|
|
||||||
|
And grocy should be accessible via `http(s)://localhost/`. The https option will work. However, since the certificate is self-signed, most browsers will complain.
|
||||||
|
|
||||||
## How to update
|
## How to update
|
||||||
Just overwrite everything with the latest release while keeping the `data` directory, check `config-dist.php` for new configuration options and add them to your `data/config.php` (the default from values `config-dist.php` will be used for not in `data/config.php` defined settings). Just to be sure, please empty `data/viewcache`.
|
Just overwrite everything with the latest release while keeping the `data` directory, check `config-dist.php` for new configuration options and add them to your `data/config.php` (the default from values `config-dist.php` will be used for not in `data/config.php` defined settings). Just to be sure, please empty `data/viewcache`.
|
||||||
|
|
||||||
|
@@ -10,4 +10,4 @@ del "%releasePath%\grocy_%version%.zip"
|
|||||||
"build_tools\7za.exe" a -r "%releasePath%\grocy_%version%.zip" "%projectPath%\*" -xr!.* -xr!build_tools -xr!build.bat -xr!composer.json -xr!composer.lock -xr!package.json -xr!yarn.lock -xr!publication_assets
|
"build_tools\7za.exe" a -r "%releasePath%\grocy_%version%.zip" "%projectPath%\*" -xr!.* -xr!build_tools -xr!build.bat -xr!composer.json -xr!composer.lock -xr!package.json -xr!yarn.lock -xr!publication_assets
|
||||||
"build_tools\7za.exe" a "%releasePath%\grocy_%version%.zip" "%projectPath%\public\.htaccess"
|
"build_tools\7za.exe" a "%releasePath%\grocy_%version%.zip" "%projectPath%\public\.htaccess"
|
||||||
"build_tools\7za.exe" rn "%releasePath%\grocy_%version%.zip" .htaccess public\.htaccess
|
"build_tools\7za.exe" rn "%releasePath%\grocy_%version%.zip" .htaccess public\.htaccess
|
||||||
"build_tools\7za.exe" d "%releasePath%\grocy_%version%.zip" data\*.* data\sessions data\viewcache\*
|
"build_tools\7za.exe" d "%releasePath%\grocy_%version%.zip" data\*.* data\storage data\viewcache\*
|
||||||
|
177
composer.lock
generated
177
composer.lock
generated
@@ -159,27 +159,27 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "illuminate/container",
|
"name": "illuminate/container",
|
||||||
"version": "v5.6.33",
|
"version": "v5.7.6",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/illuminate/container.git",
|
"url": "https://github.com/illuminate/container.git",
|
||||||
"reference": "1f0757cae8749400aeda730f6438a081fc3c082d"
|
"reference": "0fc33b14ae6cf9a1e694fd43f2a274e590a824b2"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/illuminate/container/zipball/1f0757cae8749400aeda730f6438a081fc3c082d",
|
"url": "https://api.github.com/repos/illuminate/container/zipball/0fc33b14ae6cf9a1e694fd43f2a274e590a824b2",
|
||||||
"reference": "1f0757cae8749400aeda730f6438a081fc3c082d",
|
"reference": "0fc33b14ae6cf9a1e694fd43f2a274e590a824b2",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"illuminate/contracts": "5.6.*",
|
"illuminate/contracts": "5.7.*",
|
||||||
"php": "^7.1.3",
|
"php": "^7.1.3",
|
||||||
"psr/container": "~1.0"
|
"psr/container": "^1.0"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "5.6-dev"
|
"dev-master": "5.7-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -199,31 +199,31 @@
|
|||||||
],
|
],
|
||||||
"description": "The Illuminate Container package.",
|
"description": "The Illuminate Container package.",
|
||||||
"homepage": "https://laravel.com",
|
"homepage": "https://laravel.com",
|
||||||
"time": "2018-05-24T13:16:56+00:00"
|
"time": "2018-05-28T08:50:10+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "illuminate/contracts",
|
"name": "illuminate/contracts",
|
||||||
"version": "v5.6.33",
|
"version": "v5.7.6",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/illuminate/contracts.git",
|
"url": "https://github.com/illuminate/contracts.git",
|
||||||
"reference": "2c029101285f6066f45e3ae5910b1b5f900fdcb4"
|
"reference": "2daf3c078610f744e2a4dc2f44fb5060cce9835b"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/illuminate/contracts/zipball/2c029101285f6066f45e3ae5910b1b5f900fdcb4",
|
"url": "https://api.github.com/repos/illuminate/contracts/zipball/2daf3c078610f744e2a4dc2f44fb5060cce9835b",
|
||||||
"reference": "2c029101285f6066f45e3ae5910b1b5f900fdcb4",
|
"reference": "2daf3c078610f744e2a4dc2f44fb5060cce9835b",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.1.3",
|
"php": "^7.1.3",
|
||||||
"psr/container": "~1.0",
|
"psr/container": "^1.0",
|
||||||
"psr/simple-cache": "~1.0"
|
"psr/simple-cache": "^1.0"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "5.6-dev"
|
"dev-master": "5.7-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -243,32 +243,32 @@
|
|||||||
],
|
],
|
||||||
"description": "The Illuminate Contracts package.",
|
"description": "The Illuminate Contracts package.",
|
||||||
"homepage": "https://laravel.com",
|
"homepage": "https://laravel.com",
|
||||||
"time": "2018-07-31T12:49:53+00:00"
|
"time": "2018-09-18T12:50:05+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "illuminate/events",
|
"name": "illuminate/events",
|
||||||
"version": "v5.6.33",
|
"version": "v5.7.6",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/illuminate/events.git",
|
"url": "https://github.com/illuminate/events.git",
|
||||||
"reference": "5bdd8e84c0528970961289da088306c632eca8f7"
|
"reference": "4cf622acc05592f86d4a5c77ad1a544d38e58dee"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/illuminate/events/zipball/5bdd8e84c0528970961289da088306c632eca8f7",
|
"url": "https://api.github.com/repos/illuminate/events/zipball/4cf622acc05592f86d4a5c77ad1a544d38e58dee",
|
||||||
"reference": "5bdd8e84c0528970961289da088306c632eca8f7",
|
"reference": "4cf622acc05592f86d4a5c77ad1a544d38e58dee",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"illuminate/container": "5.6.*",
|
"illuminate/container": "5.7.*",
|
||||||
"illuminate/contracts": "5.6.*",
|
"illuminate/contracts": "5.7.*",
|
||||||
"illuminate/support": "5.6.*",
|
"illuminate/support": "5.7.*",
|
||||||
"php": "^7.1.3"
|
"php": "^7.1.3"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "5.6-dev"
|
"dev-master": "5.7-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -288,39 +288,39 @@
|
|||||||
],
|
],
|
||||||
"description": "The Illuminate Events package.",
|
"description": "The Illuminate Events package.",
|
||||||
"homepage": "https://laravel.com",
|
"homepage": "https://laravel.com",
|
||||||
"time": "2018-07-23T01:01:28+00:00"
|
"time": "2018-07-26T15:27:42+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "illuminate/filesystem",
|
"name": "illuminate/filesystem",
|
||||||
"version": "v5.6.33",
|
"version": "v5.7.6",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/illuminate/filesystem.git",
|
"url": "https://github.com/illuminate/filesystem.git",
|
||||||
"reference": "97e2f19e2c2ec74779431acedfb746d9054da33a"
|
"reference": "a09fae4470494dc9867609221b46fe844f2f3b70"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/illuminate/filesystem/zipball/97e2f19e2c2ec74779431acedfb746d9054da33a",
|
"url": "https://api.github.com/repos/illuminate/filesystem/zipball/a09fae4470494dc9867609221b46fe844f2f3b70",
|
||||||
"reference": "97e2f19e2c2ec74779431acedfb746d9054da33a",
|
"reference": "a09fae4470494dc9867609221b46fe844f2f3b70",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"illuminate/contracts": "5.6.*",
|
"illuminate/contracts": "5.7.*",
|
||||||
"illuminate/support": "5.6.*",
|
"illuminate/support": "5.7.*",
|
||||||
"php": "^7.1.3",
|
"php": "^7.1.3",
|
||||||
"symfony/finder": "~4.0"
|
"symfony/finder": "^4.1"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"league/flysystem": "Required to use the Flysystem local and FTP drivers (~1.0).",
|
"league/flysystem": "Required to use the Flysystem local and FTP drivers (^1.0).",
|
||||||
"league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (~1.0).",
|
"league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).",
|
||||||
"league/flysystem-cached-adapter": "Required to use the Flysystem cache (~1.0).",
|
"league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).",
|
||||||
"league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (~1.0).",
|
"league/flysystem-rackspace": "Required to use the Flysystem Rackspace driver (^1.0).",
|
||||||
"league/flysystem-sftp": "Required to use the Flysystem SFTP driver (~1.0)."
|
"league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0)."
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "5.6-dev"
|
"dev-master": "5.7-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -340,42 +340,43 @@
|
|||||||
],
|
],
|
||||||
"description": "The Illuminate Filesystem package.",
|
"description": "The Illuminate Filesystem package.",
|
||||||
"homepage": "https://laravel.com",
|
"homepage": "https://laravel.com",
|
||||||
"time": "2018-07-29T15:22:18+00:00"
|
"time": "2018-08-14T19:42:44+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "illuminate/support",
|
"name": "illuminate/support",
|
||||||
"version": "v5.6.33",
|
"version": "v5.7.6",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/illuminate/support.git",
|
"url": "https://github.com/illuminate/support.git",
|
||||||
"reference": "0561e4e48797fbaeafeec0054b14605b08722a5a"
|
"reference": "f7c68e8c8aab200cc8ad84f974d5511cda58a742"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/illuminate/support/zipball/0561e4e48797fbaeafeec0054b14605b08722a5a",
|
"url": "https://api.github.com/repos/illuminate/support/zipball/f7c68e8c8aab200cc8ad84f974d5511cda58a742",
|
||||||
"reference": "0561e4e48797fbaeafeec0054b14605b08722a5a",
|
"reference": "f7c68e8c8aab200cc8ad84f974d5511cda58a742",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"doctrine/inflector": "~1.1",
|
"doctrine/inflector": "^1.1",
|
||||||
"ext-mbstring": "*",
|
"ext-mbstring": "*",
|
||||||
"illuminate/contracts": "5.6.*",
|
"illuminate/contracts": "5.7.*",
|
||||||
"nesbot/carbon": "^1.24.1",
|
"nesbot/carbon": "^1.26.3",
|
||||||
"php": "^7.1.3"
|
"php": "^7.1.3"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"tightenco/collect": "<5.5.33"
|
"tightenco/collect": "<5.5.33"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"illuminate/filesystem": "Required to use the composer class (5.6.*).",
|
"illuminate/filesystem": "Required to use the composer class (5.7.*).",
|
||||||
|
"moontoast/math": "Required to use ordered UUIDs (^1.1).",
|
||||||
"ramsey/uuid": "Required to use Str::uuid() (^3.7).",
|
"ramsey/uuid": "Required to use Str::uuid() (^3.7).",
|
||||||
"symfony/process": "Required to use the composer class (~4.0).",
|
"symfony/process": "Required to use the composer class (^4.1).",
|
||||||
"symfony/var-dumper": "Required to use the dd function (~4.0)."
|
"symfony/var-dumper": "Required to use the dd function (^4.1)."
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "5.6-dev"
|
"dev-master": "5.7-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -398,35 +399,35 @@
|
|||||||
],
|
],
|
||||||
"description": "The Illuminate Support package.",
|
"description": "The Illuminate Support package.",
|
||||||
"homepage": "https://laravel.com",
|
"homepage": "https://laravel.com",
|
||||||
"time": "2018-08-06T19:58:11+00:00"
|
"time": "2018-09-19T18:36:57+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "illuminate/view",
|
"name": "illuminate/view",
|
||||||
"version": "v5.6.33",
|
"version": "v5.7.6",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/illuminate/view.git",
|
"url": "https://github.com/illuminate/view.git",
|
||||||
"reference": "8d4e1c4d8c133eaca33c94ee35b7c0d2ef1dc66f"
|
"reference": "3ccd29550afe61eb02ad9e4bae0c2e661aadd7af"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/illuminate/view/zipball/8d4e1c4d8c133eaca33c94ee35b7c0d2ef1dc66f",
|
"url": "https://api.github.com/repos/illuminate/view/zipball/3ccd29550afe61eb02ad9e4bae0c2e661aadd7af",
|
||||||
"reference": "8d4e1c4d8c133eaca33c94ee35b7c0d2ef1dc66f",
|
"reference": "3ccd29550afe61eb02ad9e4bae0c2e661aadd7af",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"illuminate/container": "5.6.*",
|
"illuminate/container": "5.7.*",
|
||||||
"illuminate/contracts": "5.6.*",
|
"illuminate/contracts": "5.7.*",
|
||||||
"illuminate/events": "5.6.*",
|
"illuminate/events": "5.7.*",
|
||||||
"illuminate/filesystem": "5.6.*",
|
"illuminate/filesystem": "5.7.*",
|
||||||
"illuminate/support": "5.6.*",
|
"illuminate/support": "5.7.*",
|
||||||
"php": "^7.1.3",
|
"php": "^7.1.3",
|
||||||
"symfony/debug": "~4.0"
|
"symfony/debug": "^4.1"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-master": "5.6-dev"
|
"dev-master": "5.7-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -446,7 +447,7 @@
|
|||||||
],
|
],
|
||||||
"description": "The Illuminate View package.",
|
"description": "The Illuminate View package.",
|
||||||
"homepage": "https://laravel.com",
|
"homepage": "https://laravel.com",
|
||||||
"time": "2018-07-19T23:06:53+00:00"
|
"time": "2018-09-18T12:50:05+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "morris/lessql",
|
"name": "morris/lessql",
|
||||||
@@ -553,16 +554,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nesbot/carbon",
|
"name": "nesbot/carbon",
|
||||||
"version": "1.33.0",
|
"version": "1.34.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/briannesbitt/Carbon.git",
|
"url": "https://github.com/briannesbitt/Carbon.git",
|
||||||
"reference": "55667c1007a99e82030874b1bb14d24d07108413"
|
"reference": "1dbd3cb01c5645f3e7deda7aa46ef780d95fcc33"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/55667c1007a99e82030874b1bb14d24d07108413",
|
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/1dbd3cb01c5645f3e7deda7aa46ef780d95fcc33",
|
||||||
"reference": "55667c1007a99e82030874b1bb14d24d07108413",
|
"reference": "1dbd3cb01c5645f3e7deda7aa46ef780d95fcc33",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -604,7 +605,7 @@
|
|||||||
"datetime",
|
"datetime",
|
||||||
"time"
|
"time"
|
||||||
],
|
],
|
||||||
"time": "2018-08-07T08:39:47+00:00"
|
"time": "2018-09-20T19:36:25+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nikic/fast-route",
|
"name": "nikic/fast-route",
|
||||||
@@ -1095,16 +1096,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "slim/slim",
|
"name": "slim/slim",
|
||||||
"version": "3.10.0",
|
"version": "3.11.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/slimphp/Slim.git",
|
"url": "https://github.com/slimphp/Slim.git",
|
||||||
"reference": "d8aabeacc3688b25e2f2dd2db91df91ec6fdd748"
|
"reference": "d378e70431e78ee92ee32ddde61ecc72edf5dc0a"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/slimphp/Slim/zipball/d8aabeacc3688b25e2f2dd2db91df91ec6fdd748",
|
"url": "https://api.github.com/repos/slimphp/Slim/zipball/d378e70431e78ee92ee32ddde61ecc72edf5dc0a",
|
||||||
"reference": "d8aabeacc3688b25e2f2dd2db91df91ec6fdd748",
|
"reference": "d378e70431e78ee92ee32ddde61ecc72edf5dc0a",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1162,20 +1163,20 @@
|
|||||||
"micro",
|
"micro",
|
||||||
"router"
|
"router"
|
||||||
],
|
],
|
||||||
"time": "2018-04-19T19:29:08+00:00"
|
"time": "2018-09-16T10:54:21+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/debug",
|
"name": "symfony/debug",
|
||||||
"version": "v4.1.3",
|
"version": "v4.1.5",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/debug.git",
|
"url": "https://github.com/symfony/debug.git",
|
||||||
"reference": "9316545571f079c4dd183e674721d9dc783ce196"
|
"reference": "b4a0b67dee59e2cae4449a8f8eabc508d622fd33"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/debug/zipball/9316545571f079c4dd183e674721d9dc783ce196",
|
"url": "https://api.github.com/repos/symfony/debug/zipball/b4a0b67dee59e2cae4449a8f8eabc508d622fd33",
|
||||||
"reference": "9316545571f079c4dd183e674721d9dc783ce196",
|
"reference": "b4a0b67dee59e2cae4449a8f8eabc508d622fd33",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1218,20 +1219,20 @@
|
|||||||
],
|
],
|
||||||
"description": "Symfony Debug Component",
|
"description": "Symfony Debug Component",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"time": "2018-07-26T11:24:31+00:00"
|
"time": "2018-09-22T19:04:12+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/finder",
|
"name": "symfony/finder",
|
||||||
"version": "v4.1.3",
|
"version": "v4.1.5",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/finder.git",
|
"url": "https://github.com/symfony/finder.git",
|
||||||
"reference": "e162f1df3102d0b7472805a5a9d5db9fcf0a8068"
|
"reference": "f0b042d445c155501793e7b8007457f9f5bb1c8c"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/finder/zipball/e162f1df3102d0b7472805a5a9d5db9fcf0a8068",
|
"url": "https://api.github.com/repos/symfony/finder/zipball/f0b042d445c155501793e7b8007457f9f5bb1c8c",
|
||||||
"reference": "e162f1df3102d0b7472805a5a9d5db9fcf0a8068",
|
"reference": "f0b042d445c155501793e7b8007457f9f5bb1c8c",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1267,7 +1268,7 @@
|
|||||||
],
|
],
|
||||||
"description": "Symfony Finder Component",
|
"description": "Symfony Finder Component",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"time": "2018-07-26T11:24:31+00:00"
|
"time": "2018-09-21T12:49:42+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-mbstring",
|
"name": "symfony/polyfill-mbstring",
|
||||||
@@ -1330,16 +1331,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/translation",
|
"name": "symfony/translation",
|
||||||
"version": "v4.1.3",
|
"version": "v4.1.5",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/translation.git",
|
"url": "https://github.com/symfony/translation.git",
|
||||||
"reference": "6fcd1bd44fd6d7181e6ea57a6f4e08a09b29ef65"
|
"reference": "6e49130ddf150b7bfe9e34edb2f3f698aa1aa43b"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/translation/zipball/6fcd1bd44fd6d7181e6ea57a6f4e08a09b29ef65",
|
"url": "https://api.github.com/repos/symfony/translation/zipball/6e49130ddf150b7bfe9e34edb2f3f698aa1aa43b",
|
||||||
"reference": "6fcd1bd44fd6d7181e6ea57a6f4e08a09b29ef65",
|
"reference": "6e49130ddf150b7bfe9e34edb2f3f698aa1aa43b",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1395,7 +1396,7 @@
|
|||||||
],
|
],
|
||||||
"description": "Symfony Translation Component",
|
"description": "Symfony Translation Component",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"time": "2018-07-26T11:24:31+00:00"
|
"time": "2018-09-21T12:49:42+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "tuupola/callable-handler",
|
"name": "tuupola/callable-handler",
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
# Either "production" or "dev"
|
# Either "production", "dev" or "prerelease"
|
||||||
Setting('MODE', 'production');
|
Setting('MODE', 'production');
|
||||||
|
|
||||||
# Either "en" or "de" or the filename (without extension) of
|
# Either "en" or "de" or the filename (without extension) of
|
||||||
# one of the other available localization files in the "/localization" directory
|
# one of the other available localization files in the "/localization" directory
|
||||||
Setting('CULTURE', 'en');
|
Setting('CULTURE', 'en');
|
||||||
|
|
||||||
# To keep it simpel, grocy does not handle any currency conversions,
|
# To keep it simple: grocy does not handle any currency conversions,
|
||||||
# this here is used to format all money values,
|
# this here is used to format all money values,
|
||||||
# so can be anything (e. g. "USD" OR "$", doesn't matter...)
|
# so can be anything (e. g. "USD" OR "$", doesn't matter...)
|
||||||
Setting('CURRENCY', '$');
|
Setting('CURRENCY', '$');
|
||||||
@@ -25,3 +25,20 @@ Setting('STOCK_BARCODE_LOOKUP_PLUGIN', 'DemoBarcodeLookupPlugin');
|
|||||||
# If, however, your webserver does not support URL rewriting,
|
# If, however, your webserver does not support URL rewriting,
|
||||||
# set this to true
|
# set this to true
|
||||||
Setting('DISABLE_URL_REWRITING', false);
|
Setting('DISABLE_URL_REWRITING', false);
|
||||||
|
|
||||||
|
|
||||||
|
# Default user settings
|
||||||
|
# These settings can be changed per user, here the defaults
|
||||||
|
# are defined which are used when the user has not changed the setting so far
|
||||||
|
|
||||||
|
# Night mode related
|
||||||
|
DefaultUserSetting('night_mode_enabled', false); // If night mode is enabled always
|
||||||
|
DefaultUserSetting('auto_night_mode_enabled', false); // If night mode is enabled automatically when inside a given time range (see the two settings below)
|
||||||
|
DefaultUserSetting('auto_night_mode_time_range_from', "20:00"); // Format HH:mm
|
||||||
|
DefaultUserSetting('auto_night_mode_time_range_to', "07:00"); // Format HH:mm
|
||||||
|
DefaultUserSetting('auto_night_mode_time_range_goes_over_midnight', true); // If the time range above goes over midnight
|
||||||
|
DefaultUserSetting('currently_inside_night_mode_range', false); // If we're currently inside of night mode time range (this is not user configurable, but stored as a user setting because it's evaluated client side to be able to use the client time instead of the maybe different server time)
|
||||||
|
|
||||||
|
# If the page should be automatically reloaded when there was
|
||||||
|
# an external change
|
||||||
|
DefaultUserSetting('auto_reload_on_db_change', true);
|
||||||
|
@@ -5,6 +5,7 @@ namespace Grocy\Controllers;
|
|||||||
use \Grocy\Services\DatabaseService;
|
use \Grocy\Services\DatabaseService;
|
||||||
use \Grocy\Services\ApplicationService;
|
use \Grocy\Services\ApplicationService;
|
||||||
use \Grocy\Services\LocalizationService;
|
use \Grocy\Services\LocalizationService;
|
||||||
|
use \Grocy\Services\UsersService;
|
||||||
|
|
||||||
class BaseController
|
class BaseController
|
||||||
{
|
{
|
||||||
@@ -15,10 +16,21 @@ class BaseController
|
|||||||
$localizationService = new LocalizationService(GROCY_CULTURE);
|
$localizationService = new LocalizationService(GROCY_CULTURE);
|
||||||
$this->LocalizationService = $localizationService;
|
$this->LocalizationService = $localizationService;
|
||||||
|
|
||||||
|
if (GROCY_MODE === 'prerelease')
|
||||||
|
{
|
||||||
|
$commitHash = trim(exec('git log --pretty="%h" -n1 HEAD'));
|
||||||
|
$commitDate = trim(exec('git log --date=iso --pretty="%cd" -n1 HEAD'));
|
||||||
|
|
||||||
|
$container->view->set('version', "pre-release-$commitHash");
|
||||||
|
$container->view->set('releaseDate', \substr($commitDate, 0, 19));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
$applicationService = new ApplicationService();
|
$applicationService = new ApplicationService();
|
||||||
$versionInfo = $applicationService->GetInstalledVersion();
|
$versionInfo = $applicationService->GetInstalledVersion();
|
||||||
$container->view->set('version', $versionInfo->Version);
|
$container->view->set('version', $versionInfo->Version);
|
||||||
$container->view->set('releaseDate', $versionInfo->ReleaseDate);
|
$container->view->set('releaseDate', $versionInfo->ReleaseDate);
|
||||||
|
}
|
||||||
|
|
||||||
$container->view->set('localizationStrings', $localizationService->GetCurrentCultureLocalizations());
|
$container->view->set('localizationStrings', $localizationService->GetCurrentCultureLocalizations());
|
||||||
$container->view->set('L', function($text, ...$placeholderValues) use($localizationService)
|
$container->view->set('L', function($text, ...$placeholderValues) use($localizationService)
|
||||||
@@ -30,6 +42,15 @@ class BaseController
|
|||||||
return $container->UrlManager->ConstructUrl($relativePath, $isResource);
|
return $container->UrlManager->ConstructUrl($relativePath, $isResource);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
$usersService = new UsersService();
|
||||||
|
$container->view->set('userSettings', $usersService->GetUserSettings(GROCY_USER_ID));
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
// Happens when database is not initialised or migrated...
|
||||||
|
}
|
||||||
|
|
||||||
$this->AppContainer = $container;
|
$this->AppContainer = $container;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,19 +2,19 @@
|
|||||||
|
|
||||||
namespace Grocy\Controllers;
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
use \Grocy\Services\HabitsService;
|
use \Grocy\Services\ChoresService;
|
||||||
|
|
||||||
class HabitsApiController extends BaseApiController
|
class ChoresApiController extends BaseApiController
|
||||||
{
|
{
|
||||||
public function __construct(\Slim\Container $container)
|
public function __construct(\Slim\Container $container)
|
||||||
{
|
{
|
||||||
parent::__construct($container);
|
parent::__construct($container);
|
||||||
$this->HabitsService = new HabitsService();
|
$this->ChoresService = new ChoresService();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected $HabitsService;
|
protected $ChoresService;
|
||||||
|
|
||||||
public function TrackHabitExecution(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function TrackChoreExecution(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
$trackedTime = date('Y-m-d H:i:s');
|
$trackedTime = date('Y-m-d H:i:s');
|
||||||
if (isset($request->getQueryParams()['tracked_time']) && !empty($request->getQueryParams()['tracked_time']) && IsIsoDateTime($request->getQueryParams()['tracked_time']))
|
if (isset($request->getQueryParams()['tracked_time']) && !empty($request->getQueryParams()['tracked_time']) && IsIsoDateTime($request->getQueryParams()['tracked_time']))
|
||||||
@@ -30,7 +30,7 @@ class HabitsApiController extends BaseApiController
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
$this->HabitsService->TrackHabit($args['habitId'], $trackedTime, $doneBy);
|
$this->ChoresService->TrackChore($args['choreId'], $trackedTime, $doneBy);
|
||||||
return $this->VoidApiActionResponse($response);
|
return $this->VoidApiActionResponse($response);
|
||||||
}
|
}
|
||||||
catch (\Exception $ex)
|
catch (\Exception $ex)
|
||||||
@@ -39,11 +39,11 @@ class HabitsApiController extends BaseApiController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function HabitDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function ChoreDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return $this->ApiResponse($this->HabitsService->GetHabitDetails($args['habitId']));
|
return $this->ApiResponse($this->ChoresService->GetChoreDetails($args['choreId']));
|
||||||
}
|
}
|
||||||
catch (\Exception $ex)
|
catch (\Exception $ex)
|
||||||
{
|
{
|
||||||
@@ -53,6 +53,6 @@ class HabitsApiController extends BaseApiController
|
|||||||
|
|
||||||
public function Current(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function Current(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
return $this->ApiResponse($this->HabitsService->GetCurrent());
|
return $this->ApiResponse($this->ChoresService->GetCurrent());
|
||||||
}
|
}
|
||||||
}
|
}
|
68
controllers/ChoresController.php
Normal file
68
controllers/ChoresController.php
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\ChoresService;
|
||||||
|
|
||||||
|
class ChoresController extends BaseController
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->ChoresService = new ChoresService();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $ChoresService;
|
||||||
|
|
||||||
|
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'choresoverview', [
|
||||||
|
'chores' => $this->Database->chores()->orderBy('name'),
|
||||||
|
'currentChores' => $this->ChoresService->GetCurrent(),
|
||||||
|
'nextXDays' => 5
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function TrackChoreExecution(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'choretracking', [
|
||||||
|
'chores' => $this->Database->chores()->orderBy('name'),
|
||||||
|
'users' => $this->Database->users()->orderBy('username')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ChoresList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'chores', [
|
||||||
|
'chores' => $this->Database->chores()->orderBy('name')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Analysis(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'choresanalysis', [
|
||||||
|
'choresLog' => $this->Database->chores_log()->orderBy('tracked_time', 'DESC'),
|
||||||
|
'chores' => $this->Database->chores()->orderBy('name'),
|
||||||
|
'users' => $this->Database->users()->orderBy('username')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ChoreEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if ($args['choreId'] == 'new')
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'choreform', [
|
||||||
|
'periodTypes' => GetClassConstants('\Grocy\Services\ChoresService'),
|
||||||
|
'mode' => 'create'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'choreform', [
|
||||||
|
'chore' => $this->Database->chores($args['choreId']),
|
||||||
|
'periodTypes' => GetClassConstants('\Grocy\Services\ChoresService'),
|
||||||
|
'mode' => 'edit'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
controllers/FilesApiController.php
Normal file
38
controllers/FilesApiController.php
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\FilesService;
|
||||||
|
|
||||||
|
class FilesApiController extends BaseApiController
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->FilesService = new FilesService();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $FilesService;
|
||||||
|
|
||||||
|
public function Upload(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (isset($request->getQueryParams()['file_name']) && !empty($request->getQueryParams()['file_name']) && IsValidFileName($request->getQueryParams()['file_name']))
|
||||||
|
{
|
||||||
|
$fileName = $request->getQueryParams()['file_name'];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new \Exception('file_name query parameter missing or contains an invalid filename');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = $request->getBody()->getContents();
|
||||||
|
file_put_contents($this->FilesService->GetFilePath($args['group'], $fileName), $data);
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,68 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Grocy\Controllers;
|
|
||||||
|
|
||||||
use \Grocy\Services\HabitsService;
|
|
||||||
|
|
||||||
class HabitsController extends BaseController
|
|
||||||
{
|
|
||||||
public function __construct(\Slim\Container $container)
|
|
||||||
{
|
|
||||||
parent::__construct($container);
|
|
||||||
$this->HabitsService = new HabitsService();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected $HabitsService;
|
|
||||||
|
|
||||||
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
|
||||||
{
|
|
||||||
return $this->AppContainer->view->render($response, 'habitsoverview', [
|
|
||||||
'habits' => $this->Database->habits()->orderBy('name'),
|
|
||||||
'currentHabits' => $this->HabitsService->GetCurrent(),
|
|
||||||
'nextXDays' => 5
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function TrackHabitExecution(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
|
||||||
{
|
|
||||||
return $this->AppContainer->view->render($response, 'habittracking', [
|
|
||||||
'habits' => $this->Database->habits()->orderBy('name'),
|
|
||||||
'users' => $this->Database->users()->orderBy('username')
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function HabitsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
|
||||||
{
|
|
||||||
return $this->AppContainer->view->render($response, 'habits', [
|
|
||||||
'habits' => $this->Database->habits()->orderBy('name')
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function Analysis(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
|
||||||
{
|
|
||||||
return $this->AppContainer->view->render($response, 'habitsanalysis', [
|
|
||||||
'habitsLog' => $this->Database->habits_log()->orderBy('tracked_time', 'DESC'),
|
|
||||||
'habits' => $this->Database->habits()->orderBy('name'),
|
|
||||||
'users' => $this->Database->users()->orderBy('username')
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function HabitEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
|
||||||
{
|
|
||||||
if ($args['habitId'] == 'new')
|
|
||||||
{
|
|
||||||
return $this->AppContainer->view->render($response, 'habitform', [
|
|
||||||
'periodTypes' => GetClassConstants('\Grocy\Services\HabitsService'),
|
|
||||||
'mode' => 'create'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return $this->AppContainer->view->render($response, 'habitform', [
|
|
||||||
'habit' => $this->Database->habits($args['habitId']),
|
|
||||||
'periodTypes' => GetClassConstants('\Grocy\Services\HabitsService'),
|
|
||||||
'mode' => 'edit'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -25,11 +25,12 @@ class LoginController extends BaseController
|
|||||||
{
|
{
|
||||||
$user = $this->Database->users()->where('username', $postParams['username'])->fetch();
|
$user = $this->Database->users()->where('username', $postParams['username'])->fetch();
|
||||||
$inputPassword = $postParams['password'];
|
$inputPassword = $postParams['password'];
|
||||||
|
$stayLoggedInPermanently = $postParams['stay_logged_in'] == 'on';
|
||||||
|
|
||||||
if ($user !== null && password_verify($inputPassword, $user->password))
|
if ($user !== null && password_verify($inputPassword, $user->password))
|
||||||
{
|
{
|
||||||
$sessionKey = $this->SessionService->CreateSession($user->id);
|
$sessionKey = $this->SessionService->CreateSession($user->id, $stayLoggedInPermanently);
|
||||||
setcookie($this->SessionCookieName, $sessionKey, time() + 31536000); // Cookie expires in 1 year, but session validity is up to SessionService
|
setcookie($this->SessionCookieName, $sessionKey, time() + 31220640000); // Cookie expires in 999 years, but session validity is up to SessionService
|
||||||
|
|
||||||
if (password_needs_rehash($user->password, PASSWORD_DEFAULT))
|
if (password_needs_rehash($user->password, PASSWORD_DEFAULT))
|
||||||
{
|
{
|
||||||
|
@@ -54,7 +54,8 @@ class StockController extends BaseController
|
|||||||
'listItems' => $this->Database->shopping_list(),
|
'listItems' => $this->Database->shopping_list(),
|
||||||
'products' => $this->Database->products()->orderBy('name'),
|
'products' => $this->Database->products()->orderBy('name'),
|
||||||
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
|
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
|
||||||
'missingProducts' => $this->StockService->GetMissingProducts()
|
'missingProducts' => $this->StockService->GetMissingProducts(),
|
||||||
|
'productGroups' => $this->Database->product_groups()->orderBy('name')
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +64,8 @@ class StockController extends BaseController
|
|||||||
return $this->AppContainer->view->render($response, 'products', [
|
return $this->AppContainer->view->render($response, 'products', [
|
||||||
'products' => $this->Database->products()->orderBy('name'),
|
'products' => $this->Database->products()->orderBy('name'),
|
||||||
'locations' => $this->Database->locations()->orderBy('name'),
|
'locations' => $this->Database->locations()->orderBy('name'),
|
||||||
'quantityunits' => $this->Database->quantity_units()->orderBy('name')
|
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
|
||||||
|
'productGroups' => $this->Database->product_groups()->orderBy('name')
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +76,13 @@ class StockController extends BaseController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function ProductGroupsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'productgroups', [
|
||||||
|
'productGroups' => $this->Database->product_groups()->orderBy('name')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function QuantityUnitsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function QuantityUnitsList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
return $this->AppContainer->view->render($response, 'quantityunits', [
|
return $this->AppContainer->view->render($response, 'quantityunits', [
|
||||||
@@ -88,6 +97,7 @@ class StockController extends BaseController
|
|||||||
return $this->AppContainer->view->render($response, 'productform', [
|
return $this->AppContainer->view->render($response, 'productform', [
|
||||||
'locations' => $this->Database->locations()->orderBy('name'),
|
'locations' => $this->Database->locations()->orderBy('name'),
|
||||||
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
|
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
|
||||||
|
'productgroups' => $this->Database->product_groups()->orderBy('name'),
|
||||||
'mode' => 'create'
|
'mode' => 'create'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -97,6 +107,7 @@ class StockController extends BaseController
|
|||||||
'product' => $this->Database->products($args['productId']),
|
'product' => $this->Database->products($args['productId']),
|
||||||
'locations' => $this->Database->locations()->orderBy('name'),
|
'locations' => $this->Database->locations()->orderBy('name'),
|
||||||
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
|
'quantityunits' => $this->Database->quantity_units()->orderBy('name'),
|
||||||
|
'productgroups' => $this->Database->product_groups()->orderBy('name'),
|
||||||
'mode' => 'edit'
|
'mode' => 'edit'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@@ -119,6 +130,23 @@ class StockController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function ProductGroupEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if ($args['productGroupId'] == 'new')
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'productgroupform', [
|
||||||
|
'mode' => 'create'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'productgroupform', [
|
||||||
|
'group' => $this->Database->product_groups($args['productGroupId']),
|
||||||
|
'mode' => 'edit'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function QuantityUnitEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function QuantityUnitEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
if ($args['quantityunitId'] == 'new')
|
if ($args['quantityunitId'] == 'new')
|
||||||
|
41
controllers/SystemApiController.php
Normal file
41
controllers/SystemApiController.php
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\DatabaseService;
|
||||||
|
|
||||||
|
class SystemApiController extends BaseApiController
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->DatabaseService = new DatabaseService();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $DatabaseService;
|
||||||
|
|
||||||
|
public function GetDbChangedTime(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->ApiResponse(array(
|
||||||
|
'changed_time' => $this->DatabaseService->GetDbChangedTime()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function LogMissingLocalization(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if (GROCY_MODE === 'dev')
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$requestBody = $request->getParsedBody();
|
||||||
|
|
||||||
|
$this->LocalizationService->LogMissingLocalization(GROCY_CULTURE, $requestBody['text']);
|
||||||
|
return $this->ApiResponse(array('success' => true));
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
controllers/TasksApiController.php
Normal file
40
controllers/TasksApiController.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\TasksService;
|
||||||
|
|
||||||
|
class TasksApiController extends BaseApiController
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->TasksService = new TasksService();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $TasksService;
|
||||||
|
|
||||||
|
public function Current(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->ApiResponse($this->TasksService->GetCurrent());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function MarkTaskAsCompleted(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$doneTime = date('Y-m-d H:i:s');
|
||||||
|
if (isset($request->getQueryParams()['done_time']) && !empty($request->getQueryParams()['done_time']) && IsIsoDateTime($request->getQueryParams()['done_time']))
|
||||||
|
{
|
||||||
|
$doneTime = $request->getQueryParams()['done_time'];
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$this->TasksService->MarkTaskAsCompleted($args['taskId'], $doneTime);
|
||||||
|
return $this->VoidApiActionResponse($response);
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
80
controllers/TasksController.php
Normal file
80
controllers/TasksController.php
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\TasksService;
|
||||||
|
|
||||||
|
class TasksController extends BaseController
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->TasksService = new TasksService();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $TasksService;
|
||||||
|
|
||||||
|
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if (isset($request->getQueryParams()['include_done']))
|
||||||
|
{
|
||||||
|
$tasks = $this->Database->tasks()->orderBy('name');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$tasks = $this->TasksService->GetCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->AppContainer->view->render($response, 'tasks', [
|
||||||
|
'tasks' => $tasks,
|
||||||
|
'nextXDays' => 5,
|
||||||
|
'taskCategories' => $this->Database->task_categories()->orderBy('name'),
|
||||||
|
'users' => $this->Database->users()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function TaskEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if ($args['taskId'] == 'new')
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'taskform', [
|
||||||
|
'mode' => 'create',
|
||||||
|
'taskCategories' => $this->Database->task_categories()->orderBy('name'),
|
||||||
|
'users' => $this->Database->users()->orderBy('username')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'taskform', [
|
||||||
|
'task' => $this->Database->tasks($args['taskId']),
|
||||||
|
'mode' => 'edit',
|
||||||
|
'taskCategories' => $this->Database->task_categories()->orderBy('name'),
|
||||||
|
'users' => $this->Database->users()->orderBy('username')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function TaskCategoriesList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'taskcategories', [
|
||||||
|
'taskCategories' => $this->Database->task_categories()->orderBy('name')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function TaskCategoryEditForm(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
if ($args['categoryId'] == 'new')
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'taskcategoryform', [
|
||||||
|
'mode' => 'create'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'taskcategoryform', [
|
||||||
|
'category' => $this->Database->task_categories($args['categoryId']),
|
||||||
|
'mode' => 'edit'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -68,4 +68,32 @@ class UsersApiController extends BaseApiController
|
|||||||
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function GetUserSetting(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$value = $this->UsersService->GetUserSetting(GROCY_USER_ID, $args['settingKey']);
|
||||||
|
return $this->ApiResponse(array('value' => $value));
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function SetUserSetting(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$requestBody = $request->getParsedBody();
|
||||||
|
|
||||||
|
$value = $this->UsersService->SetUserSetting(GROCY_USER_ID, $args['settingKey'], $requestBody['value']);
|
||||||
|
return $this->ApiResponse(array('success' => true));
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
30
docker-compose.yml
Normal file
30
docker-compose.yml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Usage:
|
||||||
|
# docker-compose build && docker-compose up
|
||||||
|
version: '2'
|
||||||
|
|
||||||
|
services:
|
||||||
|
grocy-nginx:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile-grocy-nginx
|
||||||
|
depends_on:
|
||||||
|
- grocy
|
||||||
|
ports:
|
||||||
|
- '80:80'
|
||||||
|
- '443:443'
|
||||||
|
volumes_from:
|
||||||
|
- grocy
|
||||||
|
container_name: grocy-nginx
|
||||||
|
|
||||||
|
grocy:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile-grocy
|
||||||
|
expose:
|
||||||
|
- 9000
|
||||||
|
environment:
|
||||||
|
PHP_MEMORY_LIMIT: 512M
|
||||||
|
MAX_UPLOAD: 50M
|
||||||
|
PHP_MAX_FILE_UPLOAD: 200
|
||||||
|
PHP_MAX_POST: 100M
|
||||||
|
container_name: grocy
|
28
docker_nginx/common.conf
Normal file
28
docker_nginx/common.conf
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
index index.php index.html index.htm;
|
||||||
|
|
||||||
|
charset utf-8;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.php?$query_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~* .(jpg|jpeg|png|gif|ico|css|js)$ {
|
||||||
|
expires 365d;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_page 404 /404.html;
|
||||||
|
error_page 500 502 503 504 /50x.html;
|
||||||
|
location = /50x.html {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ \.php$ {
|
||||||
|
fastcgi_pass grocy:9000;
|
||||||
|
fastcgi_index index.php;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
|
include fastcgi_params;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ /\.ht {
|
||||||
|
deny all;
|
||||||
|
}
|
8
docker_nginx/conf.d/default.conf
Normal file
8
docker_nginx/conf.d/default.conf
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
server {
|
||||||
|
listen 80 default_server;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /www/public; # see: volumes_from
|
||||||
|
|
||||||
|
include /etc/nginx/common.conf;
|
||||||
|
}
|
20
docker_nginx/conf.d/ssl.conf
Normal file
20
docker_nginx/conf.d/ssl.conf
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /www/public; # see: volumes_from
|
||||||
|
|
||||||
|
ssl_certificate /etc/nginx/certificates/cert.pem;
|
||||||
|
ssl_certificate_key /etc/nginx/certificates/key.pem;
|
||||||
|
|
||||||
|
error_log /var/log/nginx/error.log;
|
||||||
|
|
||||||
|
# ssl_session_cache shared:SSL:1m;
|
||||||
|
# ssl_session_timeout 5m;
|
||||||
|
|
||||||
|
# ssl_ciphers HIGH:!aNULL:!MD5;
|
||||||
|
# ssl_prefer_server_ciphers on;
|
||||||
|
|
||||||
|
include /etc/nginx/common.conf;
|
||||||
|
|
||||||
|
}
|
42
docker_nginx/nginx.conf
Normal file
42
docker_nginx/nginx.conf
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
user nobody;
|
||||||
|
worker_processes 1;
|
||||||
|
|
||||||
|
pid /var/run/nginx/nginx.pid;
|
||||||
|
|
||||||
|
error_log /var/log/nginx/error.log;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
|
sendfile on;
|
||||||
|
#tcp_nopush on;
|
||||||
|
|
||||||
|
client_body_timeout 12;
|
||||||
|
client_header_timeout 12;
|
||||||
|
keepalive_timeout 15;
|
||||||
|
send_timeout 10;
|
||||||
|
|
||||||
|
client_body_buffer_size 10K;
|
||||||
|
client_header_buffer_size 1k;
|
||||||
|
client_max_body_size 50M;
|
||||||
|
large_client_header_buffers 2 1k;
|
||||||
|
|
||||||
|
gzip on;
|
||||||
|
gzip_comp_level 2;
|
||||||
|
gzip_min_length 1000;
|
||||||
|
gzip_proxied expired no-cache no-store private auth;
|
||||||
|
gzip_types text/plain application/x-javascript text/xml text/css application/xml;
|
||||||
|
|
||||||
|
access_log on;
|
||||||
|
|
||||||
|
include /etc/nginx/conf.d/*.conf;
|
||||||
|
}
|
@@ -24,6 +24,67 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
|
"/system/get-db-changed-time": {
|
||||||
|
"get": {
|
||||||
|
"description": "Returns the time when the database was last changed",
|
||||||
|
"tags": [
|
||||||
|
"System"
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "An DbChangedTimeResponse object",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/DbChangedTimeResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/system/log-missing-localization": {
|
||||||
|
"post": {
|
||||||
|
"description": "Logs a missing localization string (only when MODE == 'dev', so should only be called then)",
|
||||||
|
"tags": [
|
||||||
|
"System"
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"description": "A valid MissingLocalizationRequest object",
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/MissingLocalizationRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A VoidApiActionResponse object",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/VoidApiActionResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "A VoidApiActionResponse object",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ErrorExampleVoidApiActionResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/get-objects/{entity}": {
|
"/get-objects/{entity}": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Returns all objects of the given entity",
|
"description": "Returns all objects of the given entity",
|
||||||
@@ -54,7 +115,7 @@
|
|||||||
"$ref": "#/components/schemas/Product"
|
"$ref": "#/components/schemas/Product"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/Habit"
|
"$ref": "#/components/schemas/Chore"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/Battery"
|
"$ref": "#/components/schemas/Battery"
|
||||||
@@ -128,7 +189,7 @@
|
|||||||
"$ref": "#/components/schemas/Product"
|
"$ref": "#/components/schemas/Product"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/Habit"
|
"$ref": "#/components/schemas/Chore"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/Battery"
|
"$ref": "#/components/schemas/Battery"
|
||||||
@@ -191,7 +252,7 @@
|
|||||||
"$ref": "#/components/schemas/Product"
|
"$ref": "#/components/schemas/Product"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/Habit"
|
"$ref": "#/components/schemas/Chore"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/Battery"
|
"$ref": "#/components/schemas/Battery"
|
||||||
@@ -274,7 +335,7 @@
|
|||||||
"$ref": "#/components/schemas/Product"
|
"$ref": "#/components/schemas/Product"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/Habit"
|
"$ref": "#/components/schemas/Chore"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/Battery"
|
"$ref": "#/components/schemas/Battery"
|
||||||
@@ -370,6 +431,66 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/files/upload/{group}": {
|
||||||
|
"post": {
|
||||||
|
"description": "Uploads a single file to /data/storage/{group}/{file_name}",
|
||||||
|
"tags": [
|
||||||
|
"Files"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "group",
|
||||||
|
"required": true,
|
||||||
|
"description": "The file group",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "query",
|
||||||
|
"name": "file_name",
|
||||||
|
"required": true,
|
||||||
|
"description": "The file name (including extension)",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/octet-stream": {
|
||||||
|
"schema": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "binary"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A VoidApiActionResponse object",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/VoidApiActionResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "A VoidApiActionResponse object",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ErrorExampleVoidApiActionResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/users/get": {
|
"/users/get": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Returns all users",
|
"description": "Returns all users",
|
||||||
@@ -537,6 +658,97 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/user/settings/{settingKey}": {
|
||||||
|
"get": {
|
||||||
|
"description": "Gets the given setting of the currently logged on user",
|
||||||
|
"tags": [
|
||||||
|
"User settings"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "settingKey",
|
||||||
|
"required": true,
|
||||||
|
"description": "The key of the user setting",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A UserSetting object",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/UserSetting"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "A VoidApiActionResponse object",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ErrorExampleVoidApiActionResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"description": "Sets the given setting of the currently logged on user",
|
||||||
|
"tags": [
|
||||||
|
"User settings"
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"description": "A valid UserSetting object",
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/UserSetting"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "settingKey",
|
||||||
|
"required": true,
|
||||||
|
"description": "The key of the user setting",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A VoidApiActionResponse object",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/VoidApiActionResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "A VoidApiActionResponse object",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ErrorExampleVoidApiActionResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/stock/add-product/{productId}/{amount}": {
|
"/stock/add-product/{productId}/{amount}": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Adds the the given amount of the given product to stock",
|
"description": "Adds the the given amount of the given product to stock",
|
||||||
@@ -1041,18 +1253,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/habits/track-habit-execution/{habitId}": {
|
"/chores/track-chore-execution/{choreId}": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Tracks an execution of the given habit",
|
"description": "Tracks an execution of the given chore",
|
||||||
"tags": [
|
"tags": [
|
||||||
"Habits"
|
"Chores"
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"name": "habitId",
|
"name": "choreId",
|
||||||
"required": true,
|
"required": true,
|
||||||
"description": "A valid habit id",
|
"description": "A valid chore id",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
}
|
}
|
||||||
@@ -1061,7 +1273,7 @@
|
|||||||
"in": "query",
|
"in": "query",
|
||||||
"name": "tracked_time",
|
"name": "tracked_time",
|
||||||
"required": false,
|
"required": false,
|
||||||
"description": "The time of when the habit was executed, when omitted, the current time is used",
|
"description": "The time of when the chore was executed, when omitted, the current time is used",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "date-time"
|
"type": "date-time"
|
||||||
}
|
}
|
||||||
@@ -1070,7 +1282,7 @@
|
|||||||
"in": "query",
|
"in": "query",
|
||||||
"name": "done_by",
|
"name": "done_by",
|
||||||
"required": false,
|
"required": false,
|
||||||
"description": "A valid user id of who executed this habit, when omitted, the currently authenticated user will be used",
|
"description": "A valid user id of who executed this chore, when omitted, the currently authenticated user will be used",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
}
|
}
|
||||||
@@ -1088,7 +1300,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
"description": "A VoidApiActionResponse object (possible errors are: Not existing habit)",
|
"description": "A VoidApiActionResponse object (possible errors are: Not existing chore)",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
@@ -1100,18 +1312,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/habits/get-habit-details/{habitId}": {
|
"/chores/get-chore-details/{choreId}": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Returns details of the given habit",
|
"description": "Returns details of the given chore",
|
||||||
"tags": [
|
"tags": [
|
||||||
"Habits"
|
"Chores"
|
||||||
],
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"name": "habitId",
|
"name": "choreId",
|
||||||
"required": true,
|
"required": true,
|
||||||
"description": "A valid habit id",
|
"description": "A valid chore id",
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
}
|
}
|
||||||
@@ -1119,17 +1331,17 @@
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "A HabitDetailsResponse object",
|
"description": "A ChoreDetailsResponse object",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/HabitDetailsResponse"
|
"$ref": "#/components/schemas/ChoreDetailsResponse"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
"description": "A VoidApiActionResponse object (possible errors are: Not existing habit)",
|
"description": "A VoidApiActionResponse object (possible errors are: Not existing chore)",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
@@ -1141,21 +1353,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/habits/get-current": {
|
"/chores/get-current": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Returns all habits incl. the next estimated execution time per habit",
|
"description": "Returns all chores incl. the next estimated execution time per chore",
|
||||||
"tags": [
|
"tags": [
|
||||||
"Habits"
|
"Chores"
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "An array of CurrentHabitResponse objects",
|
"description": "An array of CurrentChoreResponse objects",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/components/schemas/CurrentHabitResponse"
|
"$ref": "#/components/schemas/CurrentChoreResponse"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1278,6 +1490,79 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/tasks/get-current": {
|
||||||
|
"get": {
|
||||||
|
"description": "Returns all tasks which are not done yet",
|
||||||
|
"tags": [
|
||||||
|
"Tasks"
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "An array of Task objects",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/Task"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/tasks/mark-task-as-completed/{taskId}": {
|
||||||
|
"get": {
|
||||||
|
"description": "Marks the given task as completed",
|
||||||
|
"tags": [
|
||||||
|
"Tasks"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "taskId",
|
||||||
|
"required": true,
|
||||||
|
"description": "A valid task id",
|
||||||
|
"schema": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "query",
|
||||||
|
"name": "done_time",
|
||||||
|
"required": false,
|
||||||
|
"description": "The time of when the task was completed, when omitted, the current time is used",
|
||||||
|
"schema": {
|
||||||
|
"type": "date-time"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A VoidApiActionResponse object",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/VoidApiActionResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "A VoidApiActionResponse object (possible errors are: Not existing task)",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ErrorExampleVoidApiActionResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
@@ -1286,13 +1571,16 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"products",
|
"products",
|
||||||
"habits",
|
"chores",
|
||||||
"batteries",
|
"batteries",
|
||||||
"locations",
|
"locations",
|
||||||
"quantity_units",
|
"quantity_units",
|
||||||
"shopping_list",
|
"shopping_list",
|
||||||
"recipes",
|
"recipes",
|
||||||
"recipes_pos"
|
"recipes_pos",
|
||||||
|
"tasks",
|
||||||
|
"task_categories",
|
||||||
|
"product_groups"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"StockTransactionType": {
|
"StockTransactionType": {
|
||||||
@@ -1494,20 +1782,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"HabitDetailsResponse": {
|
"ChoreDetailsResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"habit": {
|
"chore": {
|
||||||
"$ref": "#/components/schemas/Habit"
|
"$ref": "#/components/schemas/Chore"
|
||||||
},
|
},
|
||||||
"last_tracked": {
|
"last_tracked": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "date-time",
|
"format": "date-time",
|
||||||
"description": "When this habit was last tracked"
|
"description": "When this chore was last tracked"
|
||||||
},
|
},
|
||||||
"track_count": {
|
"track_count": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "How often this habit was tracked so far"
|
"description": "How often this chore was tracked so far"
|
||||||
},
|
},
|
||||||
"last_done_by": {
|
"last_done_by": {
|
||||||
"$ref": "#/components/schemas/UserDto"
|
"$ref": "#/components/schemas/UserDto"
|
||||||
@@ -1521,7 +1809,7 @@
|
|||||||
"BatteryDetailsResponse": {
|
"BatteryDetailsResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"habit": {
|
"chore": {
|
||||||
"$ref": "#/components/schemas/Battery"
|
"$ref": "#/components/schemas/Battery"
|
||||||
},
|
},
|
||||||
"last_charged": {
|
"last_charged": {
|
||||||
@@ -1709,7 +1997,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Habit": {
|
"Chore": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
@@ -1737,13 +2025,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"HabitLogEntry": {
|
"ChoreLogEntry": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"habit_id": {
|
"chore_id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"tracked_time": {
|
"tracked_time": {
|
||||||
@@ -1842,10 +2130,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CurrentHabitResponse": {
|
"CurrentChoreResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"habit_id": {
|
"chore_id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"last_tracked_time": {
|
"last_tracked_time": {
|
||||||
@@ -1855,7 +2143,7 @@
|
|||||||
"next_estimated_execution_time": {
|
"next_estimated_execution_time": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "date-time",
|
"format": "date-time",
|
||||||
"description": "The next estimated execution time of this habit, 2999-12-31 23:59:59 when the given habit has a period_type of manually"
|
"description": "The next estimated execution time of this chore, 2999-12-31 23:59:59 when the given chore has a period_type of manually"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1898,6 +2186,66 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Task": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"due_date": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"done": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"done_timestamp": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"category_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"assigned_to_user_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"row_created_timestamp": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DbChangedTimeResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"changed_time": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"UserSetting": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"value": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"MissingLocalizationRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"text": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"examples": {
|
"examples": {
|
||||||
|
@@ -32,6 +32,11 @@ class UrlManager
|
|||||||
|
|
||||||
private function GetBaseUrl()
|
private function GetBaseUrl()
|
||||||
{
|
{
|
||||||
|
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false)
|
||||||
|
{
|
||||||
|
$_SERVER['HTTPS'] = 'on';
|
||||||
|
}
|
||||||
|
|
||||||
return (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]";
|
return (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -145,6 +145,17 @@ function Setting(string $name, $value)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
global $GROCY_DEFAULT_USER_SETTINGS;
|
||||||
|
$GROCY_DEFAULT_USER_SETTINGS = array();
|
||||||
|
function DefaultUserSetting(string $name, $value)
|
||||||
|
{
|
||||||
|
global $GROCY_DEFAULT_USER_SETTINGS;
|
||||||
|
if (!array_key_exists($name, $GROCY_DEFAULT_USER_SETTINGS))
|
||||||
|
{
|
||||||
|
$GROCY_DEFAULT_USER_SETTINGS[$name] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function GetUserDisplayName($user)
|
function GetUserDisplayName($user)
|
||||||
{
|
{
|
||||||
$displayName = '';
|
$displayName = '';
|
||||||
@@ -178,3 +189,13 @@ function Pluralize($number, $singularForm, $pluralForm)
|
|||||||
}
|
}
|
||||||
return $text;
|
return $text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function IsValidFileName($fileName)
|
||||||
|
{
|
||||||
|
if(preg_match('#^[a-z0-9]+\.[a-z]+?$#i', $fileName))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
6
info.php
Normal file
6
info.php
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// Show all information, defaults to INFO_ALL
|
||||||
|
phpinfo();
|
||||||
|
|
||||||
|
?>
|
@@ -9,20 +9,20 @@ return array(
|
|||||||
'Amount' => 'Menge',
|
'Amount' => 'Menge',
|
||||||
'Next best before date' => 'Nächstes MHD',
|
'Next best before date' => 'Nächstes MHD',
|
||||||
'Logout' => 'Abmelden',
|
'Logout' => 'Abmelden',
|
||||||
'Habits overview' => 'Gewohnheiten',
|
'Chores overview' => 'Hausarbeiten',
|
||||||
'Batteries overview' => 'Batterien',
|
'Batteries overview' => 'Batterien',
|
||||||
'Purchase' => 'Einkauf',
|
'Purchase' => 'Einkauf',
|
||||||
'Consume' => 'Verbrauch',
|
'Consume' => 'Verbrauch',
|
||||||
'Inventory' => 'Inventur',
|
'Inventory' => 'Inventur',
|
||||||
'Shopping list' => 'Einkaufszettel',
|
'Shopping list' => 'Einkaufszettel',
|
||||||
'Habit tracking' => 'Gewohnheit-Ausführung',
|
'Chore tracking' => 'Hausarbeiten-Ausführung',
|
||||||
'Battery tracking' => 'Batterie-Ladzyklus',
|
'Battery tracking' => 'Batterie-Ladzyklus',
|
||||||
'Products' => 'Produkte',
|
'Products' => 'Produkte',
|
||||||
'Locations' => 'Standorte',
|
'Locations' => 'Standorte',
|
||||||
'Quantity units' => 'Mengeneinheiten',
|
'Quantity units' => 'Mengeneinheiten',
|
||||||
'Habits' => 'Gewohnheiten',
|
'Chores' => 'Hausarbeiten',
|
||||||
'Batteries' => 'Batterien',
|
'Batteries' => 'Batterien',
|
||||||
'Habit' => 'Gewohnheit',
|
'Chore' => 'Hausarbeit',
|
||||||
'Next estimated tracking' => 'Nächste geplante Ausführung',
|
'Next estimated tracking' => 'Nächste geplante Ausführung',
|
||||||
'Last tracked' => 'Zuletzt ausgeführt',
|
'Last tracked' => 'Zuletzt ausgeführt',
|
||||||
'Battery' => 'Batterie',
|
'Battery' => 'Batterie',
|
||||||
@@ -41,7 +41,7 @@ return array(
|
|||||||
'New amount' => 'Neue Menge',
|
'New amount' => 'Neue Menge',
|
||||||
'Note' => 'Notiz',
|
'Note' => 'Notiz',
|
||||||
'Tracked time' => 'Ausführungszeit',
|
'Tracked time' => 'Ausführungszeit',
|
||||||
'Habit overview' => 'Gewohnheit Übersicht',
|
'Chore overview' => 'Hausarbeit Übersicht',
|
||||||
'Tracked count' => 'Ausführungsanzahl',
|
'Tracked count' => 'Ausführungsanzahl',
|
||||||
'Battery overview' => 'Batterie Übersicht',
|
'Battery overview' => 'Batterie Übersicht',
|
||||||
'Charge cycles count' => 'Ladezyklen',
|
'Charge cycles count' => 'Ladezyklen',
|
||||||
@@ -68,11 +68,11 @@ return array(
|
|||||||
'Create quantity unit' => 'Mengeneinheit erstellen',
|
'Create quantity unit' => 'Mengeneinheit erstellen',
|
||||||
'Period type' => 'Periodentyp',
|
'Period type' => 'Periodentyp',
|
||||||
'Period days' => 'Tage/Periode',
|
'Period days' => 'Tage/Periode',
|
||||||
'Create habit' => 'Gewohnheit erstellen',
|
'Create chore' => 'Hausarbeit erstellen',
|
||||||
'Used in' => 'Benutzt in',
|
'Used in' => 'Benutzt in',
|
||||||
'Create battery' => 'Batterie erstellen',
|
'Create battery' => 'Batterie erstellen',
|
||||||
'Edit battery' => 'Batterie bearbeiten',
|
'Edit battery' => 'Batterie bearbeiten',
|
||||||
'Edit habit' => 'Gewohnheit bearbeiten',
|
'Edit chore' => 'Hausarbeit bearbeiten',
|
||||||
'Edit quantity unit' => 'Mengeneinheit bearbeiten',
|
'Edit quantity unit' => 'Mengeneinheit bearbeiten',
|
||||||
'Edit product' => 'Produkt bearbeiten',
|
'Edit product' => 'Produkt bearbeiten',
|
||||||
'Edit location' => 'Standort bearbeiten',
|
'Edit location' => 'Standort bearbeiten',
|
||||||
@@ -90,7 +90,7 @@ return array(
|
|||||||
'Are you sure to delete battery "#1"?' => 'Battery "#1" wirklich löschen?',
|
'Are you sure to delete battery "#1"?' => 'Battery "#1" wirklich löschen?',
|
||||||
'Yes' => 'Ja',
|
'Yes' => 'Ja',
|
||||||
'No' => 'Nein',
|
'No' => 'Nein',
|
||||||
'Are you sure to delete habit "#1"?' => 'Gewohnheit "#1" wirklich löschen?',
|
'Are you sure to delete chore "#1"?' => 'Hausarbeit "#1" wirklich löschen?',
|
||||||
'"#1" could not be resolved to a product, how do you want to proceed?' => '"#1" konnte nicht zu einem Produkt aufgelöst werden, wie möchtest du weiter machen?',
|
'"#1" could not be resolved to a product, how do you want to proceed?' => '"#1" konnte nicht zu einem Produkt aufgelöst werden, wie möchtest du weiter machen?',
|
||||||
'Create or assign product' => 'Produkt erstellen oder verknüpfen',
|
'Create or assign product' => 'Produkt erstellen oder verknüpfen',
|
||||||
'Cancel' => 'Abbrechen',
|
'Cancel' => 'Abbrechen',
|
||||||
@@ -110,29 +110,29 @@ return array(
|
|||||||
'This product is not in stock' => 'Dieses Produkt ist nicht vorrätig',
|
'This product is not in stock' => 'Dieses Produkt ist nicht vorrätig',
|
||||||
'This means #1 will be added to stock' => 'Das bedeutet #1 wird dem Bestand hinzugefügt',
|
'This means #1 will be added to stock' => 'Das bedeutet #1 wird dem Bestand hinzugefügt',
|
||||||
'This means #1 will be removed from stock' => 'Das bedeutet #1 wird aus dem Bestand entfernt',
|
'This means #1 will be removed from stock' => 'Das bedeutet #1 wird aus dem Bestand entfernt',
|
||||||
'This means it is estimated that a new execution of this habit is tracked #1 days after the last was tracked' => 'Das bedeutet, dass eine erneute Ausführung der Gewohnheit #1 Tage nach der letzten Ausführung geplant wird',
|
'This means it is estimated that a new execution of this chore is tracked #1 days after the last was tracked' => 'Das bedeutet, dass eine erneute Ausführung der Hausarbeit #1 Tage nach der letzten Ausführung geplant wird',
|
||||||
'Removed #1 #2 of #3 from stock' => '#1 #2 #3 aus dem Bestand entfernt',
|
'Removed #1 #2 of #3 from stock' => '#1 #2 #3 aus dem Bestand entfernt',
|
||||||
'About grocy' => 'Über grocy',
|
'About grocy' => 'Über grocy',
|
||||||
'Close' => 'Schließen',
|
'Close' => 'Schließen',
|
||||||
'#1 batteries are due to be charged within the next #2 days' => '#1 Batterien müssen in den nächsten #2 Tagen geladen werden',
|
'#1 batteries are due to be charged within the next #2 days' => '#1 Batterien müssen in den nächsten #2 Tagen geladen werden',
|
||||||
'#1 batteries are overdue to be charged' => '#1 Batterien sind überfällig',
|
'#1 batteries are overdue to be charged' => '#1 Batterien sind überfällig',
|
||||||
'#1 habits are due to be done within the next #2 days' => '#1 Gewohnheiten stehen in den nächsten #2 Tagen an',
|
'#1 chores are due to be done within the next #2 days' => '#1 Hausarbeiten stehen in den nächsten #2 Tagen an',
|
||||||
'#1 habits are overdue to be done' => '#1 Gewohnheiten sind überfällig',
|
'#1 chores are overdue to be done' => '#1 Hausarbeiten sind überfällig',
|
||||||
'Released on' => 'Veröffentlicht am',
|
'Released on' => 'Veröffentlicht am',
|
||||||
'Consume #3 #1 of #2' => 'Verbrauche #3 #1 #2',
|
'Consume #3 #1 of #2' => 'Verbrauche #3 #1 #2',
|
||||||
'Added #1 #2 of #3 to stock' => '#1 #2 #3 dem Bestand hinzugefügt',
|
'Added #1 #2 of #3 to stock' => '#1 #2 #3 dem Bestand hinzugefügt',
|
||||||
'Stock amount of #1 is now #2 #3' => 'Es sind nun #2 #3 #1 im Bestand',
|
'Stock amount of #1 is now #2 #3' => 'Es sind nun #2 #3 #1 im Bestand',
|
||||||
'Tracked execution of habit #1 on #2' => 'Ausführung von #1 am #2 erfasst',
|
'Tracked execution of chore #1 on #2' => 'Ausführung von #1 am #2 erfasst',
|
||||||
'Tracked charge cylce of battery #1 on #2' => 'Ladezyklus für Batterie #1 am #2 erfasst',
|
'Tracked charge cycle of battery #1 on #2' => 'Ladezyklus für Batterie #1 am #2 erfasst',
|
||||||
'Consume all #1 which are currently in stock' => 'Verbrauche den kompletten Bestand von #1',
|
'Consume all #1 which are currently in stock' => 'Verbrauche den kompletten Bestand von #1',
|
||||||
'All' => 'Alle',
|
'All' => 'Alle',
|
||||||
'Track charge cycle of battery #1' => 'Erfasse einen Ladezyklus für Batterie #1',
|
'Track charge cycle of battery #1' => 'Erfasse einen Ladezyklus für Batterie #1',
|
||||||
'Track execution of habit #1' => 'Erfasse eine Ausführung von #1',
|
'Track execution of chore #1' => 'Erfasse eine Ausführung von #1',
|
||||||
'Filter by location' => 'Nach Standort filtern',
|
'Filter by location' => 'Nach Standort filtern',
|
||||||
'Search' => 'Suche',
|
'Search' => 'Suche',
|
||||||
'Not logged in' => 'Nicht angemeldet',
|
'Not logged in' => 'Nicht angemeldet',
|
||||||
'You have to select a product' => 'Ein Produkt muss ausgewählt werden',
|
'You have to select a product' => 'Ein Produkt muss ausgewählt werden',
|
||||||
'You have to select a habit' => 'Eine Gewohnheit muss ausgewählt werden',
|
'You have to select a chore' => 'Eine Hausarbeit muss ausgewählt werden',
|
||||||
'You have to select a battery' => 'Eine Batterie muss ausgewählt werden',
|
'You have to select a battery' => 'Eine Batterie muss ausgewählt werden',
|
||||||
'A name is required' => 'Ein Name ist erforderlich',
|
'A name is required' => 'Ein Name ist erforderlich',
|
||||||
'A location is required' => 'Ein Standort ist erforderlich',
|
'A location is required' => 'Ein Standort ist erforderlich',
|
||||||
@@ -183,8 +183,8 @@ return array(
|
|||||||
'Done by' => 'Ausgeführt von',
|
'Done by' => 'Ausgeführt von',
|
||||||
'Last done by' => 'Zuletzt ausgeführt von',
|
'Last done by' => 'Zuletzt ausgeführt von',
|
||||||
'Unknown' => 'Unbekannt',
|
'Unknown' => 'Unbekannt',
|
||||||
'Filter by habit' => 'Nach Gewohnheit filtern',
|
'Filter by chore' => 'Nach Hausarbeit filtern',
|
||||||
'Habits analysis' => 'Gewohnheiten Analyse',
|
'Chores analysis' => 'Hausarbeiten Analyse',
|
||||||
'0 means suggestions for the next charge cycle are disabled' => '0 bedeutet dass Vorschläge für den nächsten Ladezyklus deaktiviert sind',
|
'0 means suggestions for the next charge cycle are disabled' => '0 bedeutet dass Vorschläge für den nächsten Ladezyklus deaktiviert sind',
|
||||||
'Charge cycle interval (days)' => 'Ladezyklusintervall (Tage)',
|
'Charge cycle interval (days)' => 'Ladezyklusintervall (Tage)',
|
||||||
'Last price' => 'Letzter Preis',
|
'Last price' => 'Letzter Preis',
|
||||||
@@ -198,8 +198,8 @@ return array(
|
|||||||
'#1 product is below defined min. stock amount' => '#1 Produkt ist unter Mindestbestand',
|
'#1 product is below defined min. stock amount' => '#1 Produkt ist unter Mindestbestand',
|
||||||
'Unit' => 'Einheit',
|
'Unit' => 'Einheit',
|
||||||
'Units' => 'Einheiten',
|
'Units' => 'Einheiten',
|
||||||
'#1 habit is due to be done within the next #2 days' => '#1 Gewohnheit steht in den nächsten #2 Tagen an',
|
'#1 chore is due to be done within the next #2 days' => '#1 Hausarbeit steht in den nächsten #2 Tagen an',
|
||||||
'#1 habit is overdue to be done' => '#1 Gewohnheit ist überfällig',
|
'#1 chore is overdue to be done' => '#1 Hausarbeit ist überfällig',
|
||||||
'#1 battery is due to be charged within the next #2 days' => '#1 Batterie muss in den nächsten #2 Tagen geladen werden',
|
'#1 battery is due to be charged within the next #2 days' => '#1 Batterie muss in den nächsten #2 Tagen geladen werden',
|
||||||
'#1 battery is overdue to be charged' => '#1 Batterie ist überfällig',
|
'#1 battery is overdue to be charged' => '#1 Batterie ist überfällig',
|
||||||
'#1 unit was automatically added and will apply in addition to the amount entered here' => '#1 Einheit wurde automatisch hinzugefügt und gilt zusätzlich der hier eingegebenen Menge',
|
'#1 unit was automatically added and will apply in addition to the amount entered here' => '#1 Einheit wurde automatisch hinzugefügt und gilt zusätzlich der hier eingegebenen Menge',
|
||||||
@@ -213,6 +213,50 @@ return array(
|
|||||||
'Are you sure to consume all ingredients needed by recipe "#1" (ingredients marked with "check only if a single unit is in stock" will be ignored)?' => 'Sicher, dass alle Zutaten die vom Rezept "#1" benötigt werden aus dem Bestand entfernt werden sollen (Zutaten markiert mit "nur prüfen, ob eine einzelne Einheit vorrätig ist" werden ignoriert)?',
|
'Are you sure to consume all ingredients needed by recipe "#1" (ingredients marked with "check only if a single unit is in stock" will be ignored)?' => 'Sicher, dass alle Zutaten die vom Rezept "#1" benötigt werden aus dem Bestand entfernt werden sollen (Zutaten markiert mit "nur prüfen, ob eine einzelne Einheit vorrätig ist" werden ignoriert)?',
|
||||||
'Removed all ingredients of recipe "#1" from stock' => 'Alle Zutaten, die vom Rezept "#1" benötigt werden, wurdem aus dem Bestand entfernt',
|
'Removed all ingredients of recipe "#1" from stock' => 'Alle Zutaten, die vom Rezept "#1" benötigt werden, wurdem aus dem Bestand entfernt',
|
||||||
'Consume all ingredients needed by this recipe' => 'Alle Zutaten, die von diesem Rezept benötigt werden, aus dem Bestand enternen',
|
'Consume all ingredients needed by this recipe' => 'Alle Zutaten, die von diesem Rezept benötigt werden, aus dem Bestand enternen',
|
||||||
|
'Click to show technical details' => 'Klick um technische Details anzuzeigen',
|
||||||
|
'Error while saving, probably this item already exists' => 'Fehler beim Speichern, möglicherweise existiert das Element bereits',
|
||||||
|
'Error details' => 'Fehlerdetails',
|
||||||
|
'Tasks' => 'Aufgaben',
|
||||||
|
'Show done tasks' => 'Erledigte Aufgaben anzeigen',
|
||||||
|
'Task' => 'Aufgabe',
|
||||||
|
'Due' => 'Fällig',
|
||||||
|
'Assigned to' => 'Zugewiesen an',
|
||||||
|
'Mark task "#1" as completed' => 'Aufgabe "#1" als erledigt markieren',
|
||||||
|
'Uncategorized' => 'Nicht kategorisiert',
|
||||||
|
'Task categories' => 'Aufgabenkategorien',
|
||||||
|
'Create task' => 'Aufgabe erstellen',
|
||||||
|
'A due date is required' => 'Ein Fälligkeitsdatum ist erforderlich',
|
||||||
|
'Category' => 'Kategorie',
|
||||||
|
'Edit task' => 'Aufgabe bearbeiten',
|
||||||
|
'Are you sure to delete task "#1"?' => 'Aufgabe "#1" wirklich löschen?',
|
||||||
|
'#1 task is due to be done within the next #2 days' => '#1 Aufgabe steht in den nächsten #2 Tagen an',
|
||||||
|
'#1 tasks are due to be done within the next #2 days' => '#1 Aufgaben stehen in den nächsten #2 Tagen an',
|
||||||
|
'#1 task is overdue to be done' => '#1 Aufgabe ist überfällig',
|
||||||
|
'#1 tasks are overdue to be done' => '#1 Aufgaben sind überfällig',
|
||||||
|
'Edit task category' => 'Aufgabenkategorie bearbeiten',
|
||||||
|
'Create task category' => 'Aufgabenkategorie erstellen',
|
||||||
|
'Product groups' => 'Produktgruppen',
|
||||||
|
'Ungrouped' => 'Ungruppiert',
|
||||||
|
'Create product group' => 'Produktgruppe erstellen',
|
||||||
|
'Edit product group' => 'Produktgruppe bearbeiten',
|
||||||
|
'Product group' => 'Produktgruppe',
|
||||||
|
'Are you sure to delete product group "#1"?' => 'Produktgruppe "#1" wirklich löschen?',
|
||||||
|
'Stay logged in permanently' => 'Dauerhaft angemeldet bleiben',
|
||||||
|
'When not set, you will get logged out at latest after 30 days' => 'Wenn nicht gesetzt, wirst du spätestens nach 30 Tagen automatisch abgemeldet',
|
||||||
|
'Filter by status' => 'Nach Status filtern',
|
||||||
|
'Below min. stock amount' => 'Unter Mindestbestand',
|
||||||
|
'Expiring soon' => 'Bald ablaufend',
|
||||||
|
'Already expired' => 'Bereits abgelaufen',
|
||||||
|
'Due soon' => 'Bald fällig',
|
||||||
|
'Overdue' => 'Überfällig',
|
||||||
|
'View settings' => 'xxx',
|
||||||
|
'Auto reload on external changes' => 'Autom. akt. bei externen Änderungen',
|
||||||
|
'Enable night mode' => 'Nachtmodus aktivieren',
|
||||||
|
'Auto enable in time range' => 'Autom. akt. in diesem Zeitraum',
|
||||||
|
'From' => 'Von',
|
||||||
|
'in format' => 'im Format',
|
||||||
|
'To' => 'Bis',
|
||||||
|
'Time range goes over midnight' => 'Zeitraum geht über Mitternacht',
|
||||||
|
|
||||||
//Constants
|
//Constants
|
||||||
'manually' => 'Manuell',
|
'manually' => 'Manuell',
|
||||||
@@ -282,5 +326,17 @@ return array(
|
|||||||
'Grams' => 'Gramm',
|
'Grams' => 'Gramm',
|
||||||
'Flour' => 'Mehl',
|
'Flour' => 'Mehl',
|
||||||
'Pancakes' => 'Pfannkuchen',
|
'Pancakes' => 'Pfannkuchen',
|
||||||
'Sugar' => 'Zucker'
|
'Sugar' => 'Zucker',
|
||||||
|
'Home' => 'Zuhause',
|
||||||
|
'Life' => 'Leben',
|
||||||
|
'Projects' => 'Projekte',
|
||||||
|
'Repair the garage door' => 'Garagentor reparieren',
|
||||||
|
'Fork and improve grocy' => 'grocy forken und verbessern',
|
||||||
|
'Find a solution for what to do when I forget the door keys' => 'Eine Lösung für "Haustürschlüssel vergessen" finden',
|
||||||
|
'Sweets' => 'Süßigkeiten',
|
||||||
|
'Bakery products' => 'Bäckerei Produkte',
|
||||||
|
'Tinned food' => 'Konservern',
|
||||||
|
'Butchery products' => 'Metzgerei',
|
||||||
|
'Vegetables/Fruits' => 'Obst/Gemüse',
|
||||||
|
'Refrigerated products' => 'Kühlregal'
|
||||||
);
|
);
|
||||||
|
@@ -9,20 +9,20 @@ return array(
|
|||||||
'Amount' => 'quantità',
|
'Amount' => 'quantità',
|
||||||
'Next best before date' => 'Prossima data di scadenza',
|
'Next best before date' => 'Prossima data di scadenza',
|
||||||
'Logout' => 'Logout',
|
'Logout' => 'Logout',
|
||||||
'Habits overview' => 'Riepilogo delle abitudini',
|
'Chores overview' => 'Riepilogo delle abitudini',
|
||||||
'Batteries overview' => 'Riepilogo delle batterie',
|
'Batteries overview' => 'Riepilogo delle batterie',
|
||||||
'Purchase' => 'Acquisti',
|
'Purchase' => 'Acquisti',
|
||||||
'Consume' => 'Consumi',
|
'Consume' => 'Consumi',
|
||||||
'Inventory' => 'Inventario',
|
'Inventory' => 'Inventario',
|
||||||
'Shopping list' => 'Lista della spesa',
|
'Shopping list' => 'Lista della spesa',
|
||||||
'Habit tracking' => 'Dati abitudini',
|
'Chore tracking' => 'Dati abitudini',
|
||||||
'Battery tracking' => 'Dati batterie',
|
'Battery tracking' => 'Dati batterie',
|
||||||
'Products' => 'Prodotti',
|
'Products' => 'Prodotti',
|
||||||
'Locations' => 'Posizioni',
|
'Locations' => 'Posizioni',
|
||||||
'Quantity units' => 'Unità di misura',
|
'Quantity units' => 'Unità di misura',
|
||||||
'Habits' => 'Abitudini',
|
'Chores' => 'Abitudini',
|
||||||
'Batteries' => 'Batterie',
|
'Batteries' => 'Batterie',
|
||||||
'Habit' => 'Abitudine',
|
'Chore' => 'Abitudine',
|
||||||
'Next estimated tracking' => 'Prossima esecuzione',
|
'Next estimated tracking' => 'Prossima esecuzione',
|
||||||
'Last tracked' => 'Ultima esecuzione',
|
'Last tracked' => 'Ultima esecuzione',
|
||||||
'Battery' => 'Batterie',
|
'Battery' => 'Batterie',
|
||||||
@@ -41,7 +41,7 @@ return array(
|
|||||||
'New amount' => 'Nuova quantità',
|
'New amount' => 'Nuova quantità',
|
||||||
'Note' => 'Nota',
|
'Note' => 'Nota',
|
||||||
'Tracked time' => 'Ora di esecuzione',
|
'Tracked time' => 'Ora di esecuzione',
|
||||||
'Habit overview' => 'Riepilogo dell\'abitudine',
|
'Chore overview' => 'Riepilogo dell\'abitudine',
|
||||||
'Tracked count' => 'Numero di esecuzioni',
|
'Tracked count' => 'Numero di esecuzioni',
|
||||||
'Battery overview' => 'Riepilogo della batteria',
|
'Battery overview' => 'Riepilogo della batteria',
|
||||||
'Charge cycles count' => 'Numero di ricariche',
|
'Charge cycles count' => 'Numero di ricariche',
|
||||||
@@ -68,11 +68,11 @@ return array(
|
|||||||
'Create quantity unit' => 'Aggiungi unità di misura',
|
'Create quantity unit' => 'Aggiungi unità di misura',
|
||||||
'Period type' => 'Tipo di ripetizione',
|
'Period type' => 'Tipo di ripetizione',
|
||||||
'Period days' => 'Periodo in giorni',
|
'Period days' => 'Periodo in giorni',
|
||||||
'Create habit' => 'Aggiungi abitudine',
|
'Create chore' => 'Aggiungi abitudine',
|
||||||
'Used in' => 'Usato in',
|
'Used in' => 'Usato in',
|
||||||
'Create battery' => 'Aggiungi batteria',
|
'Create battery' => 'Aggiungi batteria',
|
||||||
'Edit battery' => 'Modifica batteria',
|
'Edit battery' => 'Modifica batteria',
|
||||||
'Edit habit' => 'Modifica abitudine',
|
'Edit chore' => 'Modifica abitudine',
|
||||||
'Edit quantity unit' => 'Modifica unità di misura',
|
'Edit quantity unit' => 'Modifica unità di misura',
|
||||||
'Edit product' => 'Modifica prodotto',
|
'Edit product' => 'Modifica prodotto',
|
||||||
'Edit location' => 'Modifica posizione',
|
'Edit location' => 'Modifica posizione',
|
||||||
@@ -90,7 +90,7 @@ return array(
|
|||||||
'Are you sure to delete battery "#1"?' => 'Sei sicuro di voler eliminare la batteria "#1"?',
|
'Are you sure to delete battery "#1"?' => 'Sei sicuro di voler eliminare la batteria "#1"?',
|
||||||
'Yes' => 'Si',
|
'Yes' => 'Si',
|
||||||
'No' => 'No',
|
'No' => 'No',
|
||||||
'Are you sure to delete habit "#1"?' => 'Sei sicuro di voler eliminare l\'abitudine "#1"?',
|
'Are you sure to delete chore "#1"?' => 'Sei sicuro di voler eliminare l\'abitudine "#1"?',
|
||||||
'"#1" could not be resolved to a product, how do you want to proceed?' => '"#1" non è stato associato a nessun prodotto, vuoi procedere?',
|
'"#1" could not be resolved to a product, how do you want to proceed?' => '"#1" non è stato associato a nessun prodotto, vuoi procedere?',
|
||||||
'Create or assign product' => 'Aggiungi o assegna prodotto',
|
'Create or assign product' => 'Aggiungi o assegna prodotto',
|
||||||
'Cancel' => 'Annulla',
|
'Cancel' => 'Annulla',
|
||||||
@@ -110,29 +110,29 @@ return array(
|
|||||||
'This product is not in stock' => 'Questo prodotto non è in dispensa',
|
'This product is not in stock' => 'Questo prodotto non è in dispensa',
|
||||||
'This means #1 will be added to stock' => '#1 sarà aggiunto alla dispensa',
|
'This means #1 will be added to stock' => '#1 sarà aggiunto alla dispensa',
|
||||||
'This means #1 will be removed from stock' => '#1 sarà rimosso dalla dispensa',
|
'This means #1 will be removed from stock' => '#1 sarà rimosso dalla dispensa',
|
||||||
'This means it is estimated that a new execution of this habit is tracked #1 days after the last was tracked' => 'L\'esecuzione dell\'abitudine è #1 giorni dopo la precedente',
|
'This means it is estimated that a new execution of this chore is tracked #1 days after the last was tracked' => 'L\'esecuzione dell\'abitudine è #1 giorni dopo la precedente',
|
||||||
'Removed #1 #2 of #3 from stock' => '#1 #2 su #3 rimossi dalla dispensa',
|
'Removed #1 #2 of #3 from stock' => '#1 #2 su #3 rimossi dalla dispensa',
|
||||||
'About grocy' => 'Riguardo grocy',
|
'About grocy' => 'Riguardo grocy',
|
||||||
'Close' => 'Chiudi',
|
'Close' => 'Chiudi',
|
||||||
'#1 batteries are due to be charged within the next #2 days' => '#1 batterie da ricaricare entro #2 giorni',
|
'#1 batteries are due to be charged within the next #2 days' => '#1 batterie da ricaricare entro #2 giorni',
|
||||||
'#1 batteries are overdue to be charged' => '#1 batterie devono essere ricaricate',
|
'#1 batteries are overdue to be charged' => '#1 batterie devono essere ricaricate',
|
||||||
'#1 habits are due to be done within the next #2 days' => '#1 abitudini da eseguire entro #2 giorni',
|
'#1 chores are due to be done within the next #2 days' => '#1 abitudini da eseguire entro #2 giorni',
|
||||||
'#1 habits are overdue to be done' => '#1 abitudini da eseguire',
|
'#1 chores are overdue to be done' => '#1 abitudini da eseguire',
|
||||||
'Released on' => 'Rilasciato il',
|
'Released on' => 'Rilasciato il',
|
||||||
'Consume #3 #1 of #2' => 'Consumati #3 #1 di #2',
|
'Consume #3 #1 of #2' => 'Consumati #3 #1 di #2',
|
||||||
'Added #1 #2 of #3 to stock' => 'Aggiunti #1 #2 di #3',
|
'Added #1 #2 of #3 to stock' => 'Aggiunti #1 #2 di #3',
|
||||||
'Stock amount of #1 is now #2 #3' => 'La quantità in dispensa di #1 è ora #2 #3',
|
'Stock amount of #1 is now #2 #3' => 'La quantità in dispensa di #1 è ora #2 #3',
|
||||||
'Tracked execution of habit #1 on #2' => 'Esecuzione dell\'abitudine #1 registrata il #2',
|
'Tracked execution of chore #1 on #2' => 'Esecuzione dell\'abitudine #1 registrata il #2',
|
||||||
'Tracked charge cylce of battery #1 on #2' => 'Ricarica della batteria #1 effettuata il #2',
|
'Tracked charge cycle of battery #1 on #2' => 'Ricarica della batteria #1 effettuata il #2',
|
||||||
'Consume all #1 which are currently in stock' => 'Consuma tutto #1 in dispensa',
|
'Consume all #1 which are currently in stock' => 'Consuma tutto #1 in dispensa',
|
||||||
'All' => 'Tutto',
|
'All' => 'Tutto',
|
||||||
'Track charge cycle of battery #1' => 'Registra la ricarica della batteria #1',
|
'Track charge cycle of battery #1' => 'Registra la ricarica della batteria #1',
|
||||||
'Track execution of habit #1' => 'Registra l\'esecuzione dell\'abitudine #1',
|
'Track execution of chore #1' => 'Registra l\'esecuzione dell\'abitudine #1',
|
||||||
'Filter by location' => 'Filtra per posizione',
|
'Filter by location' => 'Filtra per posizione',
|
||||||
'Search' => 'Cerca',
|
'Search' => 'Cerca',
|
||||||
'Not logged in' => 'Non autenticato',
|
'Not logged in' => 'Non autenticato',
|
||||||
'You have to select a product' => 'Devi selezionare un prodotto',
|
'You have to select a product' => 'Devi selezionare un prodotto',
|
||||||
'You have to select a habit' => 'Devi selezionare un\'abitudine',
|
'You have to select a chore' => 'Devi selezionare un\'abitudine',
|
||||||
'You have to select a battery' => 'Devi selezionare una batteria',
|
'You have to select a battery' => 'Devi selezionare una batteria',
|
||||||
'A name is required' => 'Inserisci un nome',
|
'A name is required' => 'Inserisci un nome',
|
||||||
'A location is required' => 'Inserisci la posizione',
|
'A location is required' => 'Inserisci la posizione',
|
||||||
|
@@ -2,27 +2,27 @@
|
|||||||
|
|
||||||
return array(
|
return array(
|
||||||
'Stock overview' => 'Husholdning',
|
'Stock overview' => 'Husholdning',
|
||||||
'#1 products expiring within the next #2 days' => '#1 Produkter som går ut på dato innen de neste #2 dagene',
|
'#1 products expiring within the next #2 days' => '#1 Produkt som går ut på dato innen de neste #2 dagene',
|
||||||
'#1 products are already expired' => '#1 Produkt som har gått ut på dato',
|
'#1 products are already expired' => '#1 Produkt som har gått ut på dato',
|
||||||
'#1 products are below defined min. stock amount' => '#1 Produkt under minimum husholdningsnivå',
|
'#1 products are below defined min. stock amount' => '#1 Produkt under minimum husholdningsnivå',
|
||||||
'Product' => 'Produkt',
|
'Product' => 'Produkt',
|
||||||
'Amount' => 'Antall',
|
'Amount' => 'Antall',
|
||||||
'Next best before date' => 'Kommende best før dato',
|
'Next best before date' => 'Kommende best før dato',
|
||||||
'Logout' => 'Logg ut',
|
'Logout' => 'Logg ut',
|
||||||
'Habits overview' => 'Oversikt Husoppgaver',
|
'Chores overview' => 'Oversikt Husarbeid',
|
||||||
'Batteries overview' => 'Oversikt Batteri',
|
'Batteries overview' => 'Oversikt Batteri',
|
||||||
'Purchase' => 'Innkjøp',
|
'Purchase' => 'Innkjøp',
|
||||||
'Consume' => 'Forbrukt',
|
'Consume' => 'Forbruk produkt',
|
||||||
'Inventory' => 'Endre Husholdning',
|
'Inventory' => 'Endre Husholdning',
|
||||||
'Shopping list' => 'Handleliste',
|
'Shopping list' => 'Handleliste',
|
||||||
'Habit tracking' => 'Logge Husoppgaver',
|
'Chore tracking' => 'Logge Husarbeid',
|
||||||
'Battery tracking' => 'Batteri Ladesyklus',
|
'Battery tracking' => 'Batteri Ladesyklus',
|
||||||
'Products' => 'Produkter',
|
'Products' => 'Produkter',
|
||||||
'Locations' => 'Lokasjoner',
|
'Locations' => 'Lokasjoner',
|
||||||
'Quantity units' => 'Forpakning',
|
'Quantity units' => 'Forpakning',
|
||||||
'Habits' => 'Husoppgaver',
|
'Chores' => 'Husarbeid',
|
||||||
'Batteries' => 'Batterier',
|
'Batteries' => 'Batterier',
|
||||||
'Habit' => 'Husoppgave',
|
'Chore' => 'Husarbeid',
|
||||||
'Next estimated tracking' => 'Neste handling',
|
'Next estimated tracking' => 'Neste handling',
|
||||||
'Last tracked' => 'Sist logget',
|
'Last tracked' => 'Sist logget',
|
||||||
'Battery' => 'Batteri',
|
'Battery' => 'Batteri',
|
||||||
@@ -30,7 +30,7 @@ return array(
|
|||||||
'Next planned charge cycle' => 'Neste planlagte ladesyklus',
|
'Next planned charge cycle' => 'Neste planlagte ladesyklus',
|
||||||
'Best before' => 'Best før',
|
'Best before' => 'Best før',
|
||||||
'OK' => 'OK',
|
'OK' => 'OK',
|
||||||
'Product overview' => 'Oversikt Produkt',
|
'Product overview' => 'Produkt oversikt',
|
||||||
'Stock quantity unit' => 'Forpakningstype i husholdningen',
|
'Stock quantity unit' => 'Forpakningstype i husholdningen',
|
||||||
'Stock amount' => 'Husholdning',
|
'Stock amount' => 'Husholdning',
|
||||||
'Last purchased' => 'Sist kjøpt',
|
'Last purchased' => 'Sist kjøpt',
|
||||||
@@ -40,9 +40,9 @@ return array(
|
|||||||
'will be added to the list of barcodes for the selected product on submit' => 'Blir lagt til liste over strekkoder når produkt blir lagt inn.',
|
'will be added to the list of barcodes for the selected product on submit' => 'Blir lagt til liste over strekkoder når produkt blir lagt inn.',
|
||||||
'New amount' => 'Nytt antall',
|
'New amount' => 'Nytt antall',
|
||||||
'Note' => 'Info',
|
'Note' => 'Info',
|
||||||
'Tracked time' => 'Tid logget',
|
'Tracked time' => 'Tid utført/ ladet',
|
||||||
'Habit overview' => 'Oversikt Husoppgave',
|
'Chore overview' => 'Oversikt Husarbeid',
|
||||||
'Tracked count' => 'Logget',
|
'Tracked count' => 'Antall utførelser/ ladninger',
|
||||||
'Battery overview' => 'Batteri Oversikt',
|
'Battery overview' => 'Batteri Oversikt',
|
||||||
'Charge cycles count' => 'Antall ladesykluser',
|
'Charge cycles count' => 'Antall ladesykluser',
|
||||||
'Create shopping list item' => 'Opprett handelisteoppføring',
|
'Create shopping list item' => 'Opprett handelisteoppføring',
|
||||||
@@ -67,12 +67,12 @@ return array(
|
|||||||
'Create location' => 'Opprett lokasjon',
|
'Create location' => 'Opprett lokasjon',
|
||||||
'Create quantity unit' => 'Opprett forpakning',
|
'Create quantity unit' => 'Opprett forpakning',
|
||||||
'Period type' => 'Gjentakelse',
|
'Period type' => 'Gjentakelse',
|
||||||
'Period days' => 'Dager for gjentakelse',
|
'Period days' => 'Antall dager for gjentakelse',
|
||||||
'Create habit' => 'Opprett husoppgave',
|
'Create chore' => 'Opprett husarbeid oppgave',
|
||||||
'Used in' => 'Brukt',
|
'Used in' => 'Brukt',
|
||||||
'Create battery' => 'Opprett batteri',
|
'Create battery' => 'Opprett batteri',
|
||||||
'Edit battery' => 'Endre batteri',
|
'Edit battery' => 'Endre batteri',
|
||||||
'Edit habit' => 'Endre husoppgave',
|
'Edit chore' => 'Endre husarbeid oppgave',
|
||||||
'Edit quantity unit' => 'Endre forpakning',
|
'Edit quantity unit' => 'Endre forpakning',
|
||||||
'Edit product' => 'Endre produkt',
|
'Edit product' => 'Endre produkt',
|
||||||
'Edit location' => 'Endre lokasjon',
|
'Edit location' => 'Endre lokasjon',
|
||||||
@@ -80,7 +80,7 @@ return array(
|
|||||||
'Manage master data' => 'Administrer masterdata',
|
'Manage master data' => 'Administrer masterdata',
|
||||||
'This will apply to added products' => 'Dette vil gjelde for produkt som blir lagt til',
|
'This will apply to added products' => 'Dette vil gjelde for produkt som blir lagt til',
|
||||||
'never' => 'aldri',
|
'never' => 'aldri',
|
||||||
'Add products that are below defined min. stock amount' => 'Legg til produkt som er under definert minimums antall for husholdningen',
|
'Add products that are below defined min. stock amount' => 'Legg til produkt som er under minimumsnivå for husholdningen',
|
||||||
'For purchases this amount of days will be added to today for the best before date suggestion' => 'For innkjøp vil dette antallet dager legges til bestfør forslaget',
|
'For purchases this amount of days will be added to today for the best before date suggestion' => 'For innkjøp vil dette antallet dager legges til bestfør forslaget',
|
||||||
'This means 1 #1 purchased will be converted into #2 #3 in stock' => 'Dette betyr at 1 #1 innkjøp vil bli omgjort til #2 #3 husholdning',
|
'This means 1 #1 purchased will be converted into #2 #3 in stock' => 'Dette betyr at 1 #1 innkjøp vil bli omgjort til #2 #3 husholdning',
|
||||||
'Login' => 'Logg inn',
|
'Login' => 'Logg inn',
|
||||||
@@ -90,7 +90,7 @@ return array(
|
|||||||
'Are you sure to delete battery "#1"?' => 'Er du sikker du ønsker å slette Batteri "#1"?',
|
'Are you sure to delete battery "#1"?' => 'Er du sikker du ønsker å slette Batteri "#1"?',
|
||||||
'Yes' => 'Ja',
|
'Yes' => 'Ja',
|
||||||
'No' => 'Nei',
|
'No' => 'Nei',
|
||||||
'Are you sure to delete habit "#1"?' => 'Er du sikker på du ønsker å slette husoppgave "#1"?',
|
'Are you sure to delete chore "#1"?' => 'Er du sikker på du ønsker å slette husarbeid oppgave "#1"?',
|
||||||
'"#1" could not be resolved to a product, how do you want to proceed?' => '"#1" kunne ikke bli tildelt et produkt, hvordan ønsker du å fortsette?',
|
'"#1" could not be resolved to a product, how do you want to proceed?' => '"#1" kunne ikke bli tildelt et produkt, hvordan ønsker du å fortsette?',
|
||||||
'Create or assign product' => 'Opprett eller tildel til produkt',
|
'Create or assign product' => 'Opprett eller tildel til produkt',
|
||||||
'Cancel' => 'Avbryt',
|
'Cancel' => 'Avbryt',
|
||||||
@@ -110,31 +110,31 @@ return array(
|
|||||||
'This product is not in stock' => 'Dette produktet er ikke i husholdningen',
|
'This product is not in stock' => 'Dette produktet er ikke i husholdningen',
|
||||||
'This means #1 will be added to stock' => 'Dette betyr at #1 vil bli lagt til i husholdningen',
|
'This means #1 will be added to stock' => 'Dette betyr at #1 vil bli lagt til i husholdningen',
|
||||||
'This means #1 will be removed from stock' => 'Dette betyr at #1 vil bli fjernet fra husholdningen',
|
'This means #1 will be removed from stock' => 'Dette betyr at #1 vil bli fjernet fra husholdningen',
|
||||||
'This means it is estimated that a new execution of this habit is tracked #1 days after the last was tracked' => 'Dette betyr at det er estimert at den nye utførelsen av denne husoppgaven er logget #1 dag etter den sist var logget',
|
'This means it is estimated that a new execution of this chore is tracked #1 days after the last was tracked' => 'Dette betyr at det er estimert at den nye utførelsen av denne husarbeid oppgaven er logget #1 dag etter den sist var logget',
|
||||||
'Removed #1 #2 of #3 from stock' => 'Fjernet #1 #2 av #3 fra husholdningen',
|
'Removed #1 #2 of #3 from stock' => 'Fjernet #1 #2 #3 fra husholdningen',
|
||||||
'About grocy' => 'Om Grocy',
|
'About grocy' => 'Om Grocy',
|
||||||
'Close' => 'Lukk',
|
'Close' => 'Lukk',
|
||||||
'#1 batteries are due to be charged within the next #2 days' => '#1 Batteri må lades innen de #2 neste dagene',
|
'#1 batteries are due to be charged within the next #2 days' => '#1 Batteri må lades innen de #2 neste dagene',
|
||||||
'#1 batteries are overdue to be charged' => '#1 Batteri har gått over fristen for å bli ladet opp',
|
'#1 batteries are overdue to be charged' => '#1 Batteri har gått over fristen for å bli ladet opp',
|
||||||
'#1 habits are due to be done within the next #2 days' => '#1 husoppgaver skal gjøres inne de #2 neste dagene',
|
'#1 chores are due to be done within the next #2 days' => '#1 husarbeid(s) oppgave(r) skal gjøres inne de #2 neste dagene',
|
||||||
'#1 habits are overdue to be done' => '#1 husoppgaver har gått over fristen for utførelse',
|
'#1 chores are overdue to be done' => '#1 husarbeid(s) oppgave(r) har gått over fristen for utførelse',
|
||||||
'Released on' => 'Utgitt',
|
'Released on' => 'Utgitt',
|
||||||
'Consume #3 #1 of #2' => 'Forbruk #3 #1 #2',
|
'Consume #3 #1 of #2' => 'Forbruk #3 #1 #2',
|
||||||
'Added #1 #2 of #3 to stock' => '#1 #2 #3 lagt til i husholdningen',
|
'Added #1 #2 of #3 to stock' => '#1 #2 #3 lagt til i husholdningen',
|
||||||
'Stock amount of #1 is now #2 #3' => 'Husholdning antall #1 er nå #2 #3',
|
'Stock amount of #1 is now #2 #3' => 'Husholdning antall #1 er nå #2 #3',
|
||||||
'Tracked execution of habit #1 on #2' => 'Logget utførelse av husoppgave "#1" den #2',
|
'Tracked execution of chore #1 on #2' => 'Utførte husarbeid oppgave "#1" den #2',
|
||||||
'Tracked charge cylce of battery #1 on #2' => 'Logget ladesyklus for batteri #1 og #2',
|
'Tracked charge cycle of battery #1 on #2' => 'Ladet #1 den #2',
|
||||||
'Consume all #1 which are currently in stock' => 'Konsumér alle #1 som er i husholdningen',
|
'Consume all #1 which are currently in stock' => 'Forbruk alle #1 som er i husholdningen',
|
||||||
'All' => 'Alle',
|
'All' => 'Alle',
|
||||||
'Track charge cycle of battery #1' => 'Logg ladesyklus for batteri #1',
|
'Track charge cycle of battery #1' => '#1 ladet',
|
||||||
'Track execution of habit #1' => 'Logg utførelse av husoppgave #1',
|
'Track execution of chore #1' => 'Utfør husarbeid oppgave #1',
|
||||||
'Filter by location' => 'Filtrér etter lokasjon',
|
'Filter by location' => 'Filtrér etter lokasjon',
|
||||||
'Search' => 'Søk',
|
'Search' => 'Søk',
|
||||||
'Not logged in' => 'Ikke logget inn',
|
'Not logged in' => 'Ikke logget inn',
|
||||||
'You have to select a product' => 'Du må velge et produkt',
|
'You have to select a product' => 'Du må velge et produkt',
|
||||||
'You have to select a habit' => 'Du må velge en husoppgaven',
|
'You have to select a chore' => 'Du må velge en husarbeid oppgave',
|
||||||
'You have to select a battery' => 'Du må velge et batteri',
|
'You have to select a battery' => 'Du må velge et batteri',
|
||||||
'A name is required' => 'Et navn kreves',
|
'A name is required' => 'Vennligst fyll inn et navn',
|
||||||
'A location is required' => 'En lokasjon kreves',
|
'A location is required' => 'En lokasjon kreves',
|
||||||
'The amount cannot be lower than #1' => 'Antallet kan ikke være lavere enn #1',
|
'The amount cannot be lower than #1' => 'Antallet kan ikke være lavere enn #1',
|
||||||
'This cannot be negative' => 'Dette kan ikke være negativt',
|
'This cannot be negative' => 'Dette kan ikke være negativt',
|
||||||
@@ -154,20 +154,20 @@ return array(
|
|||||||
'Are you sure to delete recipe ingredient "#1"?' => 'Er du sikker du ønsker å slette ingrediens "#1" fra oppskriften?',
|
'Are you sure to delete recipe ingredient "#1"?' => 'Er du sikker du ønsker å slette ingrediens "#1" fra oppskriften?',
|
||||||
'Are you sure to empty the shopping list?' => 'Er du sikker du ønsker å slette handlelisten?',
|
'Are you sure to empty the shopping list?' => 'Er du sikker du ønsker å slette handlelisten?',
|
||||||
'Clear list' => 'Tøm liste',
|
'Clear list' => 'Tøm liste',
|
||||||
'Requirements fulfilled' => 'Krav oppfylt',
|
'Requirements fulfilled' => 'Har jeg alt jeg trenger for denne oppskriften?',
|
||||||
'Put missing products on shopping list' => 'Legg manglende produkter til handlelisten',
|
'Put missing products on shopping list' => 'Legg manglende produkter til handlelisten',
|
||||||
'Not enough in stock, #1 ingredients missing' => 'Ikke nok i husholdningen, #1 ingredienser mangler',
|
'Not enough in stock, #1 ingredients missing' => 'Ikke nok i husholdningen, #1 ingredienser mangler',
|
||||||
'Enough in stock' => 'Nok i husholdningen',
|
'Enough in stock' => 'Nok i husholdningen',
|
||||||
'Not enough in stock, #1 ingredients missing but already on the shopping list' => 'Ikke nok i husholdningen, #1 ingrediens mangler, men står allerede på handelisten',
|
'Not enough in stock, #1 ingredients missing but already on the shopping list' => 'Ikke nok i husholdningen, #1 ingrediens mangler, men denne er på handelisten',
|
||||||
'Expand to fullscreen' => 'Full skjerm',
|
'Expand to fullscreen' => 'Full skjerm',
|
||||||
'Ingredients' => 'Ingredienser',
|
'Ingredients' => 'Ingredienser',
|
||||||
'Preparation' => 'Forberedelse / Slik gjør du',
|
'Preparation' => 'Forberedelse / Slik gjør du',
|
||||||
'Recipe' => 'Oppskrift',
|
'Recipe' => 'Oppskrift',
|
||||||
'Not enough in stock, #1 missing, #2 already on shopping list' => 'Ikke nok i husholdningen, #1 mangler, #2 allerede i handlisten',
|
'Not enough in stock, #1 missing, #2 already on shopping list' => 'Ikke nok i husholdningen, mangler #1, er #2 på handlelisten',
|
||||||
'Show notes' => 'Vis notater',
|
'Show notes' => 'Vis notater',
|
||||||
'Put missing amount on shopping list' => 'Legg manglende til handlelisten',
|
'Put missing amount on shopping list' => 'Legg manglende til handlelisten',
|
||||||
'Are you sure to put all missing ingredients for recipe "#1" on the shopping list?' => 'Er du sikker du ønsker å legge alle manglende ingredienser til oppskrift "#1"?',
|
'Are you sure to put all missing ingredients for recipe "#1" on the shopping list?' => 'Er du sikker du ønsker å legge alle manglende ingredienser til oppskrift "#1"?',
|
||||||
'Added for recipe #1' => 'Lagt til oppskrift #1',
|
'Added for recipe #1' => 'Lagt til fra oppskrift "#1"',
|
||||||
'Manage users' => 'Administrer brukere',
|
'Manage users' => 'Administrer brukere',
|
||||||
'User' => 'Bruker',
|
'User' => 'Bruker',
|
||||||
'Users' => 'Brukere',
|
'Users' => 'Brukere',
|
||||||
@@ -183,8 +183,8 @@ return array(
|
|||||||
'Done by' => 'Utført av',
|
'Done by' => 'Utført av',
|
||||||
'Last done by' => 'Sist utført av',
|
'Last done by' => 'Sist utført av',
|
||||||
'Unknown' => 'Ukjent',
|
'Unknown' => 'Ukjent',
|
||||||
'Filter by habit' => 'Filtrér husoppave',
|
'Filter by chore' => 'Filtrér husarbeid',
|
||||||
'Habits analysis' => 'Statistikk husoppgaver',
|
'Chores analysis' => 'Statistikk husarbeid',
|
||||||
'0 means suggestions for the next charge cycle are disabled' => '0 betyr neste ladesyklus er avslått',
|
'0 means suggestions for the next charge cycle are disabled' => '0 betyr neste ladesyklus er avslått',
|
||||||
'Charge cycle interval (days)' => 'Ladesyklysintervall (Dager)',
|
'Charge cycle interval (days)' => 'Ladesyklysintervall (Dager)',
|
||||||
'Last price' => 'Siste pris',
|
'Last price' => 'Siste pris',
|
||||||
@@ -198,8 +198,8 @@ return array(
|
|||||||
'#1 product is below defined min. stock amount' => '#1 Produkt er under minimums husholdningsnivå',
|
'#1 product is below defined min. stock amount' => '#1 Produkt er under minimums husholdningsnivå',
|
||||||
'Unit' => 'Enhet',
|
'Unit' => 'Enhet',
|
||||||
'Units' => 'Enheter',
|
'Units' => 'Enheter',
|
||||||
'#1 habit is due to be done within the next #2 days' => '#1 husoppgave skal gjøres inne de #2 neste dagene',
|
'#1 chore is due to be done within the next #2 days' => '#1 husarbeid oppgave(r) skal gjøres inne de #2 neste dagene',
|
||||||
'#1 habit is overdue to be done' => '#1 husoppgave har gått over fristen for utførelse',
|
'#1 chore is overdue to be done' => '#1 husarbeid(s) oppgave(r) har gått over fristen for utførelse',
|
||||||
'#1 battery is due to be charged within the next #2 days' => '#1 Batteri må lades innen #2 dager',
|
'#1 battery is due to be charged within the next #2 days' => '#1 Batteri må lades innen #2 dager',
|
||||||
'#1 battery is overdue to be charged' => '#1 Batteri har gått over fristen for å lades',
|
'#1 battery is overdue to be charged' => '#1 Batteri har gått over fristen for å lades',
|
||||||
'#1 unit was automatically added and will apply in addition to the amount entered here' => '#1 enhet ble automatisk lagt til i tillegg til hva som blir skrevet inn her',
|
'#1 unit was automatically added and will apply in addition to the amount entered here' => '#1 enhet ble automatisk lagt til i tillegg til hva som blir skrevet inn her',
|
||||||
@@ -207,11 +207,52 @@ return array(
|
|||||||
'in plural form' => 'I flertall',
|
'in plural form' => 'I flertall',
|
||||||
'Never expires' => 'Går ikke ut på dato',
|
'Never expires' => 'Går ikke ut på dato',
|
||||||
'This cannot be lower than #1' => 'Dette kan ikke være lavere enn #1',
|
'This cannot be lower than #1' => 'Dette kan ikke være lavere enn #1',
|
||||||
'-1 means that this product never expires' => '-1 Betyr at dette produktet aldru går ut på dato',
|
'-1 means that this product never expires' => '-1 Betyr at dette produktet aldri går ut på dato',
|
||||||
|
'Quantity unit' => 'Forpakning',
|
||||||
|
'Only check if a single unit is in stock (a different quantity can then be used above)' => 'Huk av hvis du ønsker å bruke mindre enn forpakningsstørrelse i husholdningen',
|
||||||
|
'Are you sure to consume all ingredients needed by recipe "#1" (ingredients marked with "check only if a single unit is in stock" will be ignored)?' => 'Er du sikker du ønsker å forbruke alle ingredienser for "#1" oppskriften? (Ingredienser merket med "bruke mindre enn forpakningsstørrelse i husholdningen" blir ignorert',
|
||||||
|
'Removed all ingredients of recipe "#1" from stock' => 'Fjern alle ingredienser for "#1" oppskriften fra husholdningen.',
|
||||||
|
'Consume all ingredients needed by this recipe' => 'Forbruk alle ingredienser for denne oppskriften',
|
||||||
|
'Click to show technical details' => 'Klikk for å vise teknisk informasjon',
|
||||||
|
'Error while saving, probably this item already exists' => 'Kunne ikke lagre, produkt er lagt til fra før',
|
||||||
|
'Error details' => 'Detaljer om feil',
|
||||||
|
'Tasks' => 'Oppgaver',
|
||||||
|
'Show done tasks' => 'Vis ferdige oppgaver',
|
||||||
|
'Task' => 'Oppgave',
|
||||||
|
'Due' => 'Forfall',
|
||||||
|
'Assigned to' => 'Tildelt',
|
||||||
|
'Mark task "#1" as completed' => 'Merk oppgave "#1" som ferdig',
|
||||||
|
'Uncategorized' => 'Mangler kategori',
|
||||||
|
'Task categories' => 'Oppgave kategorier',
|
||||||
|
'Create task' => 'Opprett en oppgave',
|
||||||
|
'A due date is required' => 'En forfallsdato kreves',
|
||||||
|
'Category' => 'Kategori',
|
||||||
|
'Edit task' => 'Endre oppgave',
|
||||||
|
'Are you sure to delete task "#1"?' => 'Er du sikker du ønsker slette oppgave "#1"?',
|
||||||
|
'#1 task is due to be done within the next #2 days' => '#1 oppgave har utførelse forfall innen de neste #2 dagene',
|
||||||
|
'#1 tasks are due to be done within the next #2 days' => '#1 oppgaver har utførelse forfall innen de neste #2 dagene',
|
||||||
|
'#1 task is overdue to be done' => '#1 oppgave har forfalt utførelse dato',
|
||||||
|
'#1 tasks are overdue to be done' => '#1 oppgaver har forfalt utførelse dato',
|
||||||
|
'Edit task category' => 'Endre oppgave kategori',
|
||||||
|
'Create task category' => 'Opprett oppgave kategori',
|
||||||
|
'Product groups' => 'Produktgrupper',
|
||||||
|
'Ungrouped' => 'Ikke i grupper',
|
||||||
|
'Create product group' => 'Opprett produkt gruppe',
|
||||||
|
'Edit product group' => 'Endre produkt gruppe',
|
||||||
|
'Product group' => 'Produktgruppe',
|
||||||
|
'Are you sure to delete product group "#1"?' => 'Er du sikker du ønsker å slette produktgruppe "#1"?',
|
||||||
|
'Stay logged in permanently' => 'Alltid være innlogget',
|
||||||
|
'When not set, you will get logged out at latest after 30 days' => 'Når den ikke er satt vil du bli logget ut etter 30 dager',
|
||||||
|
'Filter by status' => 'Filtrér etter status',
|
||||||
|
'Below min. stock amount' => 'Under under minimum husholdningsnivå',
|
||||||
|
'Expiring soon' => 'Går snart ut på dato',
|
||||||
|
'Already expired' => 'Utgått på dato',
|
||||||
|
'Due soon' => 'Forfaller snart',
|
||||||
|
'Overdue' => 'Forfalt',
|
||||||
|
|
||||||
//Constants
|
//Constants
|
||||||
'manually' => 'Manuel',
|
'manually' => 'Manuel',
|
||||||
'dynamic-regular' => 'Automatisk (rullering settes under)',
|
'dynamic-regular' => 'Automatisk',
|
||||||
|
|
||||||
//Technical component translations
|
//Technical component translations
|
||||||
'timeago_locale' => 'no',
|
'timeago_locale' => 'no',
|
||||||
@@ -233,9 +274,9 @@ return array(
|
|||||||
'Glass' => 'Glass',
|
'Glass' => 'Glass',
|
||||||
'Glasses' => 'Glass',
|
'Glasses' => 'Glass',
|
||||||
'Tin' => 'Hermetikkboks',
|
'Tin' => 'Hermetikkboks',
|
||||||
'Tin' => 'Hermetikkbokser',
|
'Tins' => 'Hermetikkbokser',
|
||||||
'Can' => 'Boks',
|
'Can' => 'Boks',
|
||||||
'Cans' => 'Boker',
|
'Cans' => 'Bokser',
|
||||||
'Bunch' => 'Klase',
|
'Bunch' => 'Klase',
|
||||||
'Bunches' => 'Klaser',
|
'Bunches' => 'Klaser',
|
||||||
'Gummy bears' => 'Vingummibjørner',
|
'Gummy bears' => 'Vingummibjørner',
|
||||||
@@ -257,7 +298,7 @@ return array(
|
|||||||
'TV remote control' => 'Fjernkontroll for TV',
|
'TV remote control' => 'Fjernkontroll for TV',
|
||||||
'Alarm clock' => 'Alarmklokke',
|
'Alarm clock' => 'Alarmklokke',
|
||||||
'Heat remote control' => 'Fjernkontroll for termostat',
|
'Heat remote control' => 'Fjernkontroll for termostat',
|
||||||
'Lawn mowed in the garden' => 'Kuttet gresse i hagen',
|
'Lawn mowed in the garden' => 'Kuttet gresset i hagen',
|
||||||
'Some good snacks' => 'Noen gode snacks',
|
'Some good snacks' => 'Noen gode snacks',
|
||||||
'Pizza dough' => 'Pizzadeig',
|
'Pizza dough' => 'Pizzadeig',
|
||||||
'Sieved tomatoes' => 'Tomatpuré',
|
'Sieved tomatoes' => 'Tomatpuré',
|
||||||
@@ -272,5 +313,22 @@ return array(
|
|||||||
'Italian' => 'Italiensk',
|
'Italian' => 'Italiensk',
|
||||||
'Demo in different language' => 'Demo i annet språk',
|
'Demo in different language' => 'Demo i annet språk',
|
||||||
'This is the note content of the recipe ingredient' => 'Dette er notisen for ingrediensen i oppskriften',
|
'This is the note content of the recipe ingredient' => 'Dette er notisen for ingrediensen i oppskriften',
|
||||||
'Demo User' => 'Demo Bruker'
|
'Demo User' => 'Demo Bruker',
|
||||||
|
'Gram' => 'Gram',
|
||||||
|
'Grams' => 'Gram',
|
||||||
|
'Flour' => 'Mel',
|
||||||
|
'Pancakes' => 'Pannekaker',
|
||||||
|
'Sugar' => 'Sukker',
|
||||||
|
'Home' => 'Hus',
|
||||||
|
'Life' => 'Livstil',
|
||||||
|
'Projects' => 'Projekter',
|
||||||
|
'Repair the garage door' => 'Reparere garasjedøren',
|
||||||
|
'Fork and improve grocy' => 'Fork og forbedre grocy',
|
||||||
|
'Find a solution for what to do when I forget the door keys' => 'Finne på løsning for hva jeg skal gjøre når jeg mister dørnøklene',
|
||||||
|
'Sweets' => 'Godteri',
|
||||||
|
'Bakery products' => 'Produkt fra bakeren ',
|
||||||
|
'Tinned food' => 'Boksemat',
|
||||||
|
'Butchery products' => 'Produkt fra slakteren',
|
||||||
|
'Vegetables/Fruits' => 'Frukt/ Grønnsaker',
|
||||||
|
'Refrigerated products' => 'Kjølte produkter'
|
||||||
);
|
);
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
ALTER TABLE habits_log
|
ALTER TABLE habits_log
|
||||||
ADD done_by_user_id;
|
ADD done_by_user_id INTEGER;
|
||||||
|
|
||||||
DROP TABLE api_keys;
|
DROP TABLE api_keys;
|
||||||
|
|
||||||
|
31
migrations/0035.sql
Normal file
31
migrations/0035.sql
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
ALTER TABLE habits RENAME TO chores;
|
||||||
|
|
||||||
|
CREATE TABLE chores_log (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
chore_id INTEGER NOT NULL,
|
||||||
|
tracked_time DATETIME,
|
||||||
|
done_by_user_id INTEGER,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO chores_log
|
||||||
|
(chore_id, tracked_time, done_by_user_id, row_created_timestamp)
|
||||||
|
SELECT habit_id, tracked_time, done_by_user_id, row_created_timestamp
|
||||||
|
FROM habits_log;
|
||||||
|
|
||||||
|
DROP TABLE habits_log;
|
||||||
|
|
||||||
|
DROP VIEW habits_current;
|
||||||
|
CREATE VIEW chores_current
|
||||||
|
AS
|
||||||
|
SELECT
|
||||||
|
h.id AS chore_id,
|
||||||
|
MAX(l.tracked_time) AS last_tracked_time,
|
||||||
|
CASE h.period_type
|
||||||
|
WHEN 'manually' THEN '2999-12-31 23:59:59'
|
||||||
|
WHEN 'dynamic-regular' THEN datetime(MAX(l.tracked_time), '+' || CAST(h.period_days AS TEXT) || ' day')
|
||||||
|
END AS next_estimated_execution_time
|
||||||
|
FROM chores h
|
||||||
|
LEFT JOIN chores_log l
|
||||||
|
ON h.id = l.chore_id
|
||||||
|
GROUP BY h.id, h.period_days;
|
24
migrations/0036.sql
Normal file
24
migrations/0036.sql
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
CREATE TABLE tasks (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
description TEXT,
|
||||||
|
due_date DATETIME,
|
||||||
|
done TINYINT NOT NULL DEFAULT 0 CHECK(done IN (0, 1)),
|
||||||
|
done_timestamp DATETIME,
|
||||||
|
category_id INTEGER,
|
||||||
|
assigned_to_user_id INTEGER,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE task_categories (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
description TEXT,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE VIEW tasks_current
|
||||||
|
AS
|
||||||
|
SELECT *
|
||||||
|
FROM tasks
|
||||||
|
WHERE done = 0;
|
9
migrations/0037.sql
Normal file
9
migrations/0037.sql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
ALTER TABLE products
|
||||||
|
ADD product_group_id INTEGER;
|
||||||
|
|
||||||
|
CREATE TABLE product_groups (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
description TEXT,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
);
|
27
migrations/0038.sql
Normal file
27
migrations/0038.sql
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
DROP VIEW stock_missing_products;
|
||||||
|
CREATE VIEW stock_missing_products
|
||||||
|
AS
|
||||||
|
SELECT
|
||||||
|
p.id,
|
||||||
|
MAX(p.name) AS name,
|
||||||
|
p.min_stock_amount - IFNULL(SUM(s.amount), 0) AS amount_missing,
|
||||||
|
CASE WHEN s.id IS NOT NULL THEN 1 ELSE 0 END AS is_partly_in_stock
|
||||||
|
FROM products p
|
||||||
|
LEFT JOIN stock s
|
||||||
|
ON p.id = s.product_id
|
||||||
|
WHERE p.min_stock_amount != 0
|
||||||
|
GROUP BY p.id
|
||||||
|
HAVING IFNULL(SUM(s.amount), 0) < p.min_stock_amount;
|
||||||
|
|
||||||
|
DROP VIEW stock_current;
|
||||||
|
CREATE VIEW stock_current
|
||||||
|
AS
|
||||||
|
SELECT product_id, SUM(amount) AS amount, MIN(best_before_date) AS best_before_date
|
||||||
|
FROM stock
|
||||||
|
GROUP BY product_id
|
||||||
|
|
||||||
|
UNION
|
||||||
|
|
||||||
|
SELECT id, 0, null
|
||||||
|
FROM stock_missing_products
|
||||||
|
WHERE is_partly_in_stock = 0;
|
10
migrations/0039.sql
Normal file
10
migrations/0039.sql
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
CREATE TABLE user_settings (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
key TEXT NOT NULL,
|
||||||
|
value TEXT,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime')),
|
||||||
|
row_updated_timestamp DATETIME DEFAULT (datetime('now', 'localtime')),
|
||||||
|
|
||||||
|
UNIQUE(user_id, key)
|
||||||
|
);
|
@@ -2,7 +2,7 @@
|
|||||||
"name": "grocy",
|
"name": "grocy",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@danielfarrell/bootstrap-combobox": "https://github.com/pallidus-fintech/bootstrap-combobox.git#enhance/boostrap_4",
|
"@danielfarrell/bootstrap-combobox": "https://github.com/berrnd/bootstrap-combobox.git#master",
|
||||||
"@fortawesome/fontawesome-free": "^5.1.0",
|
"@fortawesome/fontawesome-free": "^5.1.0",
|
||||||
"TagManager": "https://github.com/max-favilli/tagmanager.git#3.0.2",
|
"TagManager": "https://github.com/max-favilli/tagmanager.git#3.0.2",
|
||||||
"bootbox": "https://github.com/makeusabrew/bootbox.git#v5.x",
|
"bootbox": "https://github.com/makeusabrew/bootbox.git#v5.x",
|
||||||
@@ -14,6 +14,8 @@
|
|||||||
"datatables.net-colreorder-bs4": "^1.5.1",
|
"datatables.net-colreorder-bs4": "^1.5.1",
|
||||||
"datatables.net-responsive": "^2.2.3",
|
"datatables.net-responsive": "^2.2.3",
|
||||||
"datatables.net-responsive-bs4": "^2.2.3",
|
"datatables.net-responsive-bs4": "^2.2.3",
|
||||||
|
"datatables.net-rowgroup": "^1.0.4",
|
||||||
|
"datatables.net-rowgroup-bs4": "^1.0.4",
|
||||||
"datatables.net-select": "^1.2.7",
|
"datatables.net-select": "^1.2.7",
|
||||||
"datatables.net-select-bs4": "^1.2.7",
|
"datatables.net-select-bs4": "^1.2.7",
|
||||||
"jquery": "^3.3.1",
|
"jquery": "^3.3.1",
|
||||||
|
@@ -11,10 +11,6 @@ body {
|
|||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-real-button {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeago-contextual {
|
.timeago-contextual {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
@@ -60,6 +56,7 @@ a.discrete-link:focus {
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-check-input.is-valid ~ .form-check-label,
|
.form-check-input.is-valid ~ .form-check-label,
|
||||||
@@ -67,6 +64,23 @@ a.discrete-link:focus {
|
|||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-strike-through {
|
||||||
|
text-decoration: line-through;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide the default up/down arrow buttons for number inputs because we use our own buttons in numberpicker */
|
||||||
|
input[type='number'] {
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
|
input::-webkit-outer-spin-button,
|
||||||
|
input::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* Navigation style customizations */
|
/* Navigation style customizations */
|
||||||
#mainNav {
|
#mainNav {
|
||||||
background-color: #e5e5e5 !important;
|
background-color: #e5e5e5 !important;
|
||||||
@@ -183,3 +197,8 @@ td {
|
|||||||
.typeahead .active {
|
.typeahead .active {
|
||||||
background-color: #e5e5e5;
|
background-color: #e5e5e5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Third party component customizations - Popper.js */
|
||||||
|
.tooltip {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
199
public/css/grocy_night_mode.css
Normal file
199
public/css/grocy_night_mode.css
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
body.night-mode {
|
||||||
|
color: #c1c1c1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .table-info,
|
||||||
|
.night-mode .table-info > td,
|
||||||
|
.night-mode .table-info > th {
|
||||||
|
background-color: #07373f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .btn,
|
||||||
|
.night-mode .nav-link,
|
||||||
|
.night-mode #mainNav.navbar-light .navbar-collapse .navbar-nav > .nav-item.dropdown > .nav-link::after,
|
||||||
|
.night-mode .dropdown-item {
|
||||||
|
color: #c1c1c1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .btn-outline-dark {
|
||||||
|
border-color: #c1c1c1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .btn-info {
|
||||||
|
color: #c1c1c1;
|
||||||
|
background-color: #07373f;
|
||||||
|
border-color: #07373f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .btn-warning {
|
||||||
|
color: #c1c1c1;
|
||||||
|
background-color: #473604;
|
||||||
|
border-color: #473604;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .btn-danger {
|
||||||
|
color: #c1c1c1;
|
||||||
|
background-color: #471116;
|
||||||
|
border-color: #471116;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .btn-success {
|
||||||
|
color: #c1c1c1;
|
||||||
|
background-color: #0d3a18;
|
||||||
|
border-color: #0d3a18;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .form-control {
|
||||||
|
color: #495057;
|
||||||
|
background-color: #333131;
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .content-wrapper {
|
||||||
|
background: #333131;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode table.dataTable tr.group td {
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: #333131;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .table-danger,
|
||||||
|
.night-mode .table-danger > td,
|
||||||
|
.night-mode .table-danger > th {
|
||||||
|
background-color: #471116;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .table-warning,
|
||||||
|
.night-mode .table-warning > td,
|
||||||
|
.night-mode .table-warning > th {
|
||||||
|
background-color: #473604;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .bg-warning {
|
||||||
|
background-color: #473604!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .bg-info {
|
||||||
|
background-color: #07373f!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .bg-danger {
|
||||||
|
background-color: #471116!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .form-control:focus {
|
||||||
|
color: #495057;
|
||||||
|
background-color: #333131;
|
||||||
|
border-color: #80bdff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .dropdown-item:focus,
|
||||||
|
.night-mode .dropdown-item:hover {
|
||||||
|
color: #16181b;
|
||||||
|
background-color: #333131;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .dropdown-item {
|
||||||
|
color: #7c7b6f;
|
||||||
|
background-color: #333131;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .list-group-item {
|
||||||
|
background-color: #333131;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .modal-content {
|
||||||
|
background-color: #1a1919;
|
||||||
|
border: 1px solid rgba(186, 189, 189, 0.66);
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .modal-footer {
|
||||||
|
border-top: 1px solid #6f7173;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .container-fluid {
|
||||||
|
background-color: #333131;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode a.discrete-link:hover {
|
||||||
|
color: #16354f !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
background-color: #333131;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode a.discrete-link:focus {
|
||||||
|
color: #3a0b0f !important;
|
||||||
|
background-color: #333131;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .card {
|
||||||
|
border: 2px solid;
|
||||||
|
border-color: #383838;
|
||||||
|
background-color: #333131;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .card-header {
|
||||||
|
background-color: #333131;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode #mainNav {
|
||||||
|
background-color: #333131 !important;
|
||||||
|
border-bottom: 2px solid !important;
|
||||||
|
border-color: #383838 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .navbar-sidenav,
|
||||||
|
.night-mode .sidenav-second-level {
|
||||||
|
background-color: #333131 !important;
|
||||||
|
border-right: 2px solid !important;
|
||||||
|
border-color: #383838 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .navbar-nav .dropdown-menu {
|
||||||
|
background-color: #333131 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .navbar-nav .dropdown-divider {
|
||||||
|
border-color: #383838 !important;
|
||||||
|
background-color: #333131;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .sidenav-toggler {
|
||||||
|
background-color: #383838 !important;
|
||||||
|
border-right: 2px solid !important;
|
||||||
|
border-color: #383838 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .navbar-sidenav > li:hover,
|
||||||
|
.night-mode .sidenav-second-level > li:hover,
|
||||||
|
.night-mode .navbar-nav .dropdown-item:hover {
|
||||||
|
box-shadow: inset 5px 0 0 #112a3f !important;
|
||||||
|
background-color: #383838 !important;
|
||||||
|
color: #c1c1c1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .navbar-sidenav > li > a:focus,
|
||||||
|
.night-mode .sidenav-second-level > li > a:focus,
|
||||||
|
.night-mode .navbar-nav .dropdown-item:focus {
|
||||||
|
box-shadow: inset 5px 0 0 #350a0f !important;
|
||||||
|
background-color: #383838 !important;
|
||||||
|
color: #c1c1c1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .active-page {
|
||||||
|
box-shadow: inset 5px 0 0 #350a0f !important;
|
||||||
|
background-color: #383838 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .toast-success {
|
||||||
|
background-color: #092810;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .toast-error {
|
||||||
|
background-color: #471015;
|
||||||
|
}
|
||||||
|
|
||||||
|
.night-mode .typeahead .active {
|
||||||
|
background-color: #333131;
|
||||||
|
}
|
@@ -31,3 +31,26 @@ GetUriParam = function(key)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
IsTouchInputDevice = function()
|
||||||
|
{
|
||||||
|
if (("ontouchstart" in window) || window.DocumentTouch && document instanceof DocumentTouch)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BoolVal = function(test)
|
||||||
|
{
|
||||||
|
var anything = test.toString().toLowerCase();
|
||||||
|
if (anything === true || anything === "true" || anything === "1" || anything === "on")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -3,6 +3,22 @@
|
|||||||
var localizedText = Grocy.LocalizationStrings[text];
|
var localizedText = Grocy.LocalizationStrings[text];
|
||||||
if (localizedText === undefined)
|
if (localizedText === undefined)
|
||||||
{
|
{
|
||||||
|
if (Grocy.Mode === 'dev')
|
||||||
|
{
|
||||||
|
jsonData = {};
|
||||||
|
jsonData.text = text;
|
||||||
|
Grocy.Api.Post('system/log-missing-localization', jsonData,
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
// Nothing to do...
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
localizedText = text;
|
localizedText = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +107,14 @@ window.FontAwesomeConfig = {
|
|||||||
searchPseudoElements: true
|
searchPseudoElements: true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't show tooltips on touch input devices
|
||||||
|
if (IsTouchInputDevice())
|
||||||
|
{
|
||||||
|
var css = document.createElement("style");
|
||||||
|
css.innerHTML = ".tooltip { display: none; }";
|
||||||
|
document.body.appendChild(css);
|
||||||
|
}
|
||||||
|
|
||||||
Grocy.Api = { };
|
Grocy.Api = { };
|
||||||
Grocy.Api.Get = function(apiFunction, success, error)
|
Grocy.Api.Get = function(apiFunction, success, error)
|
||||||
{
|
{
|
||||||
@@ -168,3 +192,59 @@ Grocy.FrontendHelpers.ValidateForm = function(formId)
|
|||||||
|
|
||||||
$(form).addClass('was-validated');
|
$(form).addClass('was-validated');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Grocy.FrontendHelpers.ShowGenericError = function(message, exception)
|
||||||
|
{
|
||||||
|
toastr.error(L(message) + '<br><br>' + L('Click to show technical details'), '', {
|
||||||
|
onclick: function()
|
||||||
|
{
|
||||||
|
bootbox.alert({
|
||||||
|
title: L('Error details'),
|
||||||
|
message: JSON.stringify(exception, null, 4)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.error(exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
$("form").on("keyup paste", "input, textarea", function()
|
||||||
|
{
|
||||||
|
$(this).closest("form").addClass("is-dirty");
|
||||||
|
});
|
||||||
|
$("form").on("click", "select", function()
|
||||||
|
{
|
||||||
|
$(this).closest("form").addClass("is-dirty");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Auto saving user setting controls
|
||||||
|
$(".user-setting-control").on("change", function()
|
||||||
|
{
|
||||||
|
var element = $(this);
|
||||||
|
var inputType = element.attr("type").toLowerCase();
|
||||||
|
var settingKey = element.attr("data-setting-key");
|
||||||
|
|
||||||
|
if (inputType === "checkbox")
|
||||||
|
{
|
||||||
|
value = element.is(":checked");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var value = element.val();
|
||||||
|
}
|
||||||
|
|
||||||
|
Grocy.UserSettings[settingKey] = value;
|
||||||
|
|
||||||
|
jsonData = { };
|
||||||
|
jsonData.value = value;
|
||||||
|
Grocy.Api.Post('user/settings/' + settingKey, jsonData,
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
// Nothing to do...
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
62
public/js/grocy_dbchangedhandling.js
Normal file
62
public/js/grocy_dbchangedhandling.js
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
Grocy.Api.Get('system/get-db-changed-time',
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
Grocy.DatabaseChangedTime = moment(result.changed_time);
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if the database has changed once a minute
|
||||||
|
// If a change is detected, reload the current page, but only if already idling for at least 50 seconds,
|
||||||
|
// when there is no unsaved form data and when the user enabled auto reloading
|
||||||
|
setInterval(function()
|
||||||
|
{
|
||||||
|
Grocy.Api.Get('system/get-db-changed-time',
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
var newDbChangedTime = moment(result.changed_time);
|
||||||
|
if (newDbChangedTime.isAfter(Grocy.DatabaseChangedTime))
|
||||||
|
{
|
||||||
|
if (Grocy.IdleTime >= 50)
|
||||||
|
{
|
||||||
|
if (BoolVal(Grocy.UserSettings.auto_reload_on_db_change) && $("form.is-dirty").length === 0)
|
||||||
|
{
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Grocy.DatabaseChangedTime = newDbChangedTime;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, 60000);
|
||||||
|
|
||||||
|
Grocy.IdleTime = 0;
|
||||||
|
Grocy.ResetIdleTime = function()
|
||||||
|
{
|
||||||
|
Grocy.IdleTime = 0;
|
||||||
|
}
|
||||||
|
window.onmousemove = Grocy.ResetIdleTime;
|
||||||
|
window.onmousedown = Grocy.ResetIdleTime;
|
||||||
|
window.onclick = Grocy.ResetIdleTime;
|
||||||
|
window.onscroll = Grocy.ResetIdleTime;
|
||||||
|
window.onkeypress = Grocy.ResetIdleTime;
|
||||||
|
|
||||||
|
// Increase the idle time once every second
|
||||||
|
// On any interaction it will be reset to 0 (see above)
|
||||||
|
setInterval(function()
|
||||||
|
{
|
||||||
|
Grocy.IdleTime += 1;
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
if (BoolVal(Grocy.UserSettings.auto_reload_on_db_change))
|
||||||
|
{
|
||||||
|
$("#auto-reload-enabled").prop("checked", true);
|
||||||
|
}
|
106
public/js/grocy_nightmode.js
Normal file
106
public/js/grocy_nightmode.js
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
$("#night-mode-enabled").on("change", function()
|
||||||
|
{
|
||||||
|
var value = $(this).is(":checked");
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
$("body").addClass("night-mode");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$("body").removeClass("night-mode");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#auto-night-mode-enabled").on("change", function()
|
||||||
|
{
|
||||||
|
var value = $(this).is(":checked");
|
||||||
|
$("#auto-night-mode-time-range-from").prop("readonly", !value);
|
||||||
|
$("#auto-night-mode-time-range-to").prop("readonly", !value);
|
||||||
|
|
||||||
|
if (!value && !BoolVal(Grocy.UserSettings.night_mode_enabled))
|
||||||
|
{
|
||||||
|
$("body").removeClass("night-mode");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on("keyup", "#auto-night-mode-time-range-from, #auto-night-mode-time-range-to", function()
|
||||||
|
{
|
||||||
|
var value = $(this).val();
|
||||||
|
var valueIsValid = moment(value, "HH:mm", true).isValid();
|
||||||
|
|
||||||
|
if (valueIsValid)
|
||||||
|
{
|
||||||
|
$(this).removeClass("bg-danger");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$(this).addClass("bg-danger");
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckNightMode();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#auto-night-mode-time-range-goes-over-midgnight").on("change", function()
|
||||||
|
{
|
||||||
|
CheckNightMode();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#night-mode-enabled").prop("checked", BoolVal(Grocy.UserSettings.night_mode_enabled));
|
||||||
|
$("#auto-night-mode-enabled").prop("checked", BoolVal(Grocy.UserSettings.auto_night_mode_enabled));
|
||||||
|
$("#auto-night-mode-time-range-goes-over-midgnight").prop("checked", BoolVal(Grocy.UserSettings.auto_night_mode_time_range_goes_over_midnight));
|
||||||
|
$("#auto-night-mode-enabled").trigger("change");
|
||||||
|
$("#auto-night-mode-time-range-from").val(Grocy.UserSettings.auto_night_mode_time_range_from);
|
||||||
|
$("#auto-night-mode-time-range-from").trigger("keyup");
|
||||||
|
$("#auto-night-mode-time-range-to").val(Grocy.UserSettings.auto_night_mode_time_range_to);
|
||||||
|
$("#auto-night-mode-time-range-to").trigger("keyup");
|
||||||
|
|
||||||
|
function CheckNightMode()
|
||||||
|
{
|
||||||
|
if (!BoolVal(Grocy.UserSettings.auto_night_mode_enabled))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var start = moment(Grocy.UserSettings.auto_night_mode_time_range_from, "HH:mm", true);
|
||||||
|
var end = moment(Grocy.UserSettings.auto_night_mode_time_range_to, "HH:mm", true);
|
||||||
|
var now = moment();
|
||||||
|
|
||||||
|
if (!start.isValid() || !end.isValid)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BoolVal(Grocy.UserSettings.auto_night_mode_time_range_goes_over_midnight))
|
||||||
|
{
|
||||||
|
end.add(1, "day");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start.isSameOrBefore(now) && end.isSameOrAfter(now)) // We're INSIDE of night mode time range
|
||||||
|
{
|
||||||
|
if (!$("body").hasClass("night-mode"))
|
||||||
|
{
|
||||||
|
$("body").addClass("night-mode");
|
||||||
|
$("#currently-inside-night-mode-range").prop("checked", true);
|
||||||
|
$("#currently-inside-night-mode-range").trigger("change");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // We're OUTSIDE of night mode time range
|
||||||
|
{
|
||||||
|
if ($("body").hasClass("night-mode"))
|
||||||
|
{
|
||||||
|
$("body").removeClass("night-mode");
|
||||||
|
$("#currently-inside-night-mode-range").prop("checked", false);
|
||||||
|
$("#currently-inside-night-mode-range").trigger("change");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CheckNightMode();
|
||||||
|
|
||||||
|
if (Grocy.Mode === "production")
|
||||||
|
{
|
||||||
|
setInterval(CheckNightMode, 60000);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setInterval(CheckNightMode, 4000);
|
||||||
|
}
|
@@ -7,7 +7,16 @@
|
|||||||
'language': JSON.parse(L('datatables_localization')),
|
'language': JSON.parse(L('datatables_localization')),
|
||||||
'scrollY': false,
|
'scrollY': false,
|
||||||
'colReorder': true,
|
'colReorder': true,
|
||||||
'stateSave': true
|
'stateSave': true,
|
||||||
|
'stateSaveParams': function(settings, data)
|
||||||
|
{
|
||||||
|
data.search.search = "";
|
||||||
|
|
||||||
|
data.columns.forEach(column =>
|
||||||
|
{
|
||||||
|
column.search.search = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#search").on("keyup", function()
|
$("#search").on("keyup", function()
|
||||||
|
@@ -7,7 +7,16 @@
|
|||||||
'language': JSON.parse(L('datatables_localization')),
|
'language': JSON.parse(L('datatables_localization')),
|
||||||
'scrollY': false,
|
'scrollY': false,
|
||||||
'colReorder': true,
|
'colReorder': true,
|
||||||
'stateSave': true
|
'stateSave': true,
|
||||||
|
'stateSaveParams': function(settings, data)
|
||||||
|
{
|
||||||
|
data.search.search = "";
|
||||||
|
|
||||||
|
data.columns.forEach(column =>
|
||||||
|
{
|
||||||
|
column.search.search = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#search").on("keyup", function()
|
$("#search").on("keyup", function()
|
||||||
@@ -21,8 +30,35 @@ $("#search").on("keyup", function()
|
|||||||
batteriesOverviewTable.search(value).draw();
|
batteriesOverviewTable.search(value).draw();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#status-filter").on("change", function()
|
||||||
|
{
|
||||||
|
var value = $(this).val();
|
||||||
|
if (value === "all")
|
||||||
|
{
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer CSS classes of selected element to dropdown element (for background)
|
||||||
|
$(this).attr("class", $("#" + $(this).attr("id") + " option[value='" + value + "']").attr("class") + " form-control");
|
||||||
|
|
||||||
|
batteriesOverviewTable.column(4).search(value).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".status-filter-button").on("click", function()
|
||||||
|
{
|
||||||
|
var value = $(this).data("status-filter");
|
||||||
|
$("#status-filter").val(value);
|
||||||
|
$("#status-filter").trigger("change");
|
||||||
|
});
|
||||||
|
|
||||||
$(document).on('click', '.track-charge-cycle-button', function(e)
|
$(document).on('click', '.track-charge-cycle-button', function(e)
|
||||||
{
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Remove the focus from the current button
|
||||||
|
// to prevent that the tooltip stays until clicked anywhere else
|
||||||
|
document.activeElement.blur();
|
||||||
|
|
||||||
var batteryId = $(e.currentTarget).attr('data-battery-id');
|
var batteryId = $(e.currentTarget).attr('data-battery-id');
|
||||||
var batteryName = $(e.currentTarget).attr('data-battery-name');
|
var batteryName = $(e.currentTarget).attr('data-battery-name');
|
||||||
var trackedTime = moment().format('YYYY-MM-DD HH:mm:ss');
|
var trackedTime = moment().format('YYYY-MM-DD HH:mm:ss');
|
||||||
@@ -66,7 +102,7 @@ $(document).on('click', '.track-charge-cycle-button', function(e)
|
|||||||
$('#battery-' + batteryId + '-next-charge-time-timeago').attr('datetime', result.next_estimated_charge_time);
|
$('#battery-' + batteryId + '-next-charge-time-timeago').attr('datetime', result.next_estimated_charge_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
toastr.success(L('Tracked charge cylce of battery #1 on #2', batteryName, trackedTime));
|
toastr.success(L('Tracked charge cycle of battery #1 on #2', batteryName, trackedTime));
|
||||||
RefreshContextualTimeago();
|
RefreshContextualTimeago();
|
||||||
RefreshStatistics();
|
RefreshStatistics();
|
||||||
},
|
},
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
console.error(xhr);
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
console.error(xhr);
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -39,9 +39,10 @@ $('#battery-form input').keydown(function(event)
|
|||||||
{
|
{
|
||||||
if (event.keyCode === 13) //Enter
|
if (event.keyCode === 13) //Enter
|
||||||
{
|
{
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
if (document.getElementById('battery-form').checkValidity() === false) //There is at least one validation error
|
if (document.getElementById('battery-form').checkValidity() === false) //There is at least one validation error
|
||||||
{
|
{
|
||||||
event.preventDefault();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@@ -49,7 +49,8 @@ $('#battery_id').on('change', function(e)
|
|||||||
});
|
});
|
||||||
|
|
||||||
$('.combobox').combobox({
|
$('.combobox').combobox({
|
||||||
appendId: '_text_input'
|
appendId: '_text_input',
|
||||||
|
bsVersion: '4'
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#battery_id').val('');
|
$('#battery_id').val('');
|
||||||
@@ -67,9 +68,10 @@ $('#batterytracking-form input').keydown(function(event)
|
|||||||
{
|
{
|
||||||
if (event.keyCode === 13) //Enter
|
if (event.keyCode === 13) //Enter
|
||||||
{
|
{
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
if (document.getElementById('batterytracking-form').checkValidity() === false) //There is at least one validation error
|
if (document.getElementById('batterytracking-form').checkValidity() === false) //There is at least one validation error
|
||||||
{
|
{
|
||||||
event.preventDefault();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
72
public/viewjs/choreform.js
Normal file
72
public/viewjs/choreform.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
$('#save-chore-button').on('click', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (Grocy.EditMode === 'create')
|
||||||
|
{
|
||||||
|
Grocy.Api.Post('add-object/chores', $('#chore-form').serializeJSON(),
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
window.location.href = U('/chores');
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Grocy.Api.Post('edit-object/chores/' + Grocy.EditObjectId, $('#chore-form').serializeJSON(),
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
window.location.href = U('/chores');
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#chore-form input').keyup(function(event)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('chore-form');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#chore-form input').keydown(function(event)
|
||||||
|
{
|
||||||
|
if (event.keyCode === 13) //Enter
|
||||||
|
{
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (document.getElementById('chore-form').checkValidity() === false) //There is at least one validation error
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$('#save-chore-button').click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#name').focus();
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('chore-form');
|
||||||
|
|
||||||
|
$('.input-group-chore-period-type').on('change', function(e)
|
||||||
|
{
|
||||||
|
var periodType = $('#period_type').val();
|
||||||
|
var periodDays = $('#period_days').val();
|
||||||
|
|
||||||
|
if (periodType === 'dynamic-regular')
|
||||||
|
{
|
||||||
|
$('#chore-period-type-info').text(L('This means it is estimated that a new execution of this chore is tracked #1 days after the last was tracked', periodDays.toString()));
|
||||||
|
$('#chore-period-type-info').removeClass('d-none');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$('#chore-period-type-info').addClass('d-none');
|
||||||
|
}
|
||||||
|
});
|
@@ -1,4 +1,4 @@
|
|||||||
var habitsTable = $('#habits-table').DataTable({
|
var choresTable = $('#chores-table').DataTable({
|
||||||
'paginate': false,
|
'paginate': false,
|
||||||
'order': [[1, 'asc']],
|
'order': [[1, 'asc']],
|
||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
@@ -7,7 +7,16 @@
|
|||||||
'language': JSON.parse(L('datatables_localization')),
|
'language': JSON.parse(L('datatables_localization')),
|
||||||
'scrollY': false,
|
'scrollY': false,
|
||||||
'colReorder': true,
|
'colReorder': true,
|
||||||
'stateSave': true
|
'stateSave': true,
|
||||||
|
'stateSaveParams': function(settings, data)
|
||||||
|
{
|
||||||
|
data.search.search = "";
|
||||||
|
|
||||||
|
data.columns.forEach(column =>
|
||||||
|
{
|
||||||
|
column.search.search = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#search").on("keyup", function()
|
$("#search").on("keyup", function()
|
||||||
@@ -18,16 +27,16 @@ $("#search").on("keyup", function()
|
|||||||
value = "";
|
value = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
habitsTable.search(value).draw();
|
choresTable.search(value).draw();
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on('click', '.habit-delete-button', function (e)
|
$(document).on('click', '.chore-delete-button', function (e)
|
||||||
{
|
{
|
||||||
var objectName = $(e.currentTarget).attr('data-habit-name');
|
var objectName = $(e.currentTarget).attr('data-chore-name');
|
||||||
var objectId = $(e.currentTarget).attr('data-habit-id');
|
var objectId = $(e.currentTarget).attr('data-chore-id');
|
||||||
|
|
||||||
bootbox.confirm({
|
bootbox.confirm({
|
||||||
message: L('Are you sure to delete habit "#1"?', objectName),
|
message: L('Are you sure to delete chore "#1"?', objectName),
|
||||||
buttons: {
|
buttons: {
|
||||||
confirm: {
|
confirm: {
|
||||||
label: L('Yes'),
|
label: L('Yes'),
|
||||||
@@ -42,10 +51,10 @@ $(document).on('click', '.habit-delete-button', function (e)
|
|||||||
{
|
{
|
||||||
if (result === true)
|
if (result === true)
|
||||||
{
|
{
|
||||||
Grocy.Api.Get('delete-object/habits/' + objectId,
|
Grocy.Api.Get('delete-object/chores/' + objectId,
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
window.location.href = U('/habits');
|
window.location.href = U('/chores');
|
||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
46
public/viewjs/choresanalysis.js
Normal file
46
public/viewjs/choresanalysis.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
var choresAnalysisTable = $('#chores-analysis-table').DataTable({
|
||||||
|
'paginate': false,
|
||||||
|
'order': [[1, 'desc']],
|
||||||
|
'language': JSON.parse(L('datatables_localization')),
|
||||||
|
'scrollY': false,
|
||||||
|
'colReorder': true,
|
||||||
|
'stateSave': true,
|
||||||
|
'stateSaveParams': function(settings, data)
|
||||||
|
{
|
||||||
|
data.search.search = "";
|
||||||
|
|
||||||
|
data.columns.forEach(column =>
|
||||||
|
{
|
||||||
|
column.search.search = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#chore-filter").on("change", function()
|
||||||
|
{
|
||||||
|
var value = $(this).val();
|
||||||
|
var text = $("#chore-filter option:selected").text();
|
||||||
|
if (value === "all")
|
||||||
|
{
|
||||||
|
text = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
choresAnalysisTable.column(0).search(text).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#search").on("keyup", function()
|
||||||
|
{
|
||||||
|
var value = $(this).val();
|
||||||
|
if (value === "all")
|
||||||
|
{
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
choresAnalysisTable.search(value).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typeof GetUriParam("chore") !== "undefined")
|
||||||
|
{
|
||||||
|
$("#chore-filter").val(GetUriParam("chore"));
|
||||||
|
$("#chore-filter").trigger("change");
|
||||||
|
}
|
154
public/viewjs/choresoverview.js
Normal file
154
public/viewjs/choresoverview.js
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
var choresOverviewTable = $('#chores-overview-table').DataTable({
|
||||||
|
'paginate': false,
|
||||||
|
'order': [[2, 'desc']],
|
||||||
|
'columnDefs': [
|
||||||
|
{ 'orderable': false, 'targets': 0 }
|
||||||
|
],
|
||||||
|
'language': JSON.parse(L('datatables_localization')),
|
||||||
|
'scrollY': false,
|
||||||
|
'colReorder': true,
|
||||||
|
'stateSave': true,
|
||||||
|
'stateSaveParams': function(settings, data)
|
||||||
|
{
|
||||||
|
data.search.search = "";
|
||||||
|
|
||||||
|
data.columns.forEach(column =>
|
||||||
|
{
|
||||||
|
column.search.search = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#search").on("keyup", function()
|
||||||
|
{
|
||||||
|
var value = $(this).val();
|
||||||
|
if (value === "all")
|
||||||
|
{
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
choresOverviewTable.search(value).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#status-filter").on("change", function()
|
||||||
|
{
|
||||||
|
var value = $(this).val();
|
||||||
|
if (value === "all")
|
||||||
|
{
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer CSS classes of selected element to dropdown element (for background)
|
||||||
|
$(this).attr("class", $("#" + $(this).attr("id") + " option[value='" + value + "']").attr("class") + " form-control");
|
||||||
|
|
||||||
|
choresOverviewTable.column(4).search(value).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".status-filter-button").on("click", function()
|
||||||
|
{
|
||||||
|
var value = $(this).data("status-filter");
|
||||||
|
$("#status-filter").val(value);
|
||||||
|
$("#status-filter").trigger("change");
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.track-chore-button', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Remove the focus from the current button
|
||||||
|
// to prevent that the tooltip stays until clicked anywhere else
|
||||||
|
document.activeElement.blur();
|
||||||
|
|
||||||
|
var choreId = $(e.currentTarget).attr('data-chore-id');
|
||||||
|
var choreName = $(e.currentTarget).attr('data-chore-name');
|
||||||
|
var trackedTime = moment().format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
|
||||||
|
Grocy.Api.Get('chores/track-chore-execution/' + choreId + '?tracked_time=' + trackedTime,
|
||||||
|
function()
|
||||||
|
{
|
||||||
|
Grocy.Api.Get('chores/get-chore-details/' + choreId,
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
var choreRow = $('#chore-' + choreId + '-row');
|
||||||
|
var nextXDaysThreshold = moment().add($("#info-due-chores").data("next-x-days"), "days");
|
||||||
|
var now = moment();
|
||||||
|
var nextExecutionTime = moment(result.next_estimated_execution_time);
|
||||||
|
|
||||||
|
choreRow.removeClass("table-warning");
|
||||||
|
choreRow.removeClass("table-danger");
|
||||||
|
if (nextExecutionTime.isBefore(now))
|
||||||
|
{
|
||||||
|
choreRow.addClass("table-danger");
|
||||||
|
}
|
||||||
|
else if (nextExecutionTime.isBefore(nextXDaysThreshold))
|
||||||
|
{
|
||||||
|
choreRow.addClass("table-warning");
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#chore-' + choreId + '-last-tracked-time').parent().effect('highlight', { }, 500);
|
||||||
|
$('#chore-' + choreId + '-last-tracked-time').fadeOut(500, function()
|
||||||
|
{
|
||||||
|
$(this).text(trackedTime).fadeIn(500);
|
||||||
|
});
|
||||||
|
$('#chore-' + choreId + '-last-tracked-time-timeago').attr('datetime', trackedTime);
|
||||||
|
|
||||||
|
if (result.chore.period_type == "dynamic-regular")
|
||||||
|
{
|
||||||
|
$('#chore-' + choreId + '-next-execution-time').parent().effect('highlight', { }, 500);
|
||||||
|
$('#chore-' + choreId + '-next-execution-time').fadeOut(500, function()
|
||||||
|
{
|
||||||
|
$(this).text(result.next_estimated_execution_time).fadeIn(500);
|
||||||
|
});
|
||||||
|
$('#chore-' + choreId + '-next-execution-time-timeago').attr('datetime', result.next_estimated_execution_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
toastr.success(L('Tracked execution of chore #1 on #2', choreName, trackedTime));
|
||||||
|
RefreshContextualTimeago();
|
||||||
|
RefreshStatistics();
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
function RefreshStatistics()
|
||||||
|
{
|
||||||
|
var nextXDays = $("#info-due-chores").data("next-x-days");
|
||||||
|
Grocy.Api.Get('chores/get-current',
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
var dueCount = 0;
|
||||||
|
var overdueCount = 0;
|
||||||
|
var now = moment();
|
||||||
|
var nextXDaysThreshold = moment().add(nextXDays, "days");
|
||||||
|
result.forEach(element => {
|
||||||
|
var date = moment(element.next_estimated_execution_time);
|
||||||
|
if (date.isBefore(now))
|
||||||
|
{
|
||||||
|
overdueCount++;
|
||||||
|
}
|
||||||
|
else if (date.isBefore(nextXDaysThreshold))
|
||||||
|
{
|
||||||
|
dueCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#info-due-chores").text(Pluralize(dueCount, L('#1 chore is due to be done within the next #2 days', dueCount, nextXDays), L('#1 chores are due to be done within the next #2 days', dueCount, nextXDays)));
|
||||||
|
$("#info-overdue-chores").text(Pluralize(overdueCount, L('#1 chore is overdue to be done', overdueCount), L('#1 chores are overdue to be done', overdueCount)));
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
RefreshStatistics();
|
84
public/viewjs/choretracking.js
Normal file
84
public/viewjs/choretracking.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
$('#save-choretracking-button').on('click', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var jsonForm = $('#choretracking-form').serializeJSON();
|
||||||
|
|
||||||
|
Grocy.Api.Get('chores/get-chore-details/' + jsonForm.chore_id,
|
||||||
|
function (choreDetails)
|
||||||
|
{
|
||||||
|
Grocy.Api.Get('chores/track-chore-execution/' + jsonForm.chore_id + '?tracked_time=' + Grocy.Components.DateTimePicker.GetValue() + "&done_by=" + Grocy.Components.UserPicker.GetValue(),
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
toastr.success(L('Tracked execution of chore #1 on #2', choreDetails.chore.name, Grocy.Components.DateTimePicker.GetValue()));
|
||||||
|
|
||||||
|
$('#chore_id').val('');
|
||||||
|
$('#chore_id_text_input').focus();
|
||||||
|
$('#chore_id_text_input').val('');
|
||||||
|
Grocy.Components.DateTimePicker.SetValue(moment().format('YYYY-MM-DD HH:mm:ss'));
|
||||||
|
$('#chore_id_text_input').trigger('change');
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('choretracking-form');
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#chore_id').on('change', function(e)
|
||||||
|
{
|
||||||
|
var input = $('#chore_id_text_input').val().toString();
|
||||||
|
$('#chore_id_text_input').val(input);
|
||||||
|
$('#chore_id').data('combobox').refresh();
|
||||||
|
|
||||||
|
var choreId = $(e.target).val();
|
||||||
|
if (choreId)
|
||||||
|
{
|
||||||
|
Grocy.Components.ChoreCard.Refresh(choreId);
|
||||||
|
Grocy.Components.DateTimePicker.GetInputElement().focus();
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('choretracking-form');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.combobox').combobox({
|
||||||
|
appendId: '_text_input',
|
||||||
|
bsVersion: '4'
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#chore_id_text_input').focus();
|
||||||
|
$('#chore_id_text_input').trigger('change');
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('choretracking-form');
|
||||||
|
|
||||||
|
$('#choretracking-form input').keyup(function (event)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('choretracking-form');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#choretracking-form input').keydown(function(event)
|
||||||
|
{
|
||||||
|
if (event.keyCode === 13) //Enter
|
||||||
|
{
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (document.getElementById('choretracking-form').checkValidity() === false) //There is at least one validation error
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$('#save-choretracking-button').click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Grocy.Components.DateTimePicker.GetInputElement().on('keypress', function(e)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('choretracking-form');
|
||||||
|
});
|
21
public/viewjs/components/chorecard.js
Normal file
21
public/viewjs/components/chorecard.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
Grocy.Components.ChoreCard = { };
|
||||||
|
|
||||||
|
Grocy.Components.ChoreCard.Refresh = function(choreId)
|
||||||
|
{
|
||||||
|
Grocy.Api.Get('chores/get-chore-details/' + choreId,
|
||||||
|
function(choreDetails)
|
||||||
|
{
|
||||||
|
$('#chorecard-chore-name').text(choreDetails.chore.name);
|
||||||
|
$('#chorecard-chore-last-tracked').text((choreDetails.last_tracked || L('never')));
|
||||||
|
$('#chorecard-chore-last-tracked-timeago').text($.timeago(choreDetails.last_tracked || ''));
|
||||||
|
$('#chorecard-chore-tracked-count').text((choreDetails.tracked_count || '0'));
|
||||||
|
$('#chorecard-chore-last-done-by').text((choreDetails.last_done_by.display_name || L('Unknown')));
|
||||||
|
|
||||||
|
EmptyElementWhenMatches('#chorecard-chore-last-tracked-timeago', L('timeago_nan'));
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
@@ -14,6 +14,14 @@ Grocy.Components.DateTimePicker.SetValue = function(value)
|
|||||||
{
|
{
|
||||||
Grocy.Components.DateTimePicker.GetInputElement().val(value);
|
Grocy.Components.DateTimePicker.GetInputElement().val(value);
|
||||||
Grocy.Components.DateTimePicker.GetInputElement().trigger('change');
|
Grocy.Components.DateTimePicker.GetInputElement().trigger('change');
|
||||||
|
|
||||||
|
// "Click" the shortcut checkbox when the desired value is
|
||||||
|
// not the shortcut value and it is currently set
|
||||||
|
var shortcutValue = $("#datetimepicker-shortcut").data("datetimepicker-shortcut-value");
|
||||||
|
if (value != shortcutValue && $("#datetimepicker-shortcut").is(":checked"))
|
||||||
|
{
|
||||||
|
$("#datetimepicker-shortcut").click();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var startDate = null;
|
var startDate = null;
|
||||||
@@ -21,6 +29,10 @@ if (Grocy.Components.DateTimePicker.GetInputElement().data('init-with-now') ===
|
|||||||
{
|
{
|
||||||
startDate = moment().format(Grocy.Components.DateTimePicker.GetInputElement().data('format'));
|
startDate = moment().format(Grocy.Components.DateTimePicker.GetInputElement().data('format'));
|
||||||
}
|
}
|
||||||
|
if (Grocy.Components.DateTimePicker.GetInputElement().data('init-value').length > 0)
|
||||||
|
{
|
||||||
|
startDate = moment(Grocy.Components.DateTimePicker.GetInputElement().data('init-value')).format(Grocy.Components.DateTimePicker.GetInputElement().data('format'));
|
||||||
|
}
|
||||||
|
|
||||||
var limitDate = moment('2999-12-31 23:59:59');
|
var limitDate = moment('2999-12-31 23:59:59');
|
||||||
if (Grocy.Components.DateTimePicker.GetInputElement().data('limit-end-to-now') === true)
|
if (Grocy.Components.DateTimePicker.GetInputElement().data('limit-end-to-now') === true)
|
||||||
|
@@ -1,21 +0,0 @@
|
|||||||
Grocy.Components.HabitCard = { };
|
|
||||||
|
|
||||||
Grocy.Components.HabitCard.Refresh = function(habitId)
|
|
||||||
{
|
|
||||||
Grocy.Api.Get('habits/get-habit-details/' + habitId,
|
|
||||||
function(habitDetails)
|
|
||||||
{
|
|
||||||
$('#habitcard-habit-name').text(habitDetails.habit.name);
|
|
||||||
$('#habitcard-habit-last-tracked').text((habitDetails.last_tracked || L('never')));
|
|
||||||
$('#habitcard-habit-last-tracked-timeago').text($.timeago(habitDetails.last_tracked || ''));
|
|
||||||
$('#habitcard-habit-tracked-count').text((habitDetails.tracked_count || '0'));
|
|
||||||
$('#habitcard-habit-last-done-by').text((habitDetails.last_done_by.display_name || L('Unknown')));
|
|
||||||
|
|
||||||
EmptyElementWhenMatches('#habitcard-habit-last-tracked-timeago', L('timeago_nan'));
|
|
||||||
},
|
|
||||||
function(xhr)
|
|
||||||
{
|
|
||||||
console.error(xhr);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
15
public/viewjs/components/numberpicker.js
Normal file
15
public/viewjs/components/numberpicker.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
$(".numberpicker-down-button").unbind('click').on("click", function ()
|
||||||
|
{
|
||||||
|
var inputElement = $(this).parent().parent().find('input[type="number"]')[0];
|
||||||
|
inputElement.stepDown();
|
||||||
|
$(inputElement).trigger('keyup');
|
||||||
|
$(inputElement).trigger('change');
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".numberpicker-up-button").unbind('click').on("click", function()
|
||||||
|
{
|
||||||
|
var inputElement = $(this).parent().parent().find('input[type="number"]')[0];
|
||||||
|
inputElement.stepUp();
|
||||||
|
$(inputElement).trigger('keyup');
|
||||||
|
$(inputElement).trigger('change');
|
||||||
|
});
|
@@ -45,7 +45,8 @@ Grocy.Components.ProductPicker.HideCustomError = function()
|
|||||||
|
|
||||||
$('.product-combobox').combobox({
|
$('.product-combobox').combobox({
|
||||||
appendId: '_text_input',
|
appendId: '_text_input',
|
||||||
bsVersion: '4'
|
bsVersion: '4',
|
||||||
|
clearIfNoMatch: false
|
||||||
});
|
});
|
||||||
|
|
||||||
var prefillProduct = GetUriParam('createdproduct');
|
var prefillProduct = GetUriParam('createdproduct');
|
||||||
@@ -81,8 +82,13 @@ if (addBarcode !== undefined)
|
|||||||
$('#barcode-lookup-disabled-hint').removeClass('d-none');
|
$('#barcode-lookup-disabled-hint').removeClass('d-none');
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#product_id_text_input').on('change', function(e)
|
$('#product_id_text_input').on('blur', function(e)
|
||||||
{
|
{
|
||||||
|
if (Grocy.Components.ProductPicker.GetPicker().hasClass("combobox-menu-visible"))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var input = $('#product_id_text_input').val().toString();
|
var input = $('#product_id_text_input').val().toString();
|
||||||
var possibleOptionElement = $("#product_id option[data-additional-searchdata*='" + input + "']").first();
|
var possibleOptionElement = $("#product_id option[data-additional-searchdata*='" + input + "']").first();
|
||||||
|
|
||||||
@@ -106,14 +112,20 @@ $('#product_id_text_input').on('change', function(e)
|
|||||||
bootbox.dialog({
|
bootbox.dialog({
|
||||||
message: L('"#1" could not be resolved to a product, how do you want to proceed?', input),
|
message: L('"#1" could not be resolved to a product, how do you want to proceed?', input),
|
||||||
title: L('Create or assign product'),
|
title: L('Create or assign product'),
|
||||||
onEscape: function() { },
|
onEscape: function()
|
||||||
|
{
|
||||||
|
Grocy.Components.ProductPicker.SetValue('');
|
||||||
|
},
|
||||||
size: 'large',
|
size: 'large',
|
||||||
backdrop: true,
|
backdrop: true,
|
||||||
buttons: {
|
buttons: {
|
||||||
cancel: {
|
cancel: {
|
||||||
label: 'Cancel',
|
label: L('Cancel'),
|
||||||
className: 'btn-default responsive-button',
|
className: 'btn-default responsive-button',
|
||||||
callback: function() { }
|
callback: function()
|
||||||
|
{
|
||||||
|
Grocy.Components.ProductPicker.SetValue('');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
addnewproduct: {
|
addnewproduct: {
|
||||||
label: '<strong>P</strong> ' + L('Add as new product'),
|
label: '<strong>P</strong> ' + L('Add as new product'),
|
||||||
|
@@ -90,9 +90,10 @@ $('#consume-form input').keydown(function(event)
|
|||||||
{
|
{
|
||||||
if (event.keyCode === 13) //Enter
|
if (event.keyCode === 13) //Enter
|
||||||
{
|
{
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
if (document.getElementById('consume-form').checkValidity() === false) //There is at least one validation error
|
if (document.getElementById('consume-form').checkValidity() === false) //There is at least one validation error
|
||||||
{
|
{
|
||||||
event.preventDefault();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@@ -1,71 +0,0 @@
|
|||||||
$('#save-habit-button').on('click', function(e)
|
|
||||||
{
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if (Grocy.EditMode === 'create')
|
|
||||||
{
|
|
||||||
Grocy.Api.Post('add-object/habits', $('#habit-form').serializeJSON(),
|
|
||||||
function(result)
|
|
||||||
{
|
|
||||||
window.location.href = U('/habits');
|
|
||||||
},
|
|
||||||
function(xhr)
|
|
||||||
{
|
|
||||||
console.error(xhr);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Grocy.Api.Post('edit-object/habits/' + Grocy.EditObjectId, $('#habit-form').serializeJSON(),
|
|
||||||
function(result)
|
|
||||||
{
|
|
||||||
window.location.href = U('/habits');
|
|
||||||
},
|
|
||||||
function(xhr)
|
|
||||||
{
|
|
||||||
console.error(xhr);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#habit-form input').keyup(function(event)
|
|
||||||
{
|
|
||||||
Grocy.FrontendHelpers.ValidateForm('habit-form');
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#habit-form input').keydown(function(event)
|
|
||||||
{
|
|
||||||
if (event.keyCode === 13) //Enter
|
|
||||||
{
|
|
||||||
if (document.getElementById('habit-form').checkValidity() === false) //There is at least one validation error
|
|
||||||
{
|
|
||||||
event.preventDefault();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$('#save-habit-button').click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#name').focus();
|
|
||||||
Grocy.FrontendHelpers.ValidateForm('habit-form');
|
|
||||||
|
|
||||||
$('.input-group-habit-period-type').on('change', function(e)
|
|
||||||
{
|
|
||||||
var periodType = $('#period_type').val();
|
|
||||||
var periodDays = $('#period_days').val();
|
|
||||||
|
|
||||||
if (periodType === 'dynamic-regular')
|
|
||||||
{
|
|
||||||
$('#habit-period-type-info').text(L('This means it is estimated that a new execution of this habit is tracked #1 days after the last was tracked', periodDays.toString()));
|
|
||||||
$('#habit-period-type-info').removeClass('d-none');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$('#habit-period-type-info').addClass('d-none');
|
|
||||||
}
|
|
||||||
});
|
|
@@ -1,37 +0,0 @@
|
|||||||
var habitsAnalysisTable = $('#habits-analysis-table').DataTable({
|
|
||||||
'paginate': false,
|
|
||||||
'order': [[1, 'desc']],
|
|
||||||
'language': JSON.parse(L('datatables_localization')),
|
|
||||||
'scrollY': false,
|
|
||||||
'colReorder': true,
|
|
||||||
'stateSave': true
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#habit-filter").on("change", function()
|
|
||||||
{
|
|
||||||
var value = $(this).val();
|
|
||||||
var text = $("#habit-filter option:selected").text();
|
|
||||||
if (value === "all")
|
|
||||||
{
|
|
||||||
text = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
habitsAnalysisTable.column(0).search(text).draw();
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#search").on("keyup", function()
|
|
||||||
{
|
|
||||||
var value = $(this).val();
|
|
||||||
if (value === "all")
|
|
||||||
{
|
|
||||||
value = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
habitsAnalysisTable.search(value).draw();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (typeof GetUriParam("habit") !== "undefined")
|
|
||||||
{
|
|
||||||
$("#habit-filter").val(GetUriParam("habit"));
|
|
||||||
$("#habit-filter").trigger("change");
|
|
||||||
}
|
|
@@ -1,118 +0,0 @@
|
|||||||
var habitsOverviewTable = $('#habits-overview-table').DataTable({
|
|
||||||
'paginate': false,
|
|
||||||
'order': [[2, 'desc']],
|
|
||||||
'columnDefs': [
|
|
||||||
{ 'orderable': false, 'targets': 0 }
|
|
||||||
],
|
|
||||||
'language': JSON.parse(L('datatables_localization')),
|
|
||||||
'scrollY': false,
|
|
||||||
'colReorder': true,
|
|
||||||
'stateSave': true
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#search").on("keyup", function()
|
|
||||||
{
|
|
||||||
var value = $(this).val();
|
|
||||||
if (value === "all")
|
|
||||||
{
|
|
||||||
value = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
habitsOverviewTable.search(value).draw();
|
|
||||||
});
|
|
||||||
|
|
||||||
$(document).on('click', '.track-habit-button', function(e)
|
|
||||||
{
|
|
||||||
var habitId = $(e.currentTarget).attr('data-habit-id');
|
|
||||||
var habitName = $(e.currentTarget).attr('data-habit-name');
|
|
||||||
var trackedTime = moment().format('YYYY-MM-DD HH:mm:ss');
|
|
||||||
|
|
||||||
Grocy.Api.Get('habits/track-habit-execution/' + habitId + '?tracked_time=' + trackedTime,
|
|
||||||
function()
|
|
||||||
{
|
|
||||||
Grocy.Api.Get('habits/get-habit-details/' + habitId,
|
|
||||||
function(result)
|
|
||||||
{
|
|
||||||
var habitRow = $('#habit-' + habitId + '-row');
|
|
||||||
var nextXDaysThreshold = moment().add($("#info-due-habits").data("next-x-days"), "days");
|
|
||||||
var now = moment();
|
|
||||||
var nextExecutionTime = moment(result.next_estimated_execution_time);
|
|
||||||
|
|
||||||
habitRow.removeClass("table-warning");
|
|
||||||
habitRow.removeClass("table-danger");
|
|
||||||
if (nextExecutionTime.isBefore(now))
|
|
||||||
{
|
|
||||||
habitRow.addClass("table-danger");
|
|
||||||
}
|
|
||||||
else if (nextExecutionTime.isBefore(nextXDaysThreshold))
|
|
||||||
{
|
|
||||||
habitRow.addClass("table-warning");
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#habit-' + habitId + '-last-tracked-time').parent().effect('highlight', { }, 500);
|
|
||||||
$('#habit-' + habitId + '-last-tracked-time').fadeOut(500, function()
|
|
||||||
{
|
|
||||||
$(this).text(trackedTime).fadeIn(500);
|
|
||||||
});
|
|
||||||
$('#habit-' + habitId + '-last-tracked-time-timeago').attr('datetime', trackedTime);
|
|
||||||
|
|
||||||
if (result.habit.period_type == "dynamic-regular")
|
|
||||||
{
|
|
||||||
$('#habit-' + habitId + '-next-execution-time').parent().effect('highlight', { }, 500);
|
|
||||||
$('#habit-' + habitId + '-next-execution-time').fadeOut(500, function()
|
|
||||||
{
|
|
||||||
$(this).text(result.next_estimated_execution_time).fadeIn(500);
|
|
||||||
});
|
|
||||||
$('#habit-' + habitId + '-next-execution-time-timeago').attr('datetime', result.next_estimated_execution_time);
|
|
||||||
}
|
|
||||||
|
|
||||||
toastr.success(L('Tracked execution of habit #1 on #2', habitName, trackedTime));
|
|
||||||
RefreshContextualTimeago();
|
|
||||||
RefreshStatistics();
|
|
||||||
},
|
|
||||||
function(xhr)
|
|
||||||
{
|
|
||||||
console.error(xhr);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
function(xhr)
|
|
||||||
{
|
|
||||||
console.error(xhr);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
function RefreshStatistics()
|
|
||||||
{
|
|
||||||
var nextXDays = $("#info-due-habits").data("next-x-days");
|
|
||||||
Grocy.Api.Get('habits/get-current',
|
|
||||||
function(result)
|
|
||||||
{
|
|
||||||
var dueCount = 0;
|
|
||||||
var overdueCount = 0;
|
|
||||||
var now = moment();
|
|
||||||
var nextXDaysThreshold = moment().add(nextXDays, "days");
|
|
||||||
result.forEach(element => {
|
|
||||||
var date = moment(element.next_estimated_execution_time);
|
|
||||||
if (date.isBefore(now))
|
|
||||||
{
|
|
||||||
overdueCount++;
|
|
||||||
}
|
|
||||||
else if (date.isBefore(nextXDaysThreshold))
|
|
||||||
{
|
|
||||||
dueCount++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$("#info-due-habits").text(Pluralize(dueCount, L('#1 habit is due to be done within the next #2 days', dueCount, nextXDays), L('#1 habits are due to be done within the next #2 days', dueCount, nextXDays)));
|
|
||||||
$("#info-overdue-habits").text(Pluralize(overdueCount, L('#1 habit is overdue to be done', overdueCount), L('#1 habits are overdue to be done', overdueCount)));
|
|
||||||
},
|
|
||||||
function(xhr)
|
|
||||||
{
|
|
||||||
console.error(xhr);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
RefreshStatistics();
|
|
@@ -1,82 +0,0 @@
|
|||||||
$('#save-habittracking-button').on('click', function(e)
|
|
||||||
{
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
var jsonForm = $('#habittracking-form').serializeJSON();
|
|
||||||
|
|
||||||
Grocy.Api.Get('habits/get-habit-details/' + jsonForm.habit_id,
|
|
||||||
function (habitDetails)
|
|
||||||
{
|
|
||||||
Grocy.Api.Get('habits/track-habit-execution/' + jsonForm.habit_id + '?tracked_time=' + Grocy.Components.DateTimePicker.GetValue() + "&done_by=" + Grocy.Components.UserPicker.GetValue(),
|
|
||||||
function(result)
|
|
||||||
{
|
|
||||||
toastr.success(L('Tracked execution of habit #1 on #2', habitDetails.habit.name, Grocy.Components.DateTimePicker.GetValue()));
|
|
||||||
|
|
||||||
$('#habit_id').val('');
|
|
||||||
$('#habit_id_text_input').focus();
|
|
||||||
$('#habit_id_text_input').val('');
|
|
||||||
Grocy.Components.DateTimePicker.SetValue(moment().format('YYYY-MM-DD HH:mm:ss'));
|
|
||||||
$('#habit_id_text_input').trigger('change');
|
|
||||||
Grocy.FrontendHelpers.ValidateForm('habittracking-form');
|
|
||||||
},
|
|
||||||
function(xhr)
|
|
||||||
{
|
|
||||||
console.error(xhr);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
function(xhr)
|
|
||||||
{
|
|
||||||
console.error(xhr);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#habit_id').on('change', function(e)
|
|
||||||
{
|
|
||||||
var input = $('#habit_id_text_input').val().toString();
|
|
||||||
$('#habit_id_text_input').val(input);
|
|
||||||
$('#habit_id').data('combobox').refresh();
|
|
||||||
|
|
||||||
var habitId = $(e.target).val();
|
|
||||||
if (habitId)
|
|
||||||
{
|
|
||||||
Grocy.Components.HabitCard.Refresh(habitId);
|
|
||||||
Grocy.Components.DateTimePicker.GetInputElement().focus();
|
|
||||||
Grocy.FrontendHelpers.ValidateForm('habittracking-form');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.combobox').combobox({
|
|
||||||
appendId: '_text_input'
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#habit_id_text_input').focus();
|
|
||||||
$('#habit_id_text_input').trigger('change');
|
|
||||||
Grocy.FrontendHelpers.ValidateForm('habittracking-form');
|
|
||||||
|
|
||||||
$('#habittracking-form input').keyup(function (event)
|
|
||||||
{
|
|
||||||
Grocy.FrontendHelpers.ValidateForm('habittracking-form');
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#habittracking-form input').keydown(function(event)
|
|
||||||
{
|
|
||||||
if (event.keyCode === 13) //Enter
|
|
||||||
{
|
|
||||||
if (document.getElementById('habittracking-form').checkValidity() === false) //There is at least one validation error
|
|
||||||
{
|
|
||||||
event.preventDefault();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$('#save-habittracking-button').click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Grocy.Components.DateTimePicker.GetInputElement().on('keypress', function(e)
|
|
||||||
{
|
|
||||||
Grocy.FrontendHelpers.ValidateForm('habittracking-form');
|
|
||||||
});
|
|
@@ -118,9 +118,10 @@ $('#inventory-form input').keydown(function(event)
|
|||||||
{
|
{
|
||||||
if (event.keyCode === 13) //Enter
|
if (event.keyCode === 13) //Enter
|
||||||
{
|
{
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
if (document.getElementById('inventory-form').checkValidity() === false) //There is at least one validation error
|
if (document.getElementById('inventory-form').checkValidity() === false) //There is at least one validation error
|
||||||
{
|
{
|
||||||
event.preventDefault();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
console.error(xhr);
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
console.error(xhr);
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -39,9 +39,10 @@ $('#location-form input').keydown(function (event)
|
|||||||
{
|
{
|
||||||
if (event.keyCode === 13) //Enter
|
if (event.keyCode === 13) //Enter
|
||||||
{
|
{
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
if (document.getElementById('location-form').checkValidity() === false) //There is at least one validation error
|
if (document.getElementById('location-form').checkValidity() === false) //There is at least one validation error
|
||||||
{
|
{
|
||||||
event.preventDefault();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@@ -7,7 +7,16 @@
|
|||||||
'language': JSON.parse(L('datatables_localization')),
|
'language': JSON.parse(L('datatables_localization')),
|
||||||
'scrollY': false,
|
'scrollY': false,
|
||||||
'colReorder': true,
|
'colReorder': true,
|
||||||
'stateSave': true
|
'stateSave': true,
|
||||||
|
'stateSaveParams': function(settings, data)
|
||||||
|
{
|
||||||
|
data.search.search = "";
|
||||||
|
|
||||||
|
data.columns.forEach(column =>
|
||||||
|
{
|
||||||
|
column.search.search = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#search").on("keyup", function()
|
$("#search").on("keyup", function()
|
||||||
|
@@ -7,7 +7,16 @@
|
|||||||
'language': JSON.parse(L('datatables_localization')),
|
'language': JSON.parse(L('datatables_localization')),
|
||||||
'scrollY': false,
|
'scrollY': false,
|
||||||
'colReorder': true,
|
'colReorder': true,
|
||||||
'stateSave': true
|
'stateSave': true,
|
||||||
|
'stateSaveParams': function(settings, data)
|
||||||
|
{
|
||||||
|
data.search.search = "";
|
||||||
|
|
||||||
|
data.columns.forEach(column =>
|
||||||
|
{
|
||||||
|
column.search.search = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var createdApiKeyId = GetUriParam('CreatedApiKeyId');
|
var createdApiKeyId = GetUriParam('CreatedApiKeyId');
|
||||||
|
@@ -18,7 +18,7 @@
|
|||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
console.error(xhr);
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
console.error(xhr);
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -99,9 +99,10 @@ $('#product-form input').keydown(function(event)
|
|||||||
{
|
{
|
||||||
if (event.keyCode === 13) //Enter
|
if (event.keyCode === 13) //Enter
|
||||||
{
|
{
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
if (document.getElementById('product-form').checkValidity() === false) //There is at least one validation error
|
if (document.getElementById('product-form').checkValidity() === false) //There is at least one validation error
|
||||||
{
|
{
|
||||||
event.preventDefault();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
56
public/viewjs/productgroupform.js
Normal file
56
public/viewjs/productgroupform.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
$('#save-product-group-button').on('click', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (Grocy.EditMode === 'create')
|
||||||
|
{
|
||||||
|
Grocy.Api.Post('add-object/product_groups', $('#product-group-form').serializeJSON(),
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
window.location.href = U('/productgroups');
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Grocy.Api.Post('edit-object/product_groups/' + Grocy.EditObjectId, $('#product-group-form').serializeJSON(),
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
window.location.href = U('/productgroups');
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#product-group-form input').keyup(function (event)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('product-group-form');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#product-group-form input').keydown(function (event)
|
||||||
|
{
|
||||||
|
if (event.keyCode === 13) //Enter
|
||||||
|
{
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (document.getElementById('product-group-form').checkValidity() === false) //There is at least one validation error
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$('#save-product-group-button').click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#name').focus();
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('product-group-form');
|
67
public/viewjs/productgroups.js
Normal file
67
public/viewjs/productgroups.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
var groupsTable = $('#productgroups-table').DataTable({
|
||||||
|
'paginate': false,
|
||||||
|
'order': [[1, 'asc']],
|
||||||
|
'columnDefs': [
|
||||||
|
{ 'orderable': false, 'targets': 0 }
|
||||||
|
],
|
||||||
|
'language': JSON.parse(L('datatables_localization')),
|
||||||
|
'scrollY': false,
|
||||||
|
'colReorder': true,
|
||||||
|
'stateSave': true,
|
||||||
|
'stateSaveParams': function(settings, data)
|
||||||
|
{
|
||||||
|
data.search.search = "";
|
||||||
|
|
||||||
|
data.columns.forEach(column =>
|
||||||
|
{
|
||||||
|
column.search.search = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#search").on("keyup", function()
|
||||||
|
{
|
||||||
|
var value = $(this).val();
|
||||||
|
if (value === "all")
|
||||||
|
{
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
groupsTable.search(value).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.product-group-delete-button', function(e)
|
||||||
|
{
|
||||||
|
var objectName = $(e.currentTarget).attr('data-group-name');
|
||||||
|
var objectId = $(e.currentTarget).attr('data-group-id');
|
||||||
|
|
||||||
|
bootbox.confirm({
|
||||||
|
message: L('Are you sure to delete product group "#1"?', objectName),
|
||||||
|
buttons: {
|
||||||
|
confirm: {
|
||||||
|
label: L('Yes'),
|
||||||
|
className: 'btn-success'
|
||||||
|
},
|
||||||
|
cancel: {
|
||||||
|
label: L('No'),
|
||||||
|
className: 'btn-danger'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
callback: function(result)
|
||||||
|
{
|
||||||
|
if (result === true)
|
||||||
|
{
|
||||||
|
Grocy.Api.Get('delete-object/product_groups/' + objectId,
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
window.location.href = U('/productgroups');
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
@@ -7,7 +7,16 @@
|
|||||||
'language': JSON.parse(L('datatables_localization')),
|
'language': JSON.parse(L('datatables_localization')),
|
||||||
'scrollY': false,
|
'scrollY': false,
|
||||||
'colReorder': true,
|
'colReorder': true,
|
||||||
'stateSave': true
|
'stateSave': true,
|
||||||
|
'stateSaveParams': function(settings, data)
|
||||||
|
{
|
||||||
|
data.search.search = "";
|
||||||
|
|
||||||
|
data.columns.forEach(column =>
|
||||||
|
{
|
||||||
|
column.search.search = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#search").on("keyup", function()
|
$("#search").on("keyup", function()
|
||||||
|
@@ -144,9 +144,10 @@ $('#purchase-form input').keydown(function(event)
|
|||||||
{
|
{
|
||||||
if (event.keyCode === 13) //Enter
|
if (event.keyCode === 13) //Enter
|
||||||
{
|
{
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
if (document.getElementById('purchase-form').checkValidity() === false) //There is at least one validation error
|
if (document.getElementById('purchase-form').checkValidity() === false) //There is at least one validation error
|
||||||
{
|
{
|
||||||
event.preventDefault();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
console.error(xhr);
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
console.error(xhr);
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -39,9 +39,10 @@ $('#quantityunit-form input').keydown(function(event)
|
|||||||
{
|
{
|
||||||
if (event.keyCode === 13) //Enter
|
if (event.keyCode === 13) //Enter
|
||||||
{
|
{
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
if (document.getElementById('quantityunit-form').checkValidity() === false) //There is at least one validation error
|
if (document.getElementById('quantityunit-form').checkValidity() === false) //There is at least one validation error
|
||||||
{
|
{
|
||||||
event.preventDefault();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@@ -7,7 +7,16 @@
|
|||||||
'language': JSON.parse(L('datatables_localization')),
|
'language': JSON.parse(L('datatables_localization')),
|
||||||
'scrollY': false,
|
'scrollY': false,
|
||||||
'colReorder': true,
|
'colReorder': true,
|
||||||
'stateSave': true
|
'stateSave': true,
|
||||||
|
'stateSaveParams': function(settings, data)
|
||||||
|
{
|
||||||
|
data.search.search = "";
|
||||||
|
|
||||||
|
data.columns.forEach(column =>
|
||||||
|
{
|
||||||
|
column.search.search = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#search").on("keyup", function()
|
$("#search").on("keyup", function()
|
||||||
|
@@ -23,7 +23,16 @@ var recipesPosTables = $('#recipes-pos-table').DataTable({
|
|||||||
'language': JSON.parse(L('datatables_localization')),
|
'language': JSON.parse(L('datatables_localization')),
|
||||||
'scrollY': false,
|
'scrollY': false,
|
||||||
'colReorder': true,
|
'colReorder': true,
|
||||||
'stateSave': true
|
'stateSave': true,
|
||||||
|
'stateSaveParams': function(settings, data)
|
||||||
|
{
|
||||||
|
data.search.search = "";
|
||||||
|
|
||||||
|
data.columns.forEach(column =>
|
||||||
|
{
|
||||||
|
column.search.search = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#search").on("keyup", function ()
|
$("#search").on("keyup", function ()
|
||||||
@@ -49,9 +58,10 @@ $('#recipe-form input').keydown(function (event)
|
|||||||
{
|
{
|
||||||
if (event.keyCode === 13) //Enter
|
if (event.keyCode === 13) //Enter
|
||||||
{
|
{
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
if (document.getElementById('recipe-form').checkValidity() === false) //There is at least one validation error
|
if (document.getElementById('recipe-form').checkValidity() === false) //There is at least one validation error
|
||||||
{
|
{
|
||||||
event.preventDefault();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
var jsonData = $('#recipe-pos-form').serializeJSON({ checkboxUncheckedValue: "0" });
|
var jsonData = $('#recipe-pos-form').serializeJSON({ checkboxUncheckedValue: "0" });
|
||||||
jsonData.recipe_id = Grocy.EditObjectParentId;
|
jsonData.recipe_id = Grocy.EditObjectParentId;
|
||||||
console.log(jsonData);
|
|
||||||
if (Grocy.EditMode === 'create')
|
if (Grocy.EditMode === 'create')
|
||||||
{
|
{
|
||||||
Grocy.Api.Post('add-object/recipes_pos', jsonData,
|
Grocy.Api.Post('add-object/recipes_pos', jsonData,
|
||||||
@@ -14,7 +13,7 @@
|
|||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
console.error(xhr);
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -27,7 +26,7 @@
|
|||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
console.error(xhr);
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -88,9 +87,10 @@ $('#recipe-pos-form input').keydown(function(event)
|
|||||||
{
|
{
|
||||||
if (event.keyCode === 13) //Enter
|
if (event.keyCode === 13) //Enter
|
||||||
{
|
{
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
if (document.getElementById('recipe-pos-form').checkValidity() === false) //There is at least one validation error
|
if (document.getElementById('recipe-pos-form').checkValidity() === false) //There is at least one validation error
|
||||||
{
|
{
|
||||||
event.preventDefault();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@@ -5,6 +5,15 @@
|
|||||||
'scrollY': false,
|
'scrollY': false,
|
||||||
'colReorder': true,
|
'colReorder': true,
|
||||||
'stateSave': true,
|
'stateSave': true,
|
||||||
|
'stateSaveParams': function(settings, data)
|
||||||
|
{
|
||||||
|
data.search.search = "";
|
||||||
|
|
||||||
|
data.columns.forEach(column =>
|
||||||
|
{
|
||||||
|
column.search.search = "";
|
||||||
|
});
|
||||||
|
},
|
||||||
'select': 'single',
|
'select': 'single',
|
||||||
'initComplete': function()
|
'initComplete': function()
|
||||||
{
|
{
|
||||||
@@ -149,4 +158,5 @@ recipesTables.on('select', function(e, dt, type, indexes)
|
|||||||
$("#selectedRecipeToggleFullscreenButton").on('click', function(e)
|
$("#selectedRecipeToggleFullscreenButton").on('click', function(e)
|
||||||
{
|
{
|
||||||
$("#selectedRecipeCard").toggleClass("fullscreen");
|
$("#selectedRecipeCard").toggleClass("fullscreen");
|
||||||
|
$("#selectedRecipeCard .card-header").toggleClass("fixed-top");
|
||||||
});
|
});
|
||||||
|
@@ -1,13 +1,27 @@
|
|||||||
var shoppingListTable = $('#shoppinglist-table').DataTable({
|
var shoppingListTable = $('#shoppinglist-table').DataTable({
|
||||||
'paginate': false,
|
'paginate': false,
|
||||||
'order': [[1, 'asc']],
|
'order': [[1, 'asc']],
|
||||||
|
"orderFixed": [[3, 'asc']],
|
||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
{ 'orderable': false, 'targets': 0 }
|
{ 'orderable': false, 'targets': 0 },
|
||||||
|
{ 'visible': false, 'targets': 3 }
|
||||||
],
|
],
|
||||||
'language': JSON.parse(L('datatables_localization')),
|
'language': JSON.parse(L('datatables_localization')),
|
||||||
'scrollY': false,
|
'scrollY': false,
|
||||||
'colReorder': true,
|
'colReorder': true,
|
||||||
'stateSave': true
|
'stateSave': true,
|
||||||
|
'stateSaveParams': function(settings, data)
|
||||||
|
{
|
||||||
|
data.search.search = "";
|
||||||
|
|
||||||
|
data.columns.forEach(column =>
|
||||||
|
{
|
||||||
|
column.search.search = "";
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'rowGroup': {
|
||||||
|
dataSrc: 3
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#search").on("keyup", function()
|
$("#search").on("keyup", function()
|
||||||
@@ -21,8 +35,31 @@ $("#search").on("keyup", function()
|
|||||||
shoppingListTable.search(value).draw();
|
shoppingListTable.search(value).draw();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#status-filter").on("change", function()
|
||||||
|
{
|
||||||
|
var value = $(this).val();
|
||||||
|
if (value === "all")
|
||||||
|
{
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer CSS classes of selected element to dropdown element (for background)
|
||||||
|
$(this).attr("class", $("#" + $(this).attr("id") + " option[value='" + value + "']").attr("class") + " form-control");
|
||||||
|
|
||||||
|
shoppingListTable.column(4).search(value).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".status-filter-button").on("click", function()
|
||||||
|
{
|
||||||
|
var value = $(this).data("status-filter");
|
||||||
|
$("#status-filter").val(value);
|
||||||
|
$("#status-filter").trigger("change");
|
||||||
|
});
|
||||||
|
|
||||||
$(document).on('click', '.shoppinglist-delete-button', function (e)
|
$(document).on('click', '.shoppinglist-delete-button', function (e)
|
||||||
{
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
var shoppingListItemId = $(e.currentTarget).attr('data-shoppinglist-id');
|
var shoppingListItemId = $(e.currentTarget).attr('data-shoppinglist-id');
|
||||||
|
|
||||||
Grocy.Api.Get('delete-object/shopping_list/' + shoppingListItemId,
|
Grocy.Api.Get('delete-object/shopping_list/' + shoppingListItemId,
|
||||||
|
@@ -85,9 +85,10 @@ $('#shoppinglist-form input').keydown(function (event)
|
|||||||
{
|
{
|
||||||
if (event.keyCode === 13) //Enter
|
if (event.keyCode === 13) //Enter
|
||||||
{
|
{
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
if (document.getElementById('shoppinglist-form').checkValidity() === false) //There is at least one validation error
|
if (document.getElementById('shoppinglist-form').checkValidity() === false) //There is at least one validation error
|
||||||
{
|
{
|
||||||
event.preventDefault();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@@ -8,7 +8,16 @@
|
|||||||
'language': JSON.parse(L('datatables_localization')),
|
'language': JSON.parse(L('datatables_localization')),
|
||||||
'scrollY': false,
|
'scrollY': false,
|
||||||
'colReorder': true,
|
'colReorder': true,
|
||||||
'stateSave': true
|
'stateSave': true,
|
||||||
|
'stateSaveParams': function(settings, data)
|
||||||
|
{
|
||||||
|
data.search.search = "";
|
||||||
|
|
||||||
|
data.columns.forEach(column =>
|
||||||
|
{
|
||||||
|
column.search.search = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#location-filter").on("change", function()
|
$("#location-filter").on("change", function()
|
||||||
@@ -22,6 +31,27 @@ $("#location-filter").on("change", function()
|
|||||||
stockOverviewTable.column(4).search(value).draw();
|
stockOverviewTable.column(4).search(value).draw();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#status-filter").on("change", function()
|
||||||
|
{
|
||||||
|
var value = $(this).val();
|
||||||
|
if (value === "all")
|
||||||
|
{
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer CSS classes of selected element to dropdown element (for background)
|
||||||
|
$(this).attr("class", $("#" + $(this).attr("id") + " option[value='" + value + "']").attr("class") + " form-control");
|
||||||
|
|
||||||
|
stockOverviewTable.column(5).search(value).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".status-filter-button").on("click", function()
|
||||||
|
{
|
||||||
|
var value = $(this).data("status-filter");
|
||||||
|
$("#status-filter").val(value);
|
||||||
|
$("#status-filter").trigger("change");
|
||||||
|
});
|
||||||
|
|
||||||
$("#search").on("keyup", function()
|
$("#search").on("keyup", function()
|
||||||
{
|
{
|
||||||
var value = $(this).val();
|
var value = $(this).val();
|
||||||
@@ -35,6 +65,12 @@ $("#search").on("keyup", function()
|
|||||||
|
|
||||||
$(document).on('click', '.product-consume-button', function(e)
|
$(document).on('click', '.product-consume-button', function(e)
|
||||||
{
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Remove the focus from the current button
|
||||||
|
// to prevent that the tooltip stays until clicked anywhere else
|
||||||
|
document.activeElement.blur();
|
||||||
|
|
||||||
var productId = $(e.currentTarget).attr('data-product-id');
|
var productId = $(e.currentTarget).attr('data-product-id');
|
||||||
var productName = $(e.currentTarget).attr('data-product-name');
|
var productName = $(e.currentTarget).attr('data-product-name');
|
||||||
var productQuName = $(e.currentTarget).attr('data-product-qu-name');
|
var productQuName = $(e.currentTarget).attr('data-product-qu-name');
|
||||||
|
67
public/viewjs/taskcategories.js
Normal file
67
public/viewjs/taskcategories.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
var categoriesTable = $('#taskcategories-table').DataTable({
|
||||||
|
'paginate': false,
|
||||||
|
'order': [[1, 'asc']],
|
||||||
|
'columnDefs': [
|
||||||
|
{ 'orderable': false, 'targets': 0 }
|
||||||
|
],
|
||||||
|
'language': JSON.parse(L('datatables_localization')),
|
||||||
|
'scrollY': false,
|
||||||
|
'colReorder': true,
|
||||||
|
'stateSave': true,
|
||||||
|
'stateSaveParams': function(settings, data)
|
||||||
|
{
|
||||||
|
data.search.search = "";
|
||||||
|
|
||||||
|
data.columns.forEach(column =>
|
||||||
|
{
|
||||||
|
column.search.search = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#search").on("keyup", function()
|
||||||
|
{
|
||||||
|
var value = $(this).val();
|
||||||
|
if (value === "all")
|
||||||
|
{
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
categoriesTable.search(value).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.task-category-delete-button', function (e)
|
||||||
|
{
|
||||||
|
var objectName = $(e.currentTarget).attr('data-category-name');
|
||||||
|
var objectId = $(e.currentTarget).attr('data-category-id');
|
||||||
|
|
||||||
|
bootbox.confirm({
|
||||||
|
message: L('Are you sure to delete task category "#1"?', objectName),
|
||||||
|
buttons: {
|
||||||
|
confirm: {
|
||||||
|
label: L('Yes'),
|
||||||
|
className: 'btn-success'
|
||||||
|
},
|
||||||
|
cancel: {
|
||||||
|
label: L('No'),
|
||||||
|
className: 'btn-danger'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
callback: function(result)
|
||||||
|
{
|
||||||
|
if (result === true)
|
||||||
|
{
|
||||||
|
Grocy.Api.Get('delete-object/task_categories/' + objectId,
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
window.location.href = U('/taskcategories');
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
56
public/viewjs/taskcategoryform.js
Normal file
56
public/viewjs/taskcategoryform.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
$('#save-task-category-button').on('click', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (Grocy.EditMode === 'create')
|
||||||
|
{
|
||||||
|
Grocy.Api.Post('add-object/task_categories', $('#task-category-form').serializeJSON(),
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
window.location.href = U('/taskcategories');
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Grocy.Api.Post('edit-object/task_categories/' + Grocy.EditObjectId, $('#task-category-form').serializeJSON(),
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
window.location.href = U('/taskcategories');
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#task-category-form input').keyup(function (event)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('task-category-form');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#task-category-form input').keydown(function (event)
|
||||||
|
{
|
||||||
|
if (event.keyCode === 13) //Enter
|
||||||
|
{
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (document.getElementById('task-category-form').checkValidity() === false) //There is at least one validation error
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$('#save-task-category-button').click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#name').focus();
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('task-category-form');
|
61
public/viewjs/taskform.js
Normal file
61
public/viewjs/taskform.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
$('#save-task-button').on('click', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var jsonData = $('#task-form').serializeJSON();
|
||||||
|
jsonData.assigned_to_user_id = jsonData.user_id;
|
||||||
|
delete jsonData.user_id;
|
||||||
|
jsonData.due_date = Grocy.Components.DateTimePicker.GetValue();
|
||||||
|
|
||||||
|
if (Grocy.EditMode === 'create')
|
||||||
|
{
|
||||||
|
Grocy.Api.Post('add-object/tasks', jsonData,
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
window.location.href = U('/tasks');
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Grocy.Api.Post('edit-object/tasks/' + Grocy.EditObjectId, jsonData,
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
window.location.href = U('/tasks');
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#task-form input').keyup(function(event)
|
||||||
|
{
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('task-form');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#task-form input').keydown(function(event)
|
||||||
|
{
|
||||||
|
if (event.keyCode === 13) //Enter
|
||||||
|
{
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (document.getElementById('task-form').checkValidity() === false) //There is at least one validation error
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$('#save-task-button').click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#name').focus();
|
||||||
|
Grocy.FrontendHelpers.ValidateForm('task-form');
|
188
public/viewjs/tasks.js
Normal file
188
public/viewjs/tasks.js
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
var tasksTable = $('#tasks-table').DataTable({
|
||||||
|
'paginate': false,
|
||||||
|
'order': [[2, 'desc']],
|
||||||
|
'columnDefs': [
|
||||||
|
{ 'orderable': false, 'targets': 0 },
|
||||||
|
{ 'visible': false, 'targets': 3 }
|
||||||
|
],
|
||||||
|
'language': JSON.parse(L('datatables_localization')),
|
||||||
|
'scrollY': false,
|
||||||
|
'colReorder': true,
|
||||||
|
'stateSave': true,
|
||||||
|
'stateSaveParams': function(settings, data)
|
||||||
|
{
|
||||||
|
data.search.search = "";
|
||||||
|
|
||||||
|
data.columns.forEach(column =>
|
||||||
|
{
|
||||||
|
column.search.search = "";
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'rowGroup': {
|
||||||
|
dataSrc: 3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#search").on("keyup", function()
|
||||||
|
{
|
||||||
|
var value = $(this).val();
|
||||||
|
if (value === "all")
|
||||||
|
{
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
tasksTable.search(value).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#status-filter").on("change", function()
|
||||||
|
{
|
||||||
|
var value = $(this).val();
|
||||||
|
if (value === "all")
|
||||||
|
{
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer CSS classes of selected element to dropdown element (for background)
|
||||||
|
$(this).attr("class", $("#" + $(this).attr("id") + " option[value='" + value + "']").attr("class") + " form-control");
|
||||||
|
|
||||||
|
tasksTable.column(5).search(value).draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".status-filter-button").on("click", function()
|
||||||
|
{
|
||||||
|
var value = $(this).data("status-filter");
|
||||||
|
$("#status-filter").val(value);
|
||||||
|
$("#status-filter").trigger("change");
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.do-task-button', function(e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Remove the focus from the current button
|
||||||
|
// to prevent that the tooltip stays until clicked anywhere else
|
||||||
|
document.activeElement.blur();
|
||||||
|
|
||||||
|
var taskId = $(e.currentTarget).attr('data-task-id');
|
||||||
|
var taskName = $(e.currentTarget).attr('data-task-name');
|
||||||
|
var doneTime = moment().format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
|
||||||
|
Grocy.Api.Get('tasks/mark-task-as-completed/' + taskId + '?done_time=' + doneTime,
|
||||||
|
function()
|
||||||
|
{
|
||||||
|
if (!$("#show-done-tasks").is(":checked"))
|
||||||
|
{
|
||||||
|
$('#task-' + taskId + '-row').fadeOut(500, function ()
|
||||||
|
{
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$('#task-' + taskId + '-row').addClass("text-muted");
|
||||||
|
$('#task-' + taskId + '-name').addClass("text-strike-through");
|
||||||
|
$('.do-task-button[data-task-id="' + taskId + '"]').addClass("disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
toastr.success(L('Marked task #1 as completed on #2', taskName, doneTime));
|
||||||
|
RefreshContextualTimeago();
|
||||||
|
RefreshStatistics();
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.delete-task-button', function (e)
|
||||||
|
{
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
var objectName = $(e.currentTarget).attr('data-task-name');
|
||||||
|
var objectId = $(e.currentTarget).attr('data-task-id');
|
||||||
|
|
||||||
|
bootbox.confirm({
|
||||||
|
message: L('Are you sure to delete task "#1"?', objectName),
|
||||||
|
buttons: {
|
||||||
|
confirm: {
|
||||||
|
label: L('Yes'),
|
||||||
|
className: 'btn-success'
|
||||||
|
},
|
||||||
|
cancel: {
|
||||||
|
label: L('No'),
|
||||||
|
className: 'btn-danger'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
callback: function(result)
|
||||||
|
{
|
||||||
|
if (result === true)
|
||||||
|
{
|
||||||
|
Grocy.Api.Get('delete-object/tasks/' + objectId,
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
$('#task-' + objectId + '-row').fadeOut(500, function ()
|
||||||
|
{
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#show-done-tasks").change(function()
|
||||||
|
{
|
||||||
|
if (this.checked)
|
||||||
|
{
|
||||||
|
window.location.href = U('/tasks?include_done');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
window.location.href = U('/tasks');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (GetUriParam('include_done'))
|
||||||
|
{
|
||||||
|
$("#show-done-tasks").prop('checked', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function RefreshStatistics()
|
||||||
|
{
|
||||||
|
var nextXDays = $("#info-due-tasks").data("next-x-days");
|
||||||
|
Grocy.Api.Get('tasks/get-current',
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
var dueCount = 0;
|
||||||
|
var overdueCount = 0;
|
||||||
|
var now = moment();
|
||||||
|
var nextXDaysThreshold = moment().add(nextXDays, "days");
|
||||||
|
result.forEach(element => {
|
||||||
|
var date = moment(element.due_date);
|
||||||
|
if (date.isBefore(now))
|
||||||
|
{
|
||||||
|
overdueCount++;
|
||||||
|
}
|
||||||
|
else if (date.isBefore(nextXDaysThreshold))
|
||||||
|
{
|
||||||
|
dueCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#info-due-tasks").text(Pluralize(dueCount, L('#1 task is due to be done within the next #2 days', dueCount, nextXDays), L('#1 tasks are due to be done within the next #2 days', dueCount, nextXDays)));
|
||||||
|
$("#info-overdue-tasks").text(Pluralize(overdueCount, L('#1 task is overdue to be done', overdueCount), L('#1 tasks are overdue to be done', overdueCount)));
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
RefreshStatistics();
|
@@ -49,9 +49,10 @@ $('#user-form input').keydown(function (event)
|
|||||||
{
|
{
|
||||||
if (event.keyCode === 13) //Enter
|
if (event.keyCode === 13) //Enter
|
||||||
{
|
{
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
if (document.getElementById('user-form').checkValidity() === false) //There is at least one validation error
|
if (document.getElementById('user-form').checkValidity() === false) //There is at least one validation error
|
||||||
{
|
{
|
||||||
event.preventDefault();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@@ -7,7 +7,16 @@
|
|||||||
'language': JSON.parse(L('datatables_localization')),
|
'language': JSON.parse(L('datatables_localization')),
|
||||||
'scrollY': false,
|
'scrollY': false,
|
||||||
'colReorder': true,
|
'colReorder': true,
|
||||||
'stateSave': true
|
'stateSave': true,
|
||||||
|
'stateSaveParams': function(settings, data)
|
||||||
|
{
|
||||||
|
data.search.search = "";
|
||||||
|
|
||||||
|
data.columns.forEach(column =>
|
||||||
|
{
|
||||||
|
column.search.search = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#search").on("keyup", function()
|
$("#search").on("keyup", function()
|
||||||
|
43
routes.php
43
routes.php
@@ -30,6 +30,8 @@ $app->group('', function()
|
|||||||
$this->get('/location/{locationId}', '\Grocy\Controllers\StockController:LocationEditForm');
|
$this->get('/location/{locationId}', '\Grocy\Controllers\StockController:LocationEditForm');
|
||||||
$this->get('/quantityunits', '\Grocy\Controllers\StockController:QuantityUnitsList');
|
$this->get('/quantityunits', '\Grocy\Controllers\StockController:QuantityUnitsList');
|
||||||
$this->get('/quantityunit/{quantityunitId}', '\Grocy\Controllers\StockController:QuantityUnitEditForm');
|
$this->get('/quantityunit/{quantityunitId}', '\Grocy\Controllers\StockController:QuantityUnitEditForm');
|
||||||
|
$this->get('/productgroups', '\Grocy\Controllers\StockController:ProductGroupsList');
|
||||||
|
$this->get('/productgroup/{productGroupId}', '\Grocy\Controllers\StockController:ProductGroupEditForm');
|
||||||
$this->get('/shoppinglist', '\Grocy\Controllers\StockController:ShoppingList');
|
$this->get('/shoppinglist', '\Grocy\Controllers\StockController:ShoppingList');
|
||||||
$this->get('/shoppinglistitem/{itemId}', '\Grocy\Controllers\StockController:ShoppingListItemEditForm');
|
$this->get('/shoppinglistitem/{itemId}', '\Grocy\Controllers\StockController:ShoppingListItemEditForm');
|
||||||
|
|
||||||
@@ -38,13 +40,13 @@ $app->group('', function()
|
|||||||
$this->get('/recipe/{recipeId}', '\Grocy\Controllers\RecipesController:RecipeEditForm');
|
$this->get('/recipe/{recipeId}', '\Grocy\Controllers\RecipesController:RecipeEditForm');
|
||||||
$this->get('/recipe/{recipeId}/pos/{recipePosId}', '\Grocy\Controllers\RecipesController:RecipePosEditForm');
|
$this->get('/recipe/{recipeId}/pos/{recipePosId}', '\Grocy\Controllers\RecipesController:RecipePosEditForm');
|
||||||
|
|
||||||
// Habit routes
|
// Chore routes
|
||||||
$this->get('/habitsoverview', '\Grocy\Controllers\HabitsController:Overview');
|
$this->get('/choresoverview', '\Grocy\Controllers\ChoresController:Overview');
|
||||||
$this->get('/habittracking', '\Grocy\Controllers\HabitsController:TrackHabitExecution');
|
$this->get('/choretracking', '\Grocy\Controllers\ChoresController:TrackChoreExecution');
|
||||||
$this->get('/habitsanalysis', '\Grocy\Controllers\HabitsController:Analysis');
|
$this->get('/choresanalysis', '\Grocy\Controllers\ChoresController:Analysis');
|
||||||
|
|
||||||
$this->get('/habits', '\Grocy\Controllers\HabitsController:HabitsList');
|
$this->get('/chores', '\Grocy\Controllers\ChoresController:ChoresList');
|
||||||
$this->get('/habit/{habitId}', '\Grocy\Controllers\HabitsController:HabitEditForm');
|
$this->get('/chore/{choreId}', '\Grocy\Controllers\ChoresController:ChoreEditForm');
|
||||||
|
|
||||||
// Battery routes
|
// Battery routes
|
||||||
$this->get('/batteriesoverview', '\Grocy\Controllers\BatteriesController:Overview');
|
$this->get('/batteriesoverview', '\Grocy\Controllers\BatteriesController:Overview');
|
||||||
@@ -53,6 +55,12 @@ $app->group('', function()
|
|||||||
$this->get('/batteries', '\Grocy\Controllers\BatteriesController:BatteriesList');
|
$this->get('/batteries', '\Grocy\Controllers\BatteriesController:BatteriesList');
|
||||||
$this->get('/battery/{batteryId}', '\Grocy\Controllers\BatteriesController:BatteryEditForm');
|
$this->get('/battery/{batteryId}', '\Grocy\Controllers\BatteriesController:BatteryEditForm');
|
||||||
|
|
||||||
|
// Task routes
|
||||||
|
$this->get('/tasks', '\Grocy\Controllers\TasksController:Overview');
|
||||||
|
$this->get('/task/{taskId}', '\Grocy\Controllers\TasksController:TaskEditForm');
|
||||||
|
$this->get('/taskcategories', '\Grocy\Controllers\TasksController:TaskCategoriesList');
|
||||||
|
$this->get('/taskcategory/{categoryId}', '\Grocy\Controllers\TasksController:TaskCategoryEditForm');
|
||||||
|
|
||||||
// OpenAPI routes
|
// OpenAPI routes
|
||||||
$this->get('/api', '\Grocy\Controllers\OpenApiController:DocumentationUi');
|
$this->get('/api', '\Grocy\Controllers\OpenApiController:DocumentationUi');
|
||||||
$this->get('/manageapikeys', '\Grocy\Controllers\OpenApiController:ApiKeysList');
|
$this->get('/manageapikeys', '\Grocy\Controllers\OpenApiController:ApiKeysList');
|
||||||
@@ -71,12 +79,23 @@ $app->group('/api', function()
|
|||||||
$this->post('/edit-object/{entity}/{objectId}', '\Grocy\Controllers\GenericEntityApiController:EditObject');
|
$this->post('/edit-object/{entity}/{objectId}', '\Grocy\Controllers\GenericEntityApiController:EditObject');
|
||||||
$this->get('/delete-object/{entity}/{objectId}', '\Grocy\Controllers\GenericEntityApiController:DeleteObject');
|
$this->get('/delete-object/{entity}/{objectId}', '\Grocy\Controllers\GenericEntityApiController:DeleteObject');
|
||||||
|
|
||||||
|
// System
|
||||||
|
$this->get('/system/get-db-changed-time', '\Grocy\Controllers\SystemApiController:GetDbChangedTime');
|
||||||
|
$this->post('/system/log-missing-localization', '\Grocy\Controllers\SystemApiController:LogMissingLocalization');
|
||||||
|
|
||||||
|
// Files
|
||||||
|
$this->post('/files/upload/{group}', '\Grocy\Controllers\FilesApiController:Upload');
|
||||||
|
|
||||||
// Users
|
// Users
|
||||||
$this->get('/users/get', '\Grocy\Controllers\UsersApiController:GetUsers');
|
$this->get('/users/get', '\Grocy\Controllers\UsersApiController:GetUsers');
|
||||||
$this->post('/users/create', '\Grocy\Controllers\UsersApiController:CreateUser');
|
$this->post('/users/create', '\Grocy\Controllers\UsersApiController:CreateUser');
|
||||||
$this->post('/users/edit/{userId}', '\Grocy\Controllers\UsersApiController:EditUser');
|
$this->post('/users/edit/{userId}', '\Grocy\Controllers\UsersApiController:EditUser');
|
||||||
$this->get('/users/delete/{userId}', '\Grocy\Controllers\UsersApiController:DeleteUser');
|
$this->get('/users/delete/{userId}', '\Grocy\Controllers\UsersApiController:DeleteUser');
|
||||||
|
|
||||||
|
// User
|
||||||
|
$this->get('/user/settings/{settingKey}', '\Grocy\Controllers\UsersApiController:GetUserSetting');
|
||||||
|
$this->post('/user/settings/{settingKey}', '\Grocy\Controllers\UsersApiController:SetUserSetting');
|
||||||
|
|
||||||
// Stock
|
// Stock
|
||||||
$this->get('/stock/add-product/{productId}/{amount}', '\Grocy\Controllers\StockApiController:AddProduct');
|
$this->get('/stock/add-product/{productId}/{amount}', '\Grocy\Controllers\StockApiController:AddProduct');
|
||||||
$this->get('/stock/consume-product/{productId}/{amount}', '\Grocy\Controllers\StockApiController:ConsumeProduct');
|
$this->get('/stock/consume-product/{productId}/{amount}', '\Grocy\Controllers\StockApiController:ConsumeProduct');
|
||||||
@@ -93,15 +112,19 @@ $app->group('/api', function()
|
|||||||
$this->get('/recipes/add-not-fulfilled-products-to-shopping-list/{recipeId}', '\Grocy\Controllers\RecipesApiController:AddNotFulfilledProductsToShoppingList');
|
$this->get('/recipes/add-not-fulfilled-products-to-shopping-list/{recipeId}', '\Grocy\Controllers\RecipesApiController:AddNotFulfilledProductsToShoppingList');
|
||||||
$this->get('/recipes/consume-recipe/{recipeId}', '\Grocy\Controllers\RecipesApiController:ConsumeRecipe');
|
$this->get('/recipes/consume-recipe/{recipeId}', '\Grocy\Controllers\RecipesApiController:ConsumeRecipe');
|
||||||
|
|
||||||
// Habits
|
// Chores
|
||||||
$this->get('/habits/track-habit-execution/{habitId}', '\Grocy\Controllers\HabitsApiController:TrackHabitExecution');
|
$this->get('/chores/track-chore-execution/{choreId}', '\Grocy\Controllers\ChoresApiController:TrackChoreExecution');
|
||||||
$this->get('/habits/get-habit-details/{habitId}', '\Grocy\Controllers\HabitsApiController:HabitDetails');
|
$this->get('/chores/get-chore-details/{choreId}', '\Grocy\Controllers\ChoresApiController:ChoreDetails');
|
||||||
$this->get('/habits/get-current', '\Grocy\Controllers\HabitsApiController:Current');
|
$this->get('/chores/get-current', '\Grocy\Controllers\ChoresApiController:Current');
|
||||||
|
|
||||||
// Batteries
|
// Batteries
|
||||||
$this->get('/batteries/track-charge-cycle/{batteryId}', '\Grocy\Controllers\BatteriesApiController:TrackChargeCycle');
|
$this->get('/batteries/track-charge-cycle/{batteryId}', '\Grocy\Controllers\BatteriesApiController:TrackChargeCycle');
|
||||||
$this->get('/batteries/get-battery-details/{batteryId}', '\Grocy\Controllers\BatteriesApiController:BatteryDetails');
|
$this->get('/batteries/get-battery-details/{batteryId}', '\Grocy\Controllers\BatteriesApiController:BatteryDetails');
|
||||||
$this->get('/batteries/get-current', '\Grocy\Controllers\BatteriesApiController:Current');
|
$this->get('/batteries/get-current', '\Grocy\Controllers\BatteriesApiController:Current');
|
||||||
|
|
||||||
|
// Tasks
|
||||||
|
$this->get('/tasks/get-current', '\Grocy\Controllers\TasksApiController:Current');
|
||||||
|
$this->get('/tasks/mark-task-as-completed/{taskId}', '\Grocy\Controllers\TasksApiController:MarkTaskAsCompleted');
|
||||||
})->add(new ApiKeyAuthMiddleware($appContainer, $appContainer->LoginControllerInstance->GetSessionCookieName(), $appContainer->ApiKeyHeaderName))
|
})->add(new ApiKeyAuthMiddleware($appContainer, $appContainer->LoginControllerInstance->GetSessionCookieName(), $appContainer->ApiKeyHeaderName))
|
||||||
->add(JsonMiddleware::class)
|
->add(JsonMiddleware::class)
|
||||||
->add(new CorsMiddleware([
|
->add(new CorsMiddleware([
|
||||||
|
74
services/ChoresService.php
Normal file
74
services/ChoresService.php
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Services;
|
||||||
|
|
||||||
|
class ChoresService extends BaseService
|
||||||
|
{
|
||||||
|
const CHORE_TYPE_MANUALLY = 'manually';
|
||||||
|
const CHORE_TYPE_DYNAMIC_REGULAR = 'dynamic-regular';
|
||||||
|
|
||||||
|
public function GetCurrent()
|
||||||
|
{
|
||||||
|
$sql = 'SELECT * from chores_current';
|
||||||
|
return $this->DatabaseService->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetChoreDetails(int $choreId)
|
||||||
|
{
|
||||||
|
if (!$this->ChoreExists($choreId))
|
||||||
|
{
|
||||||
|
throw new \Exception('Chore does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
$chore = $this->Database->chores($choreId);
|
||||||
|
$choreTrackedCount = $this->Database->chores_log()->where('chore_id', $choreId)->count();
|
||||||
|
$choreLastTrackedTime = $this->Database->chores_log()->where('chore_id', $choreId)->max('tracked_time');
|
||||||
|
$nextExeuctionTime = $this->Database->chores_current()->where('chore_id', $choreId)->min('next_estimated_execution_time');
|
||||||
|
|
||||||
|
$lastChoreLogRow = $this->Database->chores_log()->where('chore_id = :1 AND tracked_time = :2', $choreId, $choreLastTrackedTime)->fetch();
|
||||||
|
$lastDoneByUser = null;
|
||||||
|
if ($lastChoreLogRow !== null && !empty($lastChoreLogRow))
|
||||||
|
{
|
||||||
|
$usersService = new UsersService();
|
||||||
|
$users = $usersService->GetUsersAsDto();
|
||||||
|
$lastDoneByUser = FindObjectInArrayByPropertyValue($users, 'id', $lastChoreLogRow->done_by_user_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'chore' => $chore,
|
||||||
|
'last_tracked' => $choreLastTrackedTime,
|
||||||
|
'tracked_count' => $choreTrackedCount,
|
||||||
|
'last_done_by' => $lastDoneByUser,
|
||||||
|
'next_estimated_execution_time' => $nextExeuctionTime
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function TrackChore(int $choreId, string $trackedTime, $doneBy = GROCY_USER_ID)
|
||||||
|
{
|
||||||
|
if (!$this->ChoreExists($choreId))
|
||||||
|
{
|
||||||
|
throw new \Exception('Chore does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
$userRow = $this->Database->users()->where('id = :1', $doneBy)->fetch();
|
||||||
|
if ($userRow === null)
|
||||||
|
{
|
||||||
|
throw new \Exception('User does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
$logRow = $this->Database->chores_log()->createRow(array(
|
||||||
|
'chore_id' => $choreId,
|
||||||
|
'tracked_time' => $trackedTime,
|
||||||
|
'done_by_user_id' => $doneBy
|
||||||
|
));
|
||||||
|
$logRow->save();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function ChoreExists($choreId)
|
||||||
|
{
|
||||||
|
$choreRow = $this->Database->chores()->where('id = :1', $choreId)->fetch();
|
||||||
|
return $choreRow !== null;
|
||||||
|
}
|
||||||
|
}
|
@@ -63,4 +63,9 @@ class DatabaseService
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function GetDbChangedTime()
|
||||||
|
{
|
||||||
|
return date('Y-m-d H:i:s', filemtime(GROCY_DATAPATH . '/grocy.db'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -31,29 +31,36 @@ class DemoDataGeneratorService extends BaseService
|
|||||||
INSERT INTO quantity_units (name, name_plural) VALUES ('{$localizationService->Localize('Bunch')}', '{$localizationService->Localize('Bunches')}'); --7
|
INSERT INTO quantity_units (name, name_plural) VALUES ('{$localizationService->Localize('Bunch')}', '{$localizationService->Localize('Bunches')}'); --7
|
||||||
INSERT INTO quantity_units (name, name_plural) VALUES ('{$localizationService->Localize('Gram')}', '{$localizationService->Localize('Grams')}'); --8
|
INSERT INTO quantity_units (name, name_plural) VALUES ('{$localizationService->Localize('Gram')}', '{$localizationService->Localize('Grams')}'); --8
|
||||||
|
|
||||||
|
INSERT INTO product_groups(name) VALUES ('01 {$localizationService->Localize('Sweets')}'); --1
|
||||||
|
INSERT INTO product_groups(name) VALUES ('02 {$localizationService->Localize('Bakery products')}'); --2
|
||||||
|
INSERT INTO product_groups(name) VALUES ('03 {$localizationService->Localize('Tinned food')}'); --3
|
||||||
|
INSERT INTO product_groups(name) VALUES ('04 {$localizationService->Localize('Butchery products')}'); --4
|
||||||
|
INSERT INTO product_groups(name) VALUES ('05 {$localizationService->Localize('Vegetables/Fruits')}'); --5
|
||||||
|
INSERT INTO product_groups(name) VALUES ('06 {$localizationService->Localize('Refrigerated products')}'); --6
|
||||||
|
|
||||||
DELETE FROM sqlite_sequence WHERE name = 'products'; --Just to keep IDs in order as mentioned here...
|
DELETE FROM sqlite_sequence WHERE name = 'products'; --Just to keep IDs in order as mentioned here...
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, min_stock_amount) VALUES ('{$localizationService->Localize('Cookies')}', 3, 3, 3, 1, 8); --1
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, min_stock_amount, product_group_id) VALUES ('{$localizationService->Localize('Cookies')}', 3, 3, 3, 1, 8, 1); --1
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, min_stock_amount) VALUES ('{$localizationService->Localize('Chocolate')}', 3, 3, 3, 1, 8); --2
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, min_stock_amount, product_group_id) VALUES ('{$localizationService->Localize('Chocolate')}', 3, 3, 3, 1, 8, 1); --2
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, min_stock_amount) VALUES ('{$localizationService->Localize('Gummy bears')}', 3, 3, 3, 1, 8); --3
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, min_stock_amount, product_group_id) VALUES ('{$localizationService->Localize('Gummy bears')}', 3, 3, 3, 1, 8, 1); --3
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, min_stock_amount) VALUES ('{$localizationService->Localize('Crisps')}', 3, 3, 3, 1, 10); --4
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, min_stock_amount, product_group_id) VALUES ('{$localizationService->Localize('Crisps')}', 3, 3, 3, 1, 10, 1); --4
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('{$localizationService->Localize('Eggs')}', 2, 3, 2, 10); --5
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->Localize('Eggs')}', 2, 3, 2, 10, 5); --5
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('{$localizationService->Localize('Noodles')}', 3, 3, 3, 1); --6
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->Localize('Noodles')}', 3, 3, 3, 1, 6); --6
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('{$localizationService->Localize('Pickles')}', 4,4, 4, 1); --7
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->Localize('Pickles')}', 4,4, 4, 1, 3); --7
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('{$localizationService->Localize('Gulash soup')}', 4, 5, 5, 1); --8
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->Localize('Gulash soup')}', 4, 5, 5, 1, 3); --8
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('{$localizationService->Localize('Yogurt')}', 2, 6, 6, 1); --9
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->Localize('Yogurt')}', 2, 6, 6, 1, 6); --9
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('{$localizationService->Localize('Cheese')}', 2, 3, 3, 1); --10
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->Localize('Cheese')}', 2, 3, 3, 1, 6); --10
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('{$localizationService->Localize('Cold cuts')}', 2, 3, 3, 1); --11
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->Localize('Cold cuts')}', 2, 3, 3, 1, 6); --11
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('{$localizationService->Localize('Paprika')}', 2, 2, 2, 1); --12
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->Localize('Paprika')}', 2, 2, 2, 1, 5); --12
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('{$localizationService->Localize('Cucumber')}', 2, 2, 2, 1); --13
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->Localize('Cucumber')}', 2, 2, 2, 1, 5); --13
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('{$localizationService->Localize('Radish')}', 2, 7, 7, 1); --14
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->Localize('Radish')}', 2, 7, 7, 1, 5); --14
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('{$localizationService->Localize('Tomato')}', 2, 2, 2, 1); --15
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->Localize('Tomato')}', 2, 2, 2, 1, 5); --15
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('{$localizationService->Localize('Pizza dough')}', 3, 3, 3, 1); --16
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->Localize('Pizza dough')}', 3, 3, 3, 1, 6); --16
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('{$localizationService->Localize('Sieved tomatoes')}', 4, 5, 5, 1); --17
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->Localize('Sieved tomatoes')}', 4, 5, 5, 1, 3); --17
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('{$localizationService->Localize('Salami')}', 2, 3, 3, 1); --18
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->Localize('Salami')}', 2, 3, 3, 1, 6); --18
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('{$localizationService->Localize('Toast')}', 4, 5, 5, 1); --19
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->Localize('Toast')}', 4, 5, 5, 1, 2); --19
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('{$localizationService->Localize('Minced meat')}', 2, 3, 3, 1); --20
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->Localize('Minced meat')}', 2, 3, 3, 1, 4); --20
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('{$localizationService->Localize('Flour')}', 2, 3, 3, 1); --21
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->Localize('Flour')}', 2, 3, 3, 1, 3); --21
|
||||||
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock) VALUES ('{$localizationService->Localize('Sugar')}', 3, 3, 3, 1); --22
|
INSERT INTO products (name, location_id, qu_id_purchase, qu_id_stock, qu_factor_purchase_to_stock, product_group_id) VALUES ('{$localizationService->Localize('Sugar')}', 3, 3, 3, 1, 3); --22
|
||||||
|
|
||||||
INSERT INTO shopping_list (note, amount) VALUES ('{$localizationService->Localize('Some good snacks')}', 1);
|
INSERT INTO shopping_list (note, amount) VALUES ('{$localizationService->Localize('Some good snacks')}', 1);
|
||||||
INSERT INTO shopping_list (product_id, amount) VALUES (20, 1);
|
INSERT INTO shopping_list (product_id, amount) VALUES (20, 1);
|
||||||
@@ -78,15 +85,26 @@ class DemoDataGeneratorService extends BaseService
|
|||||||
INSERT INTO recipes_pos (recipe_id, product_id, amount, qu_id, only_check_single_unit_in_stock) VALUES (4, 21, 200, 8, 1);
|
INSERT INTO recipes_pos (recipe_id, product_id, amount, qu_id, only_check_single_unit_in_stock) VALUES (4, 21, 200, 8, 1);
|
||||||
INSERT INTO recipes_pos (recipe_id, product_id, amount, qu_id, only_check_single_unit_in_stock) VALUES (4, 22, 200, 8, 1);
|
INSERT INTO recipes_pos (recipe_id, product_id, amount, qu_id, only_check_single_unit_in_stock) VALUES (4, 22, 200, 8, 1);
|
||||||
|
|
||||||
INSERT INTO habits (name, period_type, period_days) VALUES ('{$localizationService->Localize('Changed towels in the bathroom')}', 'manually', 5); --1
|
INSERT INTO chores (name, period_type, period_days) VALUES ('{$localizationService->Localize('Changed towels in the bathroom')}', 'manually', 5); --1
|
||||||
INSERT INTO habits (name, period_type, period_days) VALUES ('{$localizationService->Localize('Cleaned the kitchen floor')}', 'dynamic-regular', 7); --2
|
INSERT INTO chores (name, period_type, period_days) VALUES ('{$localizationService->Localize('Cleaned the kitchen floor')}', 'dynamic-regular', 7); --2
|
||||||
INSERT INTO habits (name, period_type, period_days) VALUES ('{$localizationService->Localize('Lawn mowed in the garden')}', 'dynamic-regular', 21); --3
|
INSERT INTO chores (name, period_type, period_days) VALUES ('{$localizationService->Localize('Lawn mowed in the garden')}', 'dynamic-regular', 21); --3
|
||||||
|
|
||||||
INSERT INTO batteries (name, description, used_in) VALUES ('{$localizationService->Localize('Battery')}1', '{$localizationService->Localize('Warranty ends')} 2023', '{$localizationService->Localize('TV remote control')}'); --1
|
INSERT INTO batteries (name, description, used_in) VALUES ('{$localizationService->Localize('Battery')}1', '{$localizationService->Localize('Warranty ends')} 2023', '{$localizationService->Localize('TV remote control')}'); --1
|
||||||
INSERT INTO batteries (name, description, used_in) VALUES ('{$localizationService->Localize('Battery')}2', '{$localizationService->Localize('Warranty ends')} 2022', '{$localizationService->Localize('Alarm clock')}'); --2
|
INSERT INTO batteries (name, description, used_in) VALUES ('{$localizationService->Localize('Battery')}2', '{$localizationService->Localize('Warranty ends')} 2022', '{$localizationService->Localize('Alarm clock')}'); --2
|
||||||
INSERT INTO batteries (name, description, used_in, charge_interval_days) VALUES ('{$localizationService->Localize('Battery')}3', '{$localizationService->Localize('Warranty ends')} 2022', '{$localizationService->Localize('Heat remote control')}', 60); --3
|
INSERT INTO batteries (name, description, used_in, charge_interval_days) VALUES ('{$localizationService->Localize('Battery')}3', '{$localizationService->Localize('Warranty ends')} 2022', '{$localizationService->Localize('Heat remote control')}', 60); --3
|
||||||
INSERT INTO batteries (name, description, used_in, charge_interval_days) VALUES ('{$localizationService->Localize('Battery')}4', '{$localizationService->Localize('Warranty ends')} 2028', '{$localizationService->Localize('Heat remote control')}', 60); --4
|
INSERT INTO batteries (name, description, used_in, charge_interval_days) VALUES ('{$localizationService->Localize('Battery')}4', '{$localizationService->Localize('Warranty ends')} 2028', '{$localizationService->Localize('Heat remote control')}', 60); --4
|
||||||
|
|
||||||
|
INSERT INTO task_categories (name) VALUES ('{$localizationService->Localize('Home')}'); --1
|
||||||
|
INSERT INTO task_categories (name) VALUES ('{$localizationService->Localize('Life')}'); --2
|
||||||
|
INSERT INTO task_categories (name) VALUES ('{$localizationService->Localize('Projects')}'); --3
|
||||||
|
|
||||||
|
INSERT INTO tasks (name, category_id, due_date, assigned_to_user_id) VALUES ('{$localizationService->Localize('Repair the garage door')}', 1, date(datetime('now', 'localtime'), '+14 day'), 1);
|
||||||
|
INSERT INTO tasks (name, category_id, due_date, assigned_to_user_id) VALUES ('{$localizationService->Localize('Fork and improve grocy')}', 3, date(datetime('now', 'localtime'), '+30 day'), 1);
|
||||||
|
INSERT INTO tasks (name, category_id, due_date, assigned_to_user_id) VALUES ('{$localizationService->Localize('Task')}1', 2, date(datetime('now', 'localtime'), '-1 day'), 1);
|
||||||
|
INSERT INTO tasks (name, category_id, due_date, assigned_to_user_id) VALUES ('{$localizationService->Localize('Task')}2', 2, date(datetime('now', 'localtime'), '-1 day'), 1);
|
||||||
|
INSERT INTO tasks (name, due_date, assigned_to_user_id) VALUES ('{$localizationService->Localize('Find a solution for what to do when I forget the door keys')}', date(datetime('now', 'localtime'), '+3 day'), 1);
|
||||||
|
INSERT INTO tasks (name, due_date, assigned_to_user_id) VALUES ('{$localizationService->Localize('Task')}3', date(datetime('now', 'localtime'), '+4 day'), 1);
|
||||||
|
|
||||||
INSERT INTO migrations (migration) VALUES (-1);
|
INSERT INTO migrations (migration) VALUES (-1);
|
||||||
";
|
";
|
||||||
|
|
||||||
@@ -164,13 +182,13 @@ class DemoDataGeneratorService extends BaseService
|
|||||||
$stockService->AddProduct(22, 1, date('Y-m-d', strtotime('+200 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice());
|
$stockService->AddProduct(22, 1, date('Y-m-d', strtotime('+200 days')), StockService::TRANSACTION_TYPE_PURCHASE, date('Y-m-d', strtotime('-20 days')), $this->RandomPrice());
|
||||||
$stockService->AddMissingProductsToShoppingList();
|
$stockService->AddMissingProductsToShoppingList();
|
||||||
|
|
||||||
$habitsService = new HabitsService();
|
$choresService = new ChoresService();
|
||||||
$habitsService->TrackHabit(1, date('Y-m-d H:i:s', strtotime('-5 days')));
|
$choresService->TrackChore(1, date('Y-m-d H:i:s', strtotime('-5 days')));
|
||||||
$habitsService->TrackHabit(1, date('Y-m-d H:i:s', strtotime('-10 days')));
|
$choresService->TrackChore(1, date('Y-m-d H:i:s', strtotime('-10 days')));
|
||||||
$habitsService->TrackHabit(1, date('Y-m-d H:i:s', strtotime('-15 days')));
|
$choresService->TrackChore(1, date('Y-m-d H:i:s', strtotime('-15 days')));
|
||||||
$habitsService->TrackHabit(2, date('Y-m-d H:i:s', strtotime('-10 days')));
|
$choresService->TrackChore(2, date('Y-m-d H:i:s', strtotime('-10 days')));
|
||||||
$habitsService->TrackHabit(2, date('Y-m-d H:i:s', strtotime('-20 days')));
|
$choresService->TrackChore(2, date('Y-m-d H:i:s', strtotime('-20 days')));
|
||||||
$habitsService->TrackHabit(3, date('Y-m-d H:i:s', strtotime('-17 days')));
|
$choresService->TrackChore(3, date('Y-m-d H:i:s', strtotime('-17 days')));
|
||||||
|
|
||||||
$batteriesService = new BatteriesService();
|
$batteriesService = new BatteriesService();
|
||||||
$batteriesService->TrackChargeCycle(1, date('Y-m-d H:i:s', strtotime('-200 days')));
|
$batteriesService->TrackChargeCycle(1, date('Y-m-d H:i:s', strtotime('-200 days')));
|
||||||
|
31
services/FilesService.php
Normal file
31
services/FilesService.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Services;
|
||||||
|
|
||||||
|
class FilesService extends BaseService
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
$this->StoragePath = GROCY_DATAPATH . '/storage';
|
||||||
|
|
||||||
|
if (!file_exists($this->StoragePath))
|
||||||
|
{
|
||||||
|
mkdir($this->StoragePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private $StoragePath;
|
||||||
|
|
||||||
|
public function GetFilePath($group, $fileName)
|
||||||
|
{
|
||||||
|
$groupFolderPath = $this->StoragePath . '/' . $group;
|
||||||
|
if (!file_exists($groupFolderPath))
|
||||||
|
{
|
||||||
|
mkdir($groupFolderPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $groupFolderPath . '/' . $fileName;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,74 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Grocy\Services;
|
|
||||||
|
|
||||||
class HabitsService extends BaseService
|
|
||||||
{
|
|
||||||
const HABIT_TYPE_MANUALLY = 'manually';
|
|
||||||
const HABIT_TYPE_DYNAMIC_REGULAR = 'dynamic-regular';
|
|
||||||
|
|
||||||
public function GetCurrent()
|
|
||||||
{
|
|
||||||
$sql = 'SELECT * from habits_current';
|
|
||||||
return $this->DatabaseService->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function GetHabitDetails(int $habitId)
|
|
||||||
{
|
|
||||||
if (!$this->HabitExists($habitId))
|
|
||||||
{
|
|
||||||
throw new \Exception('Habit does not exist');
|
|
||||||
}
|
|
||||||
|
|
||||||
$habit = $this->Database->habits($habitId);
|
|
||||||
$habitTrackedCount = $this->Database->habits_log()->where('habit_id', $habitId)->count();
|
|
||||||
$habitLastTrackedTime = $this->Database->habits_log()->where('habit_id', $habitId)->max('tracked_time');
|
|
||||||
$nextExeuctionTime = $this->Database->habits_current()->where('habit_id', $habitId)->min('next_estimated_execution_time');
|
|
||||||
|
|
||||||
$lastHabitLogRow = $this->Database->habits_log()->where('habit_id = :1 AND tracked_time = :2', $habitId, $habitLastTrackedTime)->fetch();
|
|
||||||
$lastDoneByUser = null;
|
|
||||||
if ($lastHabitLogRow !== null && !empty($lastHabitLogRow))
|
|
||||||
{
|
|
||||||
$usersService = new UsersService();
|
|
||||||
$users = $usersService->GetUsersAsDto();
|
|
||||||
$lastDoneByUser = FindObjectInArrayByPropertyValue($users, 'id', $lastHabitLogRow->done_by_user_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return array(
|
|
||||||
'habit' => $habit,
|
|
||||||
'last_tracked' => $habitLastTrackedTime,
|
|
||||||
'tracked_count' => $habitTrackedCount,
|
|
||||||
'last_done_by' => $lastDoneByUser,
|
|
||||||
'next_estimated_execution_time' => $nextExeuctionTime
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function TrackHabit(int $habitId, string $trackedTime, $doneBy = GROCY_USER_ID)
|
|
||||||
{
|
|
||||||
if (!$this->HabitExists($habitId))
|
|
||||||
{
|
|
||||||
throw new \Exception('Habit does not exist');
|
|
||||||
}
|
|
||||||
|
|
||||||
$userRow = $this->Database->users()->where('id = :1', $doneBy)->fetch();
|
|
||||||
if ($userRow === null)
|
|
||||||
{
|
|
||||||
throw new \Exception('User does not exist');
|
|
||||||
}
|
|
||||||
|
|
||||||
$logRow = $this->Database->habits_log()->createRow(array(
|
|
||||||
'habit_id' => $habitId,
|
|
||||||
'tracked_time' => $trackedTime,
|
|
||||||
'done_by_user_id' => $doneBy
|
|
||||||
));
|
|
||||||
$logRow->save();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function HabitExists($habitId)
|
|
||||||
{
|
|
||||||
$habitRow = $this->Database->habits()->where('id = :1', $habitId)->fetch();
|
|
||||||
return $habitRow !== null;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -34,7 +34,7 @@ class LocalizationService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function LogMissingLocalization(string $culture, string $text)
|
public function LogMissingLocalization(string $culture, string $text)
|
||||||
{
|
{
|
||||||
$file = GROCY_DATAPATH . "/missing_translations_$culture.json";
|
$file = GROCY_DATAPATH . "/missing_translations_$culture.json";
|
||||||
|
|
||||||
|
@@ -33,14 +33,20 @@ class SessionService extends BaseService
|
|||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function CreateSession($userId)
|
public function CreateSession($userId, $stayLoggedInPermanently = false)
|
||||||
{
|
{
|
||||||
$newSessionKey = $this->GenerateSessionKey();
|
$newSessionKey = $this->GenerateSessionKey();
|
||||||
|
|
||||||
|
$expires = date('Y-m-d H:i:s', time() + 2592000); // Default is that sessions expire in 30 days
|
||||||
|
if ($stayLoggedInPermanently === true)
|
||||||
|
{
|
||||||
|
$expires = date('Y-m-d H:i:s', time() + 31220640000); // 999 years aka forever
|
||||||
|
}
|
||||||
|
|
||||||
$sessionRow = $this->Database->sessions()->createRow(array(
|
$sessionRow = $this->Database->sessions()->createRow(array(
|
||||||
'user_id' => $userId,
|
'user_id' => $userId,
|
||||||
'session_key' => $newSessionKey,
|
'session_key' => $newSessionKey,
|
||||||
'expires' => date('Y-m-d H:i:s', time() + 2592000) // Default is that sessions expire in 30 days
|
'expires' => $expires
|
||||||
));
|
));
|
||||||
$sessionRow->save();
|
$sessionRow->save();
|
||||||
|
|
||||||
|
34
services/TasksService.php
Normal file
34
services/TasksService.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Services;
|
||||||
|
|
||||||
|
class TasksService extends BaseService
|
||||||
|
{
|
||||||
|
public function GetCurrent()
|
||||||
|
{
|
||||||
|
$sql = 'SELECT * from tasks_current';
|
||||||
|
return $this->DatabaseService->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function MarkTaskAsCompleted($taskId, $doneTime)
|
||||||
|
{
|
||||||
|
if (!$this->TaskExists($taskId))
|
||||||
|
{
|
||||||
|
throw new \Exception('Task does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
$taskRow = $this->Database->tasks()->where('id = :1', $taskId)->fetch();
|
||||||
|
$taskRow->update(array(
|
||||||
|
'done' => 1,
|
||||||
|
'done_timestamp' => $doneTime
|
||||||
|
));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function TaskExists($taskId)
|
||||||
|
{
|
||||||
|
$taskRow = $this->Database->tasks()->where('id = :1', $taskId)->fetch();
|
||||||
|
return $taskRow !== null;
|
||||||
|
}
|
||||||
|
}
|
@@ -50,6 +50,55 @@ class UsersService extends BaseService
|
|||||||
return $returnUsers;
|
return $returnUsers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function GetUserSetting($userId, $settingKey)
|
||||||
|
{
|
||||||
|
$settingRow = $this->Database->user_settings()->where('user_id = :1 AND key = :2', $userId, $settingKey)->fetch();
|
||||||
|
if ($settingRow !== null)
|
||||||
|
{
|
||||||
|
return $settingRow->value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetUserSettings($userId)
|
||||||
|
{
|
||||||
|
$settings = array();
|
||||||
|
|
||||||
|
$settingRows = $this->Database->user_settings()->where('user_id = :1', $userId)->fetchAll();
|
||||||
|
foreach ($settingRows as $settingRow)
|
||||||
|
{
|
||||||
|
$settings[$settingRow->key] = $settingRow->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the configured default values for all missing settings
|
||||||
|
global $GROCY_DEFAULT_USER_SETTINGS;
|
||||||
|
return array_merge($GROCY_DEFAULT_USER_SETTINGS, $settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function SetUserSetting($userId, $settingKey, $settingValue)
|
||||||
|
{
|
||||||
|
$settingRow = $this->Database->user_settings()->where('user_id = :1 AND key = :2', $userId, $settingKey)->fetch();
|
||||||
|
if ($settingRow !== null)
|
||||||
|
{
|
||||||
|
$settingRow->update(array(
|
||||||
|
'value' => $settingValue,
|
||||||
|
'row_updated_timestamp' => date('Y-m-d H:i:s')
|
||||||
|
));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$settingRow = $this->Database->user_settings()->createRow(array(
|
||||||
|
'user_id' => $userId,
|
||||||
|
'key' => $settingKey,
|
||||||
|
'value' => $settingValue
|
||||||
|
));
|
||||||
|
$settingRow->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private function UserExists($userId)
|
private function UserExists($userId)
|
||||||
{
|
{
|
||||||
$userRow = $this->Database->users()->where('id = :1', $userId)->fetch();
|
$userRow = $this->Database->users()->where('id = :1', $userId)->fetch();
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"Version": "1.18.0",
|
"Version": "1.20.0",
|
||||||
"ReleaseDate": "2018-08-11"
|
"ReleaseDate": "2018-09-30"
|
||||||
}
|
}
|
||||||
|
@@ -12,12 +12,20 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h1>@yield('title')</h1>
|
<h1>@yield('title')</h1>
|
||||||
<p id="info-due-batteries" data-next-x-days="{{ $nextXDays }}" class="btn btn-lg btn-warning no-real-button responsive-button mr-2"></p>
|
<p id="info-due-batteries" data-status-filter="duesoon" data-next-x-days="{{ $nextXDays }}" class="btn btn-lg btn-warning status-filter-button responsive-button mr-2"></p>
|
||||||
<p id="info-overdue-batteries" class="btn btn-lg btn-danger no-real-button responsive-button"></p>
|
<p id="info-overdue-batteries" data-status-filter="overdue" class="btn btn-lg btn-danger status-filter-button responsive-button"></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row mt-3">
|
<div class="row mt-3">
|
||||||
|
<div class="col-xs-12 col-md-6 col-xl-3">
|
||||||
|
<label for="status-filter">{{ $L('Filter by status') }}</label> <i class="fas fa-filter"></i>
|
||||||
|
<select class="form-control" id="status-filter">
|
||||||
|
<option class="bg-white" value="all">{{ $L('All') }}</option>
|
||||||
|
<option class="bg-warning" value="duesoon">{{ $L('Due soon') }}</option>
|
||||||
|
<option class="bg-danger" value="overdue">{{ $L('Overdue') }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="col-xs-12 col-md-6 col-xl-3">
|
<div class="col-xs-12 col-md-6 col-xl-3">
|
||||||
<label for="search">{{ $L('Search') }}</label> <i class="fas fa-search"></i>
|
<label for="search">{{ $L('Search') }}</label> <i class="fas fa-search"></i>
|
||||||
<input type="text" class="form-control" id="search">
|
<input type="text" class="form-control" id="search">
|
||||||
@@ -33,13 +41,14 @@
|
|||||||
<th>{{ $L('Battery') }}</th>
|
<th>{{ $L('Battery') }}</th>
|
||||||
<th>{{ $L('Last charged') }}</th>
|
<th>{{ $L('Last charged') }}</th>
|
||||||
<th>{{ $L('Next planned charge cycle') }}</th>
|
<th>{{ $L('Next planned charge cycle') }}</th>
|
||||||
|
<th class="d-none">Hidden status</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@foreach($current as $curentBatteryEntry)
|
@foreach($current as $curentBatteryEntry)
|
||||||
<tr id="battery-{{ $curentBatteryEntry->battery_id }}-row" class="@if(FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->charge_interval_days > 0 && $curentBatteryEntry->next_estimated_charge_time < date('Y-m-d H:i:s')) table-danger @elseif(FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->charge_interval_days > 0 && $curentBatteryEntry->next_estimated_charge_time < date('Y-m-d H:i:s', strtotime("+$nextXDays days"))) table-warning @endif">
|
<tr id="battery-{{ $curentBatteryEntry->battery_id }}-row" class="@if(FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->charge_interval_days > 0 && $curentBatteryEntry->next_estimated_charge_time < date('Y-m-d H:i:s')) table-danger @elseif(FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->charge_interval_days > 0 && $curentBatteryEntry->next_estimated_charge_time < date('Y-m-d H:i:s', strtotime("+$nextXDays days"))) table-warning @endif">
|
||||||
<td class="fit-content">
|
<td class="fit-content">
|
||||||
<a class="btn btn-success btn-sm track-charge-cycle-button" href="#" data-toggle="tooltip" title="{{ $L('Track charge cycle of battery #1', FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->name) }}"
|
<a class="btn btn-success btn-sm track-charge-cycle-button" href="#" data-toggle="tooltip" data-placement="left" title="{{ $L('Track charge cycle of battery #1', FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->name) }}"
|
||||||
data-battery-id="{{ $curentBatteryEntry->battery_id }}"
|
data-battery-id="{{ $curentBatteryEntry->battery_id }}"
|
||||||
data-battery-name="{{ FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->name }}">
|
data-battery-name="{{ FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->name }}">
|
||||||
<i class="fas fa-fire"></i>
|
<i class="fas fa-fire"></i>
|
||||||
@@ -60,6 +69,9 @@
|
|||||||
...
|
...
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
|
<td class="d-none">
|
||||||
|
"@if(FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->charge_interval_days > 0 && $curentBatteryEntry->next_estimated_charge_time < date('Y-m-d H:i:s')) overdue @elseif(FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->charge_interval_days > 0 && $curentBatteryEntry->next_estimated_charge_time < date('Y-m-d H:i:s', strtotime("+$nextXDays days"))) duesoon @endif
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@endforeach
|
@endforeach
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@@ -37,13 +37,17 @@
|
|||||||
<input type="text" class="form-control" id="used_in" name="used_in" value="@if($mode == 'edit'){{ $battery->used_in }}@endif">
|
<input type="text" class="form-control" id="used_in" name="used_in" value="@if($mode == 'edit'){{ $battery->used_in }}@endif">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
@php if($mode == 'edit') { $value = $battery->charge_interval_days; } else { $value = 0; } @endphp
|
||||||
<label for="charge_interval_days">{{ $L('Charge cycle interval (days)') }}<br><span class="small text-muted">{{ $L('0 means suggestions for the next charge cycle are disabled') }}</span></label>
|
@include('components.numberpicker', array(
|
||||||
<input required min="0" step="1" type="number" class="form-control" id="charge_interval_days" name="charge_interval_days" value="@if($mode == 'edit'){{ $battery->charge_interval_days }}@else{{0}}@endif">
|
'id' => 'charge_interval_days',
|
||||||
<div class="invalid-feedback">{{ $L('This cannot be negative') }}</div>
|
'label' => 'Charge cycle interval (days)',
|
||||||
</div>
|
'value' => $value,
|
||||||
|
'min' => '0',
|
||||||
|
'hint' => $L('0 means suggestions for the next charge cycle are disabled'),
|
||||||
|
'invalidFeedback' => $L('This cannot be negative')
|
||||||
|
))
|
||||||
|
|
||||||
<button id="save-battery-button" type="submit" class="btn btn-success">{{ $L('Save') }}</button>
|
<button id="save-battery-button" class="btn btn-success">{{ $L('Save') }}</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -32,7 +32,7 @@
|
|||||||
'invalidFeedback' => $L('This can only be before now')
|
'invalidFeedback' => $L('This can only be before now')
|
||||||
))
|
))
|
||||||
|
|
||||||
<button id="save-batterytracking-button" type="submit" class="btn btn-success">{{ $L('OK') }}</button>
|
<button id="save-batterytracking-button" class="btn btn-success">{{ $L('OK') }}</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
@extends('layout.default')
|
@extends('layout.default')
|
||||||
|
|
||||||
@if($mode == 'edit')
|
@if($mode == 'edit')
|
||||||
@section('title', $L('Edit habit'))
|
@section('title', $L('Edit chore'))
|
||||||
@else
|
@else
|
||||||
@section('title', $L('Create habit'))
|
@section('title', $L('Create chore'))
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@section('viewJsName', 'habitform')
|
@section('viewJsName', 'choreform')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -16,41 +16,44 @@
|
|||||||
<script>Grocy.EditMode = '{{ $mode }}';</script>
|
<script>Grocy.EditMode = '{{ $mode }}';</script>
|
||||||
|
|
||||||
@if($mode == 'edit')
|
@if($mode == 'edit')
|
||||||
<script>Grocy.EditObjectId = {{ $habit->id }};</script>
|
<script>Grocy.EditObjectId = {{ $chore->id }};</script>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<form id="habit-form" novalidate>
|
<form id="chore-form" novalidate>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="name">{{ $L('Name') }}</label>
|
<label for="name">{{ $L('Name') }}</label>
|
||||||
<input type="text" class="form-control" required id="name" name="name" value="@if($mode == 'edit'){{ $habit->name }}@endif">
|
<input type="text" class="form-control" required id="name" name="name" value="@if($mode == 'edit'){{ $chore->name }}@endif">
|
||||||
<div class="invalid-feedback">{{ $L('A name is required') }}</div>
|
<div class="invalid-feedback">{{ $L('A name is required') }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="description">{{ $L('Description') }}</label>
|
<label for="description">{{ $L('Description') }}</label>
|
||||||
<textarea class="form-control" rows="2" id="description" name="description">@if($mode == 'edit'){{ $habit->description }}@endif</textarea>
|
<textarea class="form-control" rows="2" id="description" name="description">@if($mode == 'edit'){{ $chore->description }}@endif</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="period_type">{{ $L('Period type') }}</label>
|
<label for="period_type">{{ $L('Period type') }}</label>
|
||||||
<select required class="form-control input-group-habit-period-type" id="period_type" name="period_type">
|
<select required class="form-control input-group-chore-period-type" id="period_type" name="period_type">
|
||||||
@foreach($periodTypes as $periodType)
|
@foreach($periodTypes as $periodType)
|
||||||
<option @if($mode == 'edit' && $periodType == $habit->period_type) selected="selected" @endif value="{{ $periodType }}">{{ $L($periodType) }}</option>
|
<option @if($mode == 'edit' && $periodType == $chore->period_type) selected="selected" @endif value="{{ $periodType }}">{{ $L($periodType) }}</option>
|
||||||
@endforeach
|
@endforeach
|
||||||
</select>
|
</select>
|
||||||
<div class="invalid-feedback">{{ $L('A period type is required') }}</div>
|
<div class="invalid-feedback">{{ $L('A period type is required') }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
@php if($mode == 'edit') { $value = $chore->period_days; } else { $value = 0; } @endphp
|
||||||
<label for="period_days">{{ $L('Period days') }}</label>
|
@include('components.numberpicker', array(
|
||||||
<input type="number" class="form-control input-group-habit-period-type" id="period_days" name="period_days" min="0" value="@if($mode == 'edit'){{ $habit->period_days }}@endif">
|
'id' => 'period_days',
|
||||||
<div class="invalid-feedback">{{ $L('This cannot be negative') }}</div>
|
'label' => 'Period days',
|
||||||
</div>
|
'value' => $value,
|
||||||
|
'min' => '0',
|
||||||
|
'additionalCssClasses' => 'input-group-chore-period-type',
|
||||||
|
'invalidFeedback' => $L('This cannot be negative'),
|
||||||
|
'additionalHtmlElements' => '<p id="chore-period-type-info" class="form-text text-muted small d-none"></p>'
|
||||||
|
))
|
||||||
|
|
||||||
<p id="habit-period-type-info" class="form-text text-muted small d-none"></p>
|
<button id="save-chore-button" class="btn btn-success">{{ $L('Save') }}</button>
|
||||||
|
|
||||||
<button id="save-habit-button" type="submit" class="btn btn-success">{{ $L('Save') }}</button>
|
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user