mirror of
https://github.com/grocy/grocy.git
synced 2025-10-15 17:58:32 +00:00
Compare commits
33 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
0954b5a741 | ||
|
02b6c3b721 | ||
|
6fa4e13ba2 | ||
|
9837f79f9c | ||
|
6e4cd22118 | ||
|
ca00dd8e2d | ||
|
5455ec7bde | ||
|
2e7af1b050 | ||
|
89bae8d25e | ||
|
5b5c272909 | ||
|
3e394a3840 | ||
|
ab8094e1c0 | ||
|
bbb5f1c7c7 | ||
|
b607f188af | ||
|
9ab1a674fe | ||
|
2f0a1391b7 | ||
|
a9a1358b08 | ||
|
4853174d03 | ||
|
538d789366 | ||
|
0751919b82 | ||
|
99b2a84667 | ||
|
9bd6aac09c | ||
|
7be35a90c1 | ||
|
eae5b8bad9 | ||
|
0c85342404 | ||
|
9ddcdb3ab2 | ||
|
1c537cf5da | ||
|
607a90cccc | ||
|
3d1c6fc5f0 | ||
|
df1d3677e8 | ||
|
4da2ac9b35 | ||
|
b4ae7d8538 | ||
|
870b679e0e |
53
README.md
53
README.md
@@ -1,22 +1,63 @@
|
|||||||
# grocy
|
# grocy
|
||||||
ERP beyond your fridge
|
ERP beyond your fridge
|
||||||
|
|
||||||
|
## Give it a try
|
||||||
|
Public demo of the latest version → [https://demo.grocy.info](https://demo.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.
|
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.
|
||||||
|
|
||||||
## What it is about
|
## What it is about
|
||||||
For now my main focus is on stock management, ERP your fridge!
|
For now my main focus is on stock management, ERP your fridge!
|
||||||
|
|
||||||
# Give it a try
|
|
||||||
Public demo of the latest version → [https://demo.grocy.info](https://demo.grocy.info)
|
|
||||||
|
|
||||||
## How to install
|
## How to install
|
||||||
Just unpack the [latest release](https://github.com/berrnd/grocy/releases/latest) on your PHP (7.0 or later required) enabled webserver (root is the `/public` directory), copy `config-dist.php` to `data/config.php`, edit it to your needs, ensure that the `data` directory is writable and you're ready to go. Alternatively clone this repository and install Composer and Bower dependencies manually.
|
Just unpack the [latest release](https://github.com/berrnd/grocy/releases/latest) on your PHP (currently only tested with PHP 7.2) enabled webserver (webservers root should point to the `/public` directory), copy `config-dist.php` to `data/config.php`, edit it to your needs, ensure that the `data` directory is writable and you're ready to go.
|
||||||
|
|
||||||
|
Default login is user `admin` with password `admin` - see the `data/config.php` file. Alternatively clone this repository and install Composer and Bower dependencies manually.
|
||||||
|
|
||||||
If you use nginx as your webserver, please include `try_files $uri /index.php;` in your location block.
|
If you use nginx as your webserver, please include `try_files $uri /index.php;` in your location block.
|
||||||
|
|
||||||
## Notes about barcode readers
|
If, however, your webserver does not support URL rewriting, set `DISABLE_URL_REWRITING` in `data/config.php`.
|
||||||
Some fields also allow to select a value by scanning a barcode. It works best when your barcode reader prefixes every barcode with a letter this is normally not part of a item name (I use a `$`) and sends a `TAB` after a scan.
|
|
||||||
|
## 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` (it will show up as an error if something is missing there).
|
||||||
|
|
||||||
|
## Things worth to know
|
||||||
|
|
||||||
|
### REST API & data model documentation
|
||||||
|
See the integrated Swagger UI instance on [/api](https://demo-en.grocy.info/api).
|
||||||
|
|
||||||
|
### Barcode readers
|
||||||
|
Some fields also allow to select a value by scanning a barcode. It works best when your barcode reader prefixes every barcode with a letter which is normally not part of a item name (I use a `$`) and sends a `TAB` after a scan.
|
||||||
|
|
||||||
|
### Input shorthands for date fields
|
||||||
|
For (productivity) reasons all date (and time) input fields use the ISO-8601 format regardless of localization.
|
||||||
|
The following shorthands are available:
|
||||||
|
- `MMDD` gets expanded to the given day on the current year in proper notation
|
||||||
|
- Example: `0517` will be converted to `2018-05-17`
|
||||||
|
- `YYYYMMDD` gets expanded to the proper ISO-8601 notation
|
||||||
|
- Example: `20190417` will be converted to `2019-04-17`
|
||||||
|
- `x` gets expanded to `2999-12-31` (which I use for products which never expire)
|
||||||
|
- Down/up arrow keys will increase/decrease the date by one day
|
||||||
|
- Right/left arrow keys will increase/decrease the date by 1 week
|
||||||
|
|
||||||
|
### Keyboard shorthands for buttons
|
||||||
|
Wherever a button contains a bold highlighted letter, this is a shortcut key.
|
||||||
|
Example: Button "Add as new **p**roduct" can be "pressed" by using the `P` key on your keyboard.
|
||||||
|
|
||||||
|
### Barcode lookup via external services
|
||||||
|
Products can be directly added to the database via looking them up against external services by a barcode.
|
||||||
|
This is currently only possible through the REST API.
|
||||||
|
There is no plugin included for any service, see the reference implementation in `data/plugins/DemoBarcodeLookupPlugin.php`.
|
||||||
|
|
||||||
|
### Database migrations
|
||||||
|
Database schema migration is automatically done when visiting the root (`/`) route (click on the logo in the left upper edge).
|
||||||
|
|
||||||
|
### Demo mode
|
||||||
|
When the file `data/demo.txt` exists, the application will work in a demo mode which means authentication is disabled and some demo data will be generated during the database schema migration.
|
||||||
|
|
||||||
|
### Other things
|
||||||
|
When the file `data/add_before_end_body.html` exists, the contents of the file be added just before `</body>` on every page, useful for your own JS/CSS without to have to modify the application itself.
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
#### Dashboard
|
#### Dashboard
|
||||||
|
52
app.php
52
app.php
@@ -3,42 +3,42 @@
|
|||||||
use \Psr\Http\Message\ServerRequestInterface as Request;
|
use \Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
use \Psr\Http\Message\ResponseInterface as Response;
|
use \Psr\Http\Message\ResponseInterface as Response;
|
||||||
|
|
||||||
use \Grocy\Middleware\SessionAuthMiddleware;
|
use \Grocy\Helpers\UrlManager;
|
||||||
use \Grocy\Services\ApplicationService;
|
use \Grocy\Controllers\LoginController;
|
||||||
|
|
||||||
require_once __DIR__ . '/vendor/autoload.php';
|
require_once __DIR__ . '/vendor/autoload.php';
|
||||||
require_once __DIR__ . '/data/config.php';
|
require_once __DIR__ . '/data/config.php';
|
||||||
require_once __DIR__ . '/extensions.php';
|
|
||||||
|
|
||||||
// Setup base application
|
// Setup base application
|
||||||
if (PHP_SAPI !== 'cli')
|
$appContainer = new \Slim\Container([
|
||||||
{
|
'settings' => [
|
||||||
$appContainer = new \Slim\Container([
|
'displayErrorDetails' => true,
|
||||||
'settings' => [
|
'determineRouteBeforeAppMiddleware' => true
|
||||||
'displayErrorDetails' => true,
|
],
|
||||||
'determineRouteBeforeAppMiddleware' => true
|
'view' => function($container)
|
||||||
],
|
{
|
||||||
'view' => function($container)
|
return new \Slim\Views\Blade(__DIR__ . '/views', __DIR__ . '/data/viewcache');
|
||||||
{
|
},
|
||||||
return new \Slim\Views\Blade(__DIR__ . '/views', __DIR__ . '/data/viewcache');
|
'LoginControllerInstance' => function($container)
|
||||||
}
|
{
|
||||||
]);
|
return new LoginController($container, 'grocy_session');
|
||||||
|
},
|
||||||
|
'UrlManager' => function($container)
|
||||||
|
{
|
||||||
|
return new UrlManager(BASE_URL);
|
||||||
|
},
|
||||||
|
'ApiKeyHeaderName' => function($container)
|
||||||
|
{
|
||||||
|
return 'GROCY-API-KEY';
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
$app = new \Slim\App($appContainer);
|
||||||
|
|
||||||
$app = new \Slim\App($appContainer);
|
if (PHP_SAPI === 'cli')
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
$app = new \Slim\App();
|
|
||||||
$app->add(\pavlakis\cli\CliRequest::class);
|
$app->add(\pavlakis\cli\CliRequest::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add session handling if this is not a demo installation
|
|
||||||
$applicationService = new ApplicationService();
|
|
||||||
if (!$applicationService->IsDemoInstallation())
|
|
||||||
{
|
|
||||||
$app->add(SessionAuthMiddleware::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
require_once __DIR__ . '/routes.php';
|
require_once __DIR__ . '/routes.php';
|
||||||
|
|
||||||
$app->run();
|
$app->run();
|
||||||
|
@@ -17,6 +17,9 @@
|
|||||||
"jquery-timeago": "^1.6.1",
|
"jquery-timeago": "^1.6.1",
|
||||||
"toastr": "^2.1.3",
|
"toastr": "^2.1.3",
|
||||||
"tagmanager": "^3.0.2",
|
"tagmanager": "^3.0.2",
|
||||||
"eonasdan-bootstrap-datetimepicker": "^4.17.47"
|
"eonasdan-bootstrap-datetimepicker": "^4.17.47",
|
||||||
|
"swagger-ui": "^3.13.4",
|
||||||
|
"jquery-ui": "^1.12.1",
|
||||||
|
"bootstrap-side-navbar": "^1.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,15 +1,21 @@
|
|||||||
{
|
{
|
||||||
"require": {
|
"require": {
|
||||||
|
"php": ">=7.2",
|
||||||
"slim/slim": "^3.8",
|
"slim/slim": "^3.8",
|
||||||
"morris/lessql": "^0.3.4",
|
"morris/lessql": "^0.3.4",
|
||||||
"pavlakis/slim-cli": "^1.0",
|
"pavlakis/slim-cli": "^1.0",
|
||||||
"rubellum/slim-blade-view": "^0.1.1"
|
"rubellum/slim-blade-view": "^0.1.1",
|
||||||
|
"tuupola/cors-middleware": "^0.7.0"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Grocy\\Services\\": "services/",
|
"Grocy\\Services\\": "services/",
|
||||||
"Grocy\\Controllers\\": "controllers/",
|
"Grocy\\Controllers\\": "controllers/",
|
||||||
"Grocy\\Middleware\\": "middleware/"
|
"Grocy\\Middleware\\": "middleware/",
|
||||||
}
|
"Grocy\\Helpers\\": "helpers/"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"helpers/extensions.php"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
430
composer.lock
generated
430
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "12ebab60e283dfdab831d1cc22430d05",
|
"content-hash": "42031c0b205b7ce7efb4b6eb95a0096a",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "container-interop/container-interop",
|
"name": "container-interop/container-interop",
|
||||||
@@ -104,9 +104,61 @@
|
|||||||
],
|
],
|
||||||
"time": "2018-01-09T20:05:19+00:00"
|
"time": "2018-01-09T20:05:19+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "http-interop/http-factory",
|
||||||
|
"version": "0.3.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/http-interop/http-factory.git",
|
||||||
|
"reference": "c2587cc0a6f74987fefb5b8074acfd32c69a4b0f"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/http-interop/http-factory/zipball/c2587cc0a6f74987fefb5b8074acfd32c69a4b0f",
|
||||||
|
"reference": "c2587cc0a6f74987fefb5b8074acfd32c69a4b0f",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.3.0",
|
||||||
|
"psr/http-message": "^1.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Interop\\Http\\Factory\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "PHP-FIG",
|
||||||
|
"homepage": "http://www.php-fig.org/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Common interface for HTTP message factories",
|
||||||
|
"keywords": [
|
||||||
|
"factory",
|
||||||
|
"http",
|
||||||
|
"message",
|
||||||
|
"psr",
|
||||||
|
"psr-17",
|
||||||
|
"psr-7",
|
||||||
|
"request",
|
||||||
|
"response"
|
||||||
|
],
|
||||||
|
"time": "2017-03-24T14:48:51+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "illuminate/container",
|
"name": "illuminate/container",
|
||||||
"version": "v5.6.16",
|
"version": "v5.6.17",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/illuminate/container.git",
|
"url": "https://github.com/illuminate/container.git",
|
||||||
@@ -150,7 +202,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "illuminate/contracts",
|
"name": "illuminate/contracts",
|
||||||
"version": "v5.6.16",
|
"version": "v5.6.17",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/illuminate/contracts.git",
|
"url": "https://github.com/illuminate/contracts.git",
|
||||||
@@ -194,7 +246,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "illuminate/events",
|
"name": "illuminate/events",
|
||||||
"version": "v5.6.16",
|
"version": "v5.6.17",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/illuminate/events.git",
|
"url": "https://github.com/illuminate/events.git",
|
||||||
@@ -239,7 +291,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "illuminate/filesystem",
|
"name": "illuminate/filesystem",
|
||||||
"version": "v5.6.16",
|
"version": "v5.6.17",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/illuminate/filesystem.git",
|
"url": "https://github.com/illuminate/filesystem.git",
|
||||||
@@ -291,16 +343,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "illuminate/support",
|
"name": "illuminate/support",
|
||||||
"version": "v5.6.16",
|
"version": "v5.6.17",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/illuminate/support.git",
|
"url": "https://github.com/illuminate/support.git",
|
||||||
"reference": "fad0669f858423679497a17f973261cc32f9a5a8"
|
"reference": "cc8d6f5cef3a901de6bb7d1b362102a6db001085"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/illuminate/support/zipball/fad0669f858423679497a17f973261cc32f9a5a8",
|
"url": "https://api.github.com/repos/illuminate/support/zipball/cc8d6f5cef3a901de6bb7d1b362102a6db001085",
|
||||||
"reference": "fad0669f858423679497a17f973261cc32f9a5a8",
|
"reference": "cc8d6f5cef3a901de6bb7d1b362102a6db001085",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -344,11 +396,11 @@
|
|||||||
],
|
],
|
||||||
"description": "The Illuminate Support package.",
|
"description": "The Illuminate Support package.",
|
||||||
"homepage": "https://laravel.com",
|
"homepage": "https://laravel.com",
|
||||||
"time": "2018-04-05T21:19:22+00:00"
|
"time": "2018-04-17T12:26:47+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "illuminate/view",
|
"name": "illuminate/view",
|
||||||
"version": "v5.6.16",
|
"version": "v5.6.17",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/illuminate/view.git",
|
"url": "https://github.com/illuminate/view.git",
|
||||||
@@ -443,17 +495,72 @@
|
|||||||
"time": "2018-01-27T13:18:21+00:00"
|
"time": "2018-01-27T13:18:21+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nesbot/carbon",
|
"name": "neomerx/cors-psr7",
|
||||||
"version": "1.25.0",
|
"version": "v1.0.12",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/briannesbitt/Carbon.git",
|
"url": "https://github.com/neomerx/cors-psr7.git",
|
||||||
"reference": "cbcf13da0b531767e39eb86e9687f5deba9857b4"
|
"reference": "24944f39483d1a89f66ae9d58cca9f82b8815b35"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/cbcf13da0b531767e39eb86e9687f5deba9857b4",
|
"url": "https://api.github.com/repos/neomerx/cors-psr7/zipball/24944f39483d1a89f66ae9d58cca9f82b8815b35",
|
||||||
"reference": "cbcf13da0b531767e39eb86e9687f5deba9857b4",
|
"reference": "24944f39483d1a89f66ae9d58cca9f82b8815b35",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.6.0",
|
||||||
|
"psr/http-message": "^1.0",
|
||||||
|
"psr/log": "^1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"mockery/mockery": "^0.9.9",
|
||||||
|
"phpunit/phpunit": "^5.7",
|
||||||
|
"scrutinizer/ocular": "^1.1",
|
||||||
|
"squizlabs/php_codesniffer": "^3.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Neomerx\\Cors\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"Apache-2.0"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "neomerx",
|
||||||
|
"email": "info@neomerx.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Framework agnostic (PSR-7) CORS implementation (www.w3.org/TR/cors/)",
|
||||||
|
"homepage": "https://github.com/neomerx/cors-psr7",
|
||||||
|
"keywords": [
|
||||||
|
"Cross Origin Resource Sharing",
|
||||||
|
"Cross-Origin Resource Sharing",
|
||||||
|
"cors",
|
||||||
|
"neomerx",
|
||||||
|
"psr-7",
|
||||||
|
"psr7",
|
||||||
|
"w3.org",
|
||||||
|
"www.w3.org"
|
||||||
|
],
|
||||||
|
"time": "2017-09-03T22:31:57+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nesbot/carbon",
|
||||||
|
"version": "1.26.4",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/briannesbitt/Carbon.git",
|
||||||
|
"reference": "e3d9014279133a3cccc01f6a691322a2d5a6a87b"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/e3d9014279133a3cccc01f6a691322a2d5a6a87b",
|
||||||
|
"reference": "e3d9014279133a3cccc01f6a691322a2d5a6a87b",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -465,14 +572,9 @@
|
|||||||
"phpunit/phpunit": "^4.8.35 || ^5.7"
|
"phpunit/phpunit": "^4.8.35 || ^5.7"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
|
||||||
"branch-alias": {
|
|
||||||
"dev-master": "1.23-dev"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Carbon\\": "src/Carbon/"
|
"": "src/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
@@ -493,7 +595,7 @@
|
|||||||
"datetime",
|
"datetime",
|
||||||
"time"
|
"time"
|
||||||
],
|
],
|
||||||
"time": "2018-03-19T15:50:49+00:00"
|
"time": "2018-04-17T15:35:42+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nikic/fast-route",
|
"name": "nikic/fast-route",
|
||||||
@@ -780,6 +882,112 @@
|
|||||||
],
|
],
|
||||||
"time": "2016-08-06T14:39:51+00:00"
|
"time": "2016-08-06T14:39:51+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "psr/http-server-handler",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/php-fig/http-server-handler.git",
|
||||||
|
"reference": "439d92054dc06097f2406ec074a2627839955a02"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/439d92054dc06097f2406ec074a2627839955a02",
|
||||||
|
"reference": "439d92054dc06097f2406ec074a2627839955a02",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.0",
|
||||||
|
"psr/http-message": "^1.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Psr\\Http\\Server\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "PHP-FIG",
|
||||||
|
"homepage": "http://www.php-fig.org/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Common interface for HTTP server-side request handler",
|
||||||
|
"keywords": [
|
||||||
|
"handler",
|
||||||
|
"http",
|
||||||
|
"http-interop",
|
||||||
|
"psr",
|
||||||
|
"psr-15",
|
||||||
|
"psr-7",
|
||||||
|
"request",
|
||||||
|
"response",
|
||||||
|
"server"
|
||||||
|
],
|
||||||
|
"time": "2018-01-22T17:04:15+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "psr/http-server-middleware",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/php-fig/http-server-middleware.git",
|
||||||
|
"reference": "ea17eb1fb2c8df6db919cc578451a8013c6a0ae5"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/ea17eb1fb2c8df6db919cc578451a8013c6a0ae5",
|
||||||
|
"reference": "ea17eb1fb2c8df6db919cc578451a8013c6a0ae5",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.0",
|
||||||
|
"psr/http-message": "^1.0",
|
||||||
|
"psr/http-server-handler": "^1.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Psr\\Http\\Server\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "PHP-FIG",
|
||||||
|
"homepage": "http://www.php-fig.org/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Common interface for HTTP server-side middleware",
|
||||||
|
"keywords": [
|
||||||
|
"http",
|
||||||
|
"http-interop",
|
||||||
|
"middleware",
|
||||||
|
"psr",
|
||||||
|
"psr-15",
|
||||||
|
"psr-7",
|
||||||
|
"request",
|
||||||
|
"response"
|
||||||
|
],
|
||||||
|
"time": "2018-01-22T17:08:31+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "psr/log",
|
"name": "psr/log",
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
@@ -927,16 +1135,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "slim/slim",
|
"name": "slim/slim",
|
||||||
"version": "3.9.2",
|
"version": "3.10.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/slimphp/Slim.git",
|
"url": "https://github.com/slimphp/Slim.git",
|
||||||
"reference": "4086d0106cf5a7135c69fce4161fe355a8feb118"
|
"reference": "d8aabeacc3688b25e2f2dd2db91df91ec6fdd748"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/slimphp/Slim/zipball/4086d0106cf5a7135c69fce4161fe355a8feb118",
|
"url": "https://api.github.com/repos/slimphp/Slim/zipball/d8aabeacc3688b25e2f2dd2db91df91ec6fdd748",
|
||||||
"reference": "4086d0106cf5a7135c69fce4161fe355a8feb118",
|
"reference": "d8aabeacc3688b25e2f2dd2db91df91ec6fdd748",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -994,7 +1202,7 @@
|
|||||||
"micro",
|
"micro",
|
||||||
"router"
|
"router"
|
||||||
],
|
],
|
||||||
"time": "2017-11-26T19:13:09+00:00"
|
"time": "2018-04-19T19:29:08+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/debug",
|
"name": "symfony/debug",
|
||||||
@@ -1227,6 +1435,166 @@
|
|||||||
"description": "Symfony Translation Component",
|
"description": "Symfony Translation Component",
|
||||||
"homepage": "https://symfony.com",
|
"homepage": "https://symfony.com",
|
||||||
"time": "2018-02-22T10:50:29+00:00"
|
"time": "2018-02-22T10:50:29+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tuupola/callable-handler",
|
||||||
|
"version": "0.3.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/tuupola/callable-handler.git",
|
||||||
|
"reference": "5141efa1e974687a3fa53338811a988198f50662"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/tuupola/callable-handler/zipball/5141efa1e974687a3fa53338811a988198f50662",
|
||||||
|
"reference": "5141efa1e974687a3fa53338811a988198f50662",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.0",
|
||||||
|
"psr/http-server-middleware": "^1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"codedungeon/phpunit-result-printer": "^0.4.4",
|
||||||
|
"overtrue/phplint": "^1.0",
|
||||||
|
"phpunit/phpunit": "^6.5",
|
||||||
|
"squizlabs/php_codesniffer": "^3.2",
|
||||||
|
"tuupola/http-factory": "^0.3.0",
|
||||||
|
"zendframework/zend-diactoros": "^1.6"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Tuupola\\Middleware\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Mika Tuupola",
|
||||||
|
"email": "tuupola@appelsiini.net",
|
||||||
|
"homepage": "https://appelsiini.net/",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Compatibility layer for PSR-7 double pass and PSR-15 middlewares.",
|
||||||
|
"homepage": "https://github.com/tuupola/callable-handler",
|
||||||
|
"keywords": [
|
||||||
|
"middleware",
|
||||||
|
"psr-15",
|
||||||
|
"psr-7"
|
||||||
|
],
|
||||||
|
"time": "2018-01-23T04:07:25+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tuupola/cors-middleware",
|
||||||
|
"version": "0.7.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/tuupola/cors-middleware.git",
|
||||||
|
"reference": "b0e2b7acacf22acae6ba029ee424fd6c073bb443"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/tuupola/cors-middleware/zipball/b0e2b7acacf22acae6ba029ee424fd6c073bb443",
|
||||||
|
"reference": "b0e2b7acacf22acae6ba029ee424fd6c073bb443",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"neomerx/cors-psr7": "^1.0",
|
||||||
|
"php": "^7.1",
|
||||||
|
"psr/http-server-middleware": "^1.0",
|
||||||
|
"tuupola/callable-handler": "^0.3.0",
|
||||||
|
"tuupola/http-factory": "^0.3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"codedungeon/phpunit-result-printer": "^0.4.4",
|
||||||
|
"equip/dispatch": "dev-approved-psr15 as 1.0.x-dev",
|
||||||
|
"overtrue/phplint": "^1.0",
|
||||||
|
"phpunit/phpunit": "^6.5",
|
||||||
|
"squizlabs/php_codesniffer": "^3.2",
|
||||||
|
"zendframework/zend-diactoros": "^1.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Tuupola\\Middleware\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Mika Tuupola",
|
||||||
|
"email": "tuupola@appelsiini.net",
|
||||||
|
"homepage": "http://www.appelsiini.net/",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PSR-7 and PSR-15 CORS middleware",
|
||||||
|
"homepage": "https://github.com/tuupola/cors-middleware",
|
||||||
|
"keywords": [
|
||||||
|
"cors",
|
||||||
|
"middleware",
|
||||||
|
"psr-15",
|
||||||
|
"psr-7"
|
||||||
|
],
|
||||||
|
"time": "2018-01-25T02:29:07+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tuupola/http-factory",
|
||||||
|
"version": "0.3.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/tuupola/http-factory.git",
|
||||||
|
"reference": "57b2e19ff3f4af0bbee4e31fd282689be351f1ad"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/tuupola/http-factory/zipball/57b2e19ff3f4af0bbee4e31fd282689be351f1ad",
|
||||||
|
"reference": "57b2e19ff3f4af0bbee4e31fd282689be351f1ad",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"http-interop/http-factory": "^0.3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"http-interop/http-factory-tests": "^0.3.0",
|
||||||
|
"overtrue/phplint": "^0.2.1",
|
||||||
|
"phpunit/phpunit": "^5.7",
|
||||||
|
"squizlabs/php_codesniffer": "^3.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Tuupola\\Http\\Factory\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Mika Tuupola",
|
||||||
|
"email": "tuupola@appelsiini.net",
|
||||||
|
"homepage": "http://www.appelsiini.net/",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Lightweight autodiscovering PSR-17 HTTP factories",
|
||||||
|
"homepage": "https://github.com/tuupola/http-factory",
|
||||||
|
"keywords": [
|
||||||
|
"http",
|
||||||
|
"psr-17",
|
||||||
|
"psr-7"
|
||||||
|
],
|
||||||
|
"time": "2017-07-15T22:03:15+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"packages-dev": [],
|
"packages-dev": [],
|
||||||
@@ -1235,6 +1603,8 @@
|
|||||||
"stability-flags": [],
|
"stability-flags": [],
|
||||||
"prefer-stable": false,
|
"prefer-stable": false,
|
||||||
"prefer-lowest": false,
|
"prefer-lowest": false,
|
||||||
"platform": [],
|
"platform": {
|
||||||
|
"php": ">=7.0"
|
||||||
|
},
|
||||||
"platform-dev": []
|
"platform-dev": []
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,26 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
define('MODE', 'production'); # Either "production" or "dev"
|
# Login credentials
|
||||||
define('CULTURE', 'en');
|
|
||||||
|
|
||||||
define('HTTP_USER', 'admin');
|
define('HTTP_USER', 'admin');
|
||||||
define('HTTP_PASSWORD', 'admin');
|
define('HTTP_PASSWORD', 'admin');
|
||||||
|
|
||||||
|
# Either "production" or "dev"
|
||||||
|
define('MODE', 'production');
|
||||||
|
|
||||||
|
# Either "en" or "de" or the filename (without extension) of
|
||||||
|
# one of the other available localization files in the "/localization" directory
|
||||||
|
define('CULTURE', 'en');
|
||||||
|
|
||||||
|
# The base url of your installation,
|
||||||
|
# should be just "/" when running directly under the root of a (sub)domain
|
||||||
|
# or for example "https:/example.com/grocy" when using a subdirectory
|
||||||
|
define('BASE_URL', '/');
|
||||||
|
|
||||||
|
# The plugin to use for external barcode lookups,
|
||||||
|
# must be the filename without .php extension and must be located in /data/plugins,
|
||||||
|
# see /data/plugins/DemoBarcodeLookupPlugin.php for an example implementation
|
||||||
|
define('STOCK_BARCODE_LOOKUP_PLUGIN', 'DemoBarcodeLookupPlugin');
|
||||||
|
|
||||||
|
# If, however, your webserver does not support URL rewriting,
|
||||||
|
# set this to true
|
||||||
|
define('DISABLE_URL_REWRITING', false);
|
||||||
|
@@ -4,8 +4,25 @@ namespace Grocy\Controllers;
|
|||||||
|
|
||||||
class BaseApiController extends BaseController
|
class BaseApiController extends BaseController
|
||||||
{
|
{
|
||||||
protected function ApiResponse($response)
|
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
{
|
{
|
||||||
return json_encode($response);
|
parent::__construct($container);
|
||||||
|
$this->OpenApiSpec = json_decode(file_get_contents(__DIR__ . '/../grocy.openapi.json'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $OpenApiSpec;
|
||||||
|
|
||||||
|
protected function ApiResponse($data)
|
||||||
|
{
|
||||||
|
return json_encode($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function VoidApiActionResponse($response, $success = true, $status = 200, $errorMessage = '')
|
||||||
|
{
|
||||||
|
return $response->withStatus($status)->withJson(array(
|
||||||
|
'success' => $success,
|
||||||
|
'error_message' => $errorMessage
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,13 +9,13 @@ use \Grocy\Services\LocalizationService;
|
|||||||
class BaseController
|
class BaseController
|
||||||
{
|
{
|
||||||
public function __construct(\Slim\Container $container) {
|
public function __construct(\Slim\Container $container) {
|
||||||
$this->AppContainer = $container;
|
|
||||||
|
|
||||||
$databaseService = new DatabaseService();
|
$databaseService = new DatabaseService();
|
||||||
$this->Database = $databaseService->GetDbConnection();
|
$this->Database = $databaseService->GetDbConnection();
|
||||||
|
|
||||||
$applicationService = new ApplicationService();
|
$applicationService = new ApplicationService();
|
||||||
$container->view->set('version', $applicationService->GetInstalledVersion());
|
$versionInfo = $applicationService->GetInstalledVersion();
|
||||||
|
$container->view->set('version', $versionInfo->Version);
|
||||||
|
$container->view->set('releaseDate', $versionInfo->ReleaseDate);
|
||||||
|
|
||||||
$localizationService = new LocalizationService(CULTURE);
|
$localizationService = new LocalizationService(CULTURE);
|
||||||
$container->view->set('localizationStrings', $localizationService->GetCurrentCultureLocalizations());
|
$container->view->set('localizationStrings', $localizationService->GetCurrentCultureLocalizations());
|
||||||
@@ -23,6 +23,12 @@ class BaseController
|
|||||||
{
|
{
|
||||||
return $localizationService->Localize($text, ...$placeholderValues);
|
return $localizationService->Localize($text, ...$placeholderValues);
|
||||||
});
|
});
|
||||||
|
$container->view->set('U', function($relativePath, $isResource = false) use($container)
|
||||||
|
{
|
||||||
|
return $container->UrlManager->ConstructUrl($relativePath, $isResource);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->AppContainer = $container;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected $AppContainer;
|
protected $AppContainer;
|
||||||
|
@@ -22,11 +22,26 @@ class BatteriesApiController extends BaseApiController
|
|||||||
$trackedTime = $request->getQueryParams()['tracked_time'];
|
$trackedTime = $request->getQueryParams()['tracked_time'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->ApiResponse(array('success' => $this->BatteriesService->TrackChargeCycle($args['batteryId'], $trackedTime)));
|
try
|
||||||
|
{
|
||||||
|
$this->BatteriesService->TrackChargeCycle($args['batteryId'], $trackedTime);
|
||||||
|
return $this->VoidApiActionResponse($response);
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function BatteryDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function BatteryDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
return $this->ApiResponse($this->BatteriesService->GetBatteryDetails($args['batteryId']));
|
try
|
||||||
|
{
|
||||||
|
return $this->ApiResponse($this->BatteriesService->GetBatteryDetails($args['batteryId']));
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,10 +22,16 @@ class BatteriesController extends BaseController
|
|||||||
$nextChargeTimes[$battery->id] = $this->BatteriesService->GetNextChargeTime($battery->id);
|
$nextChargeTimes[$battery->id] = $this->BatteriesService->GetNextChargeTime($battery->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$nextXDays = 5;
|
||||||
|
$countDueNextXDays = count(FindAllItemsInArrayByValue($nextChargeTimes, date('Y-m-d', strtotime("+$nextXDays days")), '<'));
|
||||||
|
$countOverdue = count(FindAllItemsInArrayByValue($nextChargeTimes, date('Y-m-d', strtotime('-1 days')), '<'));
|
||||||
return $this->AppContainer->view->render($response, 'batteriesoverview', [
|
return $this->AppContainer->view->render($response, 'batteriesoverview', [
|
||||||
'batteries' => $this->Database->batteries(),
|
'batteries' => $this->Database->batteries(),
|
||||||
'current' => $this->BatteriesService->GetCurrent(),
|
'current' => $this->BatteriesService->GetCurrent(),
|
||||||
'nextChargeTimes' => $nextChargeTimes
|
'nextChargeTimes' => $nextChargeTimes,
|
||||||
|
'nextXDays' => $nextXDays,
|
||||||
|
'countDueNextXDays' => $countDueNextXDays - $countOverdue,
|
||||||
|
'countOverdue' => $countOverdue
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,35 +6,75 @@ class GenericEntityApiController extends BaseApiController
|
|||||||
{
|
{
|
||||||
public function GetObjects(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function GetObjects(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
return $this->ApiResponse($this->Database->{$args['entity']}());
|
if ($this->IsValidEntity($args['entity']))
|
||||||
|
{
|
||||||
|
return $this->ApiResponse($this->Database->{$args['entity']}());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, 'Entity does not exist or is not exposed');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function GetObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function GetObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
return $this->ApiResponse($this->Database->{$args['entity']}($args['objectId']));
|
if ($this->IsValidEntity($args['entity']))
|
||||||
|
{
|
||||||
|
return $this->ApiResponse($this->Database->{$args['entity']}($args['objectId']));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, 'Entity does not exist or is not exposed');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function AddObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function AddObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
$newRow = $this->Database->{$args['entity']}()->createRow($request->getParsedBody());
|
if ($this->IsValidEntity($args['entity']))
|
||||||
$newRow->save();
|
{
|
||||||
$success = $newRow->isClean();
|
$newRow = $this->Database->{$args['entity']}()->createRow($request->getParsedBody());
|
||||||
return $this->ApiResponse(array('success' => $success));
|
$newRow->save();
|
||||||
|
$success = $newRow->isClean();
|
||||||
|
return $this->ApiResponse(array('success' => $success));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, 'Entity does not exist or is not exposed');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function EditObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function EditObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
$row = $this->Database->{$args['entity']}($args['objectId']);
|
if ($this->IsValidEntity($args['entity']))
|
||||||
$row->update($request->getParsedBody());
|
{
|
||||||
$success = $row->isClean();
|
$row = $this->Database->{$args['entity']}($args['objectId']);
|
||||||
return $this->ApiResponse(array('success' => $success));
|
$row->update($request->getParsedBody());
|
||||||
|
$success = $row->isClean();
|
||||||
|
return $this->ApiResponse(array('success' => $success));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, 'Entity does not exist or is not exposed');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function DeleteObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function DeleteObject(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
$row = $this->Database->{$args['entity']}($args['objectId']);
|
if ($this->IsValidEntity($args['entity']))
|
||||||
$row->delete();
|
{
|
||||||
$success = $row->isClean();
|
$row = $this->Database->{$args['entity']}($args['objectId']);
|
||||||
return $this->ApiResponse(array('success' => $success));
|
$row->delete();
|
||||||
|
$success = $row->isClean();
|
||||||
|
return $this->ApiResponse(array('success' => $success));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, 'Entity does not exist or is not exposed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function IsValidEntity($entity)
|
||||||
|
{
|
||||||
|
return in_array($entity, $this->OpenApiSpec->components->internalSchemas->ExposedEntity->enum);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,11 +22,26 @@ class HabitsApiController extends BaseApiController
|
|||||||
$trackedTime = $request->getQueryParams()['tracked_time'];
|
$trackedTime = $request->getQueryParams()['tracked_time'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->ApiResponse(array('success' => $this->HabitsService->TrackHabit($args['habitId'], $trackedTime)));
|
try
|
||||||
|
{
|
||||||
|
$this->HabitsService->TrackHabit($args['habitId'], $trackedTime);
|
||||||
|
return $this->VoidApiActionResponse($response);
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function HabitDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function HabitDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
return $this->ApiResponse($this->HabitsService->GetHabitDetails($args['habitId']));
|
try
|
||||||
|
{
|
||||||
|
return $this->ApiResponse($this->HabitsService->GetHabitDetails($args['habitId']));
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,10 +22,16 @@ class HabitsController extends BaseController
|
|||||||
$nextHabitTimes[$habit->id] = $this->HabitsService->GetNextHabitTime($habit->id);
|
$nextHabitTimes[$habit->id] = $this->HabitsService->GetNextHabitTime($habit->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$nextXDays = 5;
|
||||||
|
$countDueNextXDays = count(FindAllItemsInArrayByValue($nextHabitTimes, date('Y-m-d', strtotime("+$nextXDays days")), '<'));
|
||||||
|
$countOverdue = count(FindAllItemsInArrayByValue($nextHabitTimes, date('Y-m-d', strtotime('-1 days')), '<'));
|
||||||
return $this->AppContainer->view->render($response, 'habitsoverview', [
|
return $this->AppContainer->view->render($response, 'habitsoverview', [
|
||||||
'habits' => $this->Database->habits(),
|
'habits' => $this->Database->habits(),
|
||||||
'currentHabits' => $this->HabitsService->GetCurrentHabits(),
|
'currentHabits' => $this->HabitsService->GetCurrentHabits(),
|
||||||
'nextHabitTimes' => $nextHabitTimes
|
'nextHabitTimes' => $nextHabitTimes,
|
||||||
|
'nextXDays' => $nextXDays,
|
||||||
|
'countDueNextXDays' => $countDueNextXDays - $countOverdue,
|
||||||
|
'countOverdue' => $countOverdue
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,13 +9,15 @@ use \Grocy\Services\DemoDataGeneratorService;
|
|||||||
|
|
||||||
class LoginController extends BaseController
|
class LoginController extends BaseController
|
||||||
{
|
{
|
||||||
public function __construct(\Slim\Container $container)
|
public function __construct(\Slim\Container $container, string $sessionCookieName)
|
||||||
{
|
{
|
||||||
parent::__construct($container);
|
parent::__construct($container);
|
||||||
$this->SessionService = new SessionService();
|
$this->SessionService = new SessionService();
|
||||||
|
$this->SessionCookieName = $sessionCookieName;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected $SessionService;
|
protected $SessionService;
|
||||||
|
protected $SessionCookieName;
|
||||||
|
|
||||||
public function ProcessLogin(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function ProcessLogin(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
@@ -25,18 +27,18 @@ class LoginController extends BaseController
|
|||||||
if ($postParams['username'] === HTTP_USER && $postParams['password'] === HTTP_PASSWORD)
|
if ($postParams['username'] === HTTP_USER && $postParams['password'] === HTTP_PASSWORD)
|
||||||
{
|
{
|
||||||
$sessionKey = $this->SessionService->CreateSession();
|
$sessionKey = $this->SessionService->CreateSession();
|
||||||
setcookie('grocy_session', $sessionKey, time() + 31536000); // Cookie expires in 1 year, but session validity is up to SessionService
|
setcookie($this->SessionCookieName, $sessionKey, time() + 31536000); // Cookie expires in 1 year, but session validity is up to SessionService
|
||||||
|
|
||||||
return $response->withRedirect('/');
|
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/'));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return $response->withRedirect('/login?invalid=true');
|
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/login?invalid=true'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return $response->withRedirect('/login?invalid=true');
|
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/login?invalid=true'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,8 +49,8 @@ class LoginController extends BaseController
|
|||||||
|
|
||||||
public function Logout(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function Logout(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
$this->SessionService->RemoveSession($_COOKIE['grocy_session']);
|
$this->SessionService->RemoveSession($_COOKIE[$this->SessionCookieName]);
|
||||||
return $response->withRedirect('/');
|
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function Root(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function Root(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
@@ -64,6 +66,11 @@ class LoginController extends BaseController
|
|||||||
$demoDataGeneratorService->PopulateDemoData();
|
$demoDataGeneratorService->PopulateDemoData();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $response->withRedirect('/stockoverview');
|
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/stockoverview'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetSessionCookieName()
|
||||||
|
{
|
||||||
|
return $this->SessionCookieName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
48
controllers/OpenApiController.php
Normal file
48
controllers/OpenApiController.php
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
|
use \Grocy\Services\ApplicationService;
|
||||||
|
use \Grocy\Services\ApiKeyService;
|
||||||
|
|
||||||
|
class OpenApiController extends BaseApiController
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->ApiKeyService = new ApiKeyService();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $ApiKeyService;
|
||||||
|
|
||||||
|
public function DocumentationUi(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'openapiui');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function DocumentationSpec(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$applicationService = new ApplicationService();
|
||||||
|
|
||||||
|
$versionInfo = $applicationService->GetInstalledVersion();
|
||||||
|
$this->OpenApiSpec->info->version = $versionInfo->Version;
|
||||||
|
$this->OpenApiSpec->info->description = str_replace('PlaceHolderManageApiKeysUrl', $this->AppContainer->UrlManager->ConstructUrl('/manageapikeys'), $this->OpenApiSpec->info->description);
|
||||||
|
$this->OpenApiSpec->servers[0]->url = $this->AppContainer->UrlManager->ConstructUrl('/api');
|
||||||
|
|
||||||
|
return $this->ApiResponse($this->OpenApiSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ApiKeysList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
return $this->AppContainer->view->render($response, 'manageapikeys', [
|
||||||
|
'apiKeys' => $this->Database->api_keys()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function CreateNewApiKey(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
$newApiKey = $this->ApiKeyService->CreateApiKey();
|
||||||
|
$newApiKeyId = $this->ApiKeyService->GetApiKeyId($newApiKey);
|
||||||
|
return $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl("/manageapikeys?CreatedApiKeyId=$newApiKeyId"));
|
||||||
|
}
|
||||||
|
}
|
@@ -16,7 +16,14 @@ class StockApiController extends BaseApiController
|
|||||||
|
|
||||||
public function ProductDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function ProductDetails(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
return $this->ApiResponse($this->StockService->GetProductDetails($args['productId']));
|
try
|
||||||
|
{
|
||||||
|
return $this->ApiResponse($this->StockService->GetProductDetails($args['productId']));
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function AddProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function AddProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
@@ -33,7 +40,15 @@ class StockApiController extends BaseApiController
|
|||||||
$transactionType = $request->getQueryParams()['transactiontype'];
|
$transactionType = $request->getQueryParams()['transactiontype'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->ApiResponse(array('success' => $this->StockService->AddProduct($args['productId'], $args['amount'], $bestBeforeDate, $transactionType)));
|
try
|
||||||
|
{
|
||||||
|
$this->StockService->AddProduct($args['productId'], $args['amount'], $bestBeforeDate, $transactionType);
|
||||||
|
return $this->VoidApiActionResponse($response);
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function ConsumeProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function ConsumeProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
@@ -50,7 +65,15 @@ class StockApiController extends BaseApiController
|
|||||||
$transactionType = $request->getQueryParams()['transactiontype'];
|
$transactionType = $request->getQueryParams()['transactiontype'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->ApiResponse(array('success' => $this->StockService->ConsumeProduct($args['productId'], $args['amount'], $spoiled, $transactionType)));
|
try
|
||||||
|
{
|
||||||
|
$this->StockService->ConsumeProduct($args['productId'], $args['amount'], $spoiled, $transactionType);
|
||||||
|
return $this->VoidApiActionResponse($response);
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function InventoryProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function InventoryProduct(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
@@ -61,7 +84,15 @@ class StockApiController extends BaseApiController
|
|||||||
$bestBeforeDate = $request->getQueryParams()['bestbeforedate'];
|
$bestBeforeDate = $request->getQueryParams()['bestbeforedate'];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->ApiResponse(array('success' => $this->StockService->InventoryProduct($args['productId'], $args['newAmount'], $bestBeforeDate)));
|
try
|
||||||
|
{
|
||||||
|
$this->StockService->InventoryProduct($args['productId'], $args['newAmount'], $bestBeforeDate);
|
||||||
|
return $this->VoidApiActionResponse($response);
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function CurrentStock(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function CurrentStock(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
@@ -69,9 +100,27 @@ class StockApiController extends BaseApiController
|
|||||||
return $this->ApiResponse($this->StockService->GetCurrentStock());
|
return $this->ApiResponse($this->StockService->GetCurrentStock());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function AddmissingProductsToShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function AddMissingProductsToShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
$this->StockService->AddMissingProductsToShoppingList();
|
$this->StockService->AddMissingProductsToShoppingList();
|
||||||
return $this->ApiResponse(array('success' => true));
|
return $this->VoidApiActionResponse($response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ExternalBarcodeLookup(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$addFoundProduct = false;
|
||||||
|
if (isset($request->getQueryParams()['add']) && ($request->getQueryParams()['add'] === 'true' || $request->getQueryParams()['add'] === 1))
|
||||||
|
{
|
||||||
|
$addFoundProduct = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->ApiResponse($this->StockService->ExternalBarcodeLookup($args['barcode'], $addFoundProduct));
|
||||||
|
}
|
||||||
|
catch (\Exception $ex)
|
||||||
|
{
|
||||||
|
return $this->VoidApiActionResponse($response, false, 400, $ex->getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -17,11 +17,18 @@ class StockController extends BaseController
|
|||||||
|
|
||||||
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
public function Overview(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||||
{
|
{
|
||||||
|
$currentStock = $this->StockService->GetCurrentStock();
|
||||||
|
$nextXDays = 5;
|
||||||
|
$countExpiringNextXDays = count(FindAllObjectsInArrayByPropertyValue($currentStock, 'best_before_date', date('Y-m-d', strtotime('+5 days')), '<'));
|
||||||
|
$countAlreadyExpired = count(FindAllObjectsInArrayByPropertyValue($currentStock, 'best_before_date', date('Y-m-d', strtotime('-1 days')), '<'));
|
||||||
return $this->AppContainer->view->render($response, 'stockoverview', [
|
return $this->AppContainer->view->render($response, 'stockoverview', [
|
||||||
'products' => $this->Database->products(),
|
'products' => $this->Database->products(),
|
||||||
'quantityunits' => $this->Database->quantity_units(),
|
'quantityunits' => $this->Database->quantity_units(),
|
||||||
'currentStock' => $this->StockService->GetCurrentStock(),
|
'currentStock' => $currentStock,
|
||||||
'missingProducts' => $this->StockService->GetMissingProducts()
|
'missingProducts' => $this->StockService->GetMissingProducts(),
|
||||||
|
'nextXDays' => $nextXDays,
|
||||||
|
'countExpiringNextXDays' => $countExpiringNextXDays,
|
||||||
|
'countAlreadyExpired' => $countAlreadyExpired
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1
data/.gitignore
vendored
1
data/.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
*
|
*
|
||||||
!.gitignore
|
!.gitignore
|
||||||
!viewcache
|
!viewcache
|
||||||
|
!plugins
|
||||||
|
3
data/plugins/.gitignore
vendored
Normal file
3
data/plugins/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
|
!DemoBarcodeLookupPlugin.php
|
78
data/plugins/DemoBarcodeLookupPlugin.php
Normal file
78
data/plugins/DemoBarcodeLookupPlugin.php
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use \Grocy\Helpers\BaseBarcodeLookupPlugin;
|
||||||
|
|
||||||
|
/*
|
||||||
|
This class must extend BaseBarcodeLookupPlugin (in namespace \Grocy\Helpers)
|
||||||
|
*/
|
||||||
|
class DemoBarcodeLookupPlugin extends BaseBarcodeLookupPlugin
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
To use this plugin, configure it in data/config.php like this:
|
||||||
|
define('STOCK_BARCODE_LOOKUP_PLUGIN', 'DemoBarcodeLookupPlugin');
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
To try it:
|
||||||
|
Call the API function at /api/stock/external-barcode-lookup/{barcode}
|
||||||
|
|
||||||
|
When you also add ?add=true as a query parameter to the API call,
|
||||||
|
on a successful lookup the product is added to the database and in the output
|
||||||
|
the new product id is included (automatically, nothing to do here in the plugin)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Provided references:
|
||||||
|
|
||||||
|
$this->Locations contains all locations
|
||||||
|
$this->QuantityUnits contains all quantity units
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Useful hints:
|
||||||
|
|
||||||
|
Get a quantity unit by name:
|
||||||
|
$quantityUnit = FindObjectInArrayByPropertyValue($this->QuantityUnits, 'name', 'Piece');
|
||||||
|
|
||||||
|
Get a location by name:
|
||||||
|
$location = FindObjectInArrayByPropertyValue($this->Locations, 'name', 'Fridge');
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
This class must implement the protected abstract function ExecuteLookup($barcode),
|
||||||
|
which is called with the barcode that needs to be looked up and must return an
|
||||||
|
associative array of the product model or null, when nothing was found for the barcode.
|
||||||
|
|
||||||
|
The returned array must contain at least these properties:
|
||||||
|
array(
|
||||||
|
'name' => '',
|
||||||
|
'location_id' => 1, // A valid id of a location object, check against $this->Locations
|
||||||
|
'qu_id_purchase' => 1, // A valid id of quantity unit object, check against $this->QuantityUnits
|
||||||
|
'qu_id_stock' => 1, // A valid id of quantity unit object, check against $this->QuantityUnits
|
||||||
|
'qu_factor_purchase_to_stock' => 1, // Normally 1 when quantity unit stock and purchase is the same
|
||||||
|
'barcode' => $barcode // The barcode of the product, maybe just pass through $barcode or manipulate it if necessary
|
||||||
|
)
|
||||||
|
*/
|
||||||
|
protected function ExecuteLookup($barcode)
|
||||||
|
{
|
||||||
|
if ($barcode === 'x') // Demonstration when nothing is found
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
elseif ($barcode === 'e') // Demonstration when an error occurred
|
||||||
|
{
|
||||||
|
throw new \Exception('This is the error message from the plugin...');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
'name' => 'LookedUpProduct_' . RandomString(5),
|
||||||
|
'location_id' => $this->Locations[0]->id,
|
||||||
|
'qu_id_purchase' => $this->QuantityUnits[0]->id,
|
||||||
|
'qu_id_stock' => $this->QuantityUnits[0]->id,
|
||||||
|
'qu_factor_purchase_to_stock' => 1,
|
||||||
|
'barcode' => $barcode
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1384
grocy.openapi.json
Normal file
1384
grocy.openapi.json
Normal file
File diff suppressed because it is too large
Load Diff
80
helpers/BaseBarcodeLookupPlugin.php
Normal file
80
helpers/BaseBarcodeLookupPlugin.php
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Helpers;
|
||||||
|
|
||||||
|
abstract class BaseBarcodeLookupPlugin
|
||||||
|
{
|
||||||
|
final public function __construct($locations, $quantityUnits)
|
||||||
|
{
|
||||||
|
$this->Locations = $locations;
|
||||||
|
$this->QuantityUnits = $quantityUnits;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $Locations;
|
||||||
|
protected $QuantityUnits;
|
||||||
|
|
||||||
|
abstract protected function ExecuteLookup($barcode);
|
||||||
|
|
||||||
|
final public function Lookup($barcode)
|
||||||
|
{
|
||||||
|
$pluginOutput = $this->ExecuteLookup($barcode);
|
||||||
|
|
||||||
|
if ($pluginOutput === null)
|
||||||
|
{
|
||||||
|
return $pluginOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plugin must return an associative array
|
||||||
|
if (!is_array($pluginOutput))
|
||||||
|
{
|
||||||
|
throw new \Exception('Plugin output must be an associative array');
|
||||||
|
}
|
||||||
|
if (!IsAssociativeArray($pluginOutput)) // $pluginOutput is at least an indexed array here
|
||||||
|
{
|
||||||
|
throw new \Exception('Plugin output must be an associative array');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for minimum needed properties
|
||||||
|
$minimunNeededProperties = array(
|
||||||
|
'name',
|
||||||
|
'location_id',
|
||||||
|
'qu_id_purchase',
|
||||||
|
'qu_id_stock',
|
||||||
|
'qu_factor_purchase_to_stock',
|
||||||
|
'barcode'
|
||||||
|
);
|
||||||
|
foreach ($minimunNeededProperties as $prop)
|
||||||
|
{
|
||||||
|
if (!array_key_exists($prop, $pluginOutput))
|
||||||
|
{
|
||||||
|
throw new \Exception("Plugin output does not provide needed property $prop");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// $pluginOutput contains all needed properties here
|
||||||
|
|
||||||
|
// Check referenced entity ids are valid
|
||||||
|
$locationId = $pluginOutput['location_id'];
|
||||||
|
if (FindObjectInArrayByPropertyValue($this->Locations, 'id', $locationId) === null)
|
||||||
|
{
|
||||||
|
throw new \Exception("Location $locationId is not a valid location id");
|
||||||
|
}
|
||||||
|
$quIdPurchase = $pluginOutput['qu_id_purchase'];
|
||||||
|
if (FindObjectInArrayByPropertyValue($this->QuantityUnits, 'id', $quIdPurchase) === null)
|
||||||
|
{
|
||||||
|
throw new \Exception("Location $quIdPurchase is not a valid quantity unit id");
|
||||||
|
}
|
||||||
|
$quIdStock = $pluginOutput['qu_id_stock'];
|
||||||
|
if (FindObjectInArrayByPropertyValue($this->QuantityUnits, 'id', $quIdStock) === null)
|
||||||
|
{
|
||||||
|
throw new \Exception("Location $quIdStock is not a valid quantity unit id");
|
||||||
|
}
|
||||||
|
$quFactor = $pluginOutput['qu_factor_purchase_to_stock'];
|
||||||
|
if (empty($quFactor) || !is_numeric($quFactor))
|
||||||
|
{
|
||||||
|
throw new \Exception('Quantity unit factor is empty or not a number');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pluginOutput;
|
||||||
|
}
|
||||||
|
}
|
37
helpers/UrlManager.php
Normal file
37
helpers/UrlManager.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Helpers;
|
||||||
|
|
||||||
|
class UrlManager
|
||||||
|
{
|
||||||
|
public function __construct(string $basePath)
|
||||||
|
{
|
||||||
|
if ($basePath === '/')
|
||||||
|
{
|
||||||
|
$this->BasePath = $this->GetBaseUrl();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->BasePath = $basePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $BasePath;
|
||||||
|
|
||||||
|
public function ConstructUrl($relativePath, $isResource = false)
|
||||||
|
{
|
||||||
|
if (DISABLE_URL_REWRITING === false || $isResource === true)
|
||||||
|
{
|
||||||
|
return rtrim($this->BasePath, '/') . $relativePath;
|
||||||
|
}
|
||||||
|
else // Is not a resource and URL rewriting is disabled
|
||||||
|
{
|
||||||
|
return rtrim($this->BasePath, '/') . '/index.php' . $relativePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function GetBaseUrl()
|
||||||
|
{
|
||||||
|
return (isset($_SERVER['HTTPS']) ? "https" : "http") . "://$_SERVER[HTTP_HOST]";
|
||||||
|
}
|
||||||
|
}
|
@@ -45,6 +45,38 @@ function FindAllObjectsInArrayByPropertyValue($array, $propertyName, $propertyVa
|
|||||||
return $returnArray;
|
return $returnArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function FindAllItemsInArrayByValue($array, $value, $operator = '==')
|
||||||
|
{
|
||||||
|
$returnArray = array();
|
||||||
|
|
||||||
|
foreach($array as $item)
|
||||||
|
{
|
||||||
|
switch($operator)
|
||||||
|
{
|
||||||
|
case '==':
|
||||||
|
if($item == $value)
|
||||||
|
{
|
||||||
|
$returnArray[] = $item;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '>':
|
||||||
|
if($item > $value)
|
||||||
|
{
|
||||||
|
$returnArray[] = $item;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '<':
|
||||||
|
if($item < $value)
|
||||||
|
{
|
||||||
|
$returnArray[] = $item;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $returnArray;
|
||||||
|
}
|
||||||
|
|
||||||
function SumArrayValue($array, $propertyName)
|
function SumArrayValue($array, $propertyName)
|
||||||
{
|
{
|
||||||
$sum = 0;
|
$sum = 0;
|
||||||
@@ -61,3 +93,20 @@ function GetClassConstants($className)
|
|||||||
$r = new ReflectionClass($className);
|
$r = new ReflectionClass($className);
|
||||||
return $r->getConstants();
|
return $r->getConstants();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function RandomString($length, $allowedChars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ')
|
||||||
|
{
|
||||||
|
$randomString = '';
|
||||||
|
for ($i = 0; $i < $length; $i++)
|
||||||
|
{
|
||||||
|
$randomString .= $allowedChars[rand(0, strlen($allowedChars) - 1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $randomString;
|
||||||
|
}
|
||||||
|
|
||||||
|
function IsAssociativeArray(array $array)
|
||||||
|
{
|
||||||
|
$keys = array_keys($array);
|
||||||
|
return array_keys($keys) !== $keys;
|
||||||
|
}
|
@@ -88,6 +88,47 @@ return array(
|
|||||||
'Username' => 'Benutzername',
|
'Username' => 'Benutzername',
|
||||||
'Password' => 'Passwort',
|
'Password' => 'Passwort',
|
||||||
'Invalid credentials, please try again' => 'Ungültige Zugangsdaten, bitte versuche es erneut',
|
'Invalid credentials, please try again' => 'Ungültige Zugangsdaten, bitte versuche es erneut',
|
||||||
|
'Are you sure to delete battery "#1"?' => 'Battery "#1" wirklich löschen?',
|
||||||
|
'Yes' => 'Ja',
|
||||||
|
'No' => 'Nein',
|
||||||
|
'Are you sure to delete habit "#1"?' => 'Gewohnheit "#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?',
|
||||||
|
'Create or assign product' => 'Produkt erstellen oder verknüpfen',
|
||||||
|
'Cancel' => 'Abbrechen',
|
||||||
|
'Add as new product' => 'Als neues Produkt hinzufügen',
|
||||||
|
'Add as barcode to existing product' => 'Barcode vorhandenem Produkt zuweisen',
|
||||||
|
'Add as new product and prefill barcode' => 'Neues Produkt erstellen und Barcode vorbelegen',
|
||||||
|
'Are you sure to delete quantity unit "#1"?' => 'Mengeneinheit "#1" wirklich löschen?',
|
||||||
|
'Are you sure to delete product "#1"?' => 'Produkt "#1" wirklich löschen?',
|
||||||
|
'Are you sure to delete location "#1"?' => 'Standort "#1" wirklich löschen?',
|
||||||
|
'Manage API keys' => 'API-Keys verwalten',
|
||||||
|
'REST API & data model documentation' => 'REST-API & Datenmodell Dokumentation',
|
||||||
|
'API keys' => 'API-Keys',
|
||||||
|
'Create new API key' => 'Neuen API-Key erstellen',
|
||||||
|
'API key' => 'API-Key',
|
||||||
|
'Expires' => 'Läuft ab',
|
||||||
|
'Created' => 'Erstellt',
|
||||||
|
'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 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',
|
||||||
|
'Removed #1 #2 of #3 from stock' => '#1 #2 #3 aus dem Bestand entfernt',
|
||||||
|
'About grocy' => 'Über grocy',
|
||||||
|
'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 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 habits are overdue to be done' => '#1 Gewohnheiten sind überfällig',
|
||||||
|
'Released on' => 'Veröffentlicht am',
|
||||||
|
'Consume #3 #1 of #2' => 'Verbrauche #3 #1 #2',
|
||||||
|
'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',
|
||||||
|
'Tracked execution of habit #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',
|
||||||
|
'Consume all #1 which are currently in stock' => 'Verbrauche den kompletten Bestand von #1',
|
||||||
|
'All' => 'Alle',
|
||||||
|
'Track charge cycle of battery #1' => 'Erfasse einen Ladezyklus für Batterie #1',
|
||||||
|
'Track execution of habit #1' => 'Erfasse eine Ausführung von #1',
|
||||||
|
|
||||||
//Constants
|
//Constants
|
||||||
'manually' => 'Manuell',
|
'manually' => 'Manuell',
|
||||||
|
58
middleware/ApiKeyAuthMiddleware.php
Normal file
58
middleware/ApiKeyAuthMiddleware.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Middleware;
|
||||||
|
|
||||||
|
use \Grocy\Services\SessionService;
|
||||||
|
use \Grocy\Services\ApiKeyService;
|
||||||
|
|
||||||
|
class ApiKeyAuthMiddleware extends BaseMiddleware
|
||||||
|
{
|
||||||
|
public function __construct(\Slim\Container $container, string $sessionCookieName, string $apiKeyHeaderName)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->SessionCookieName = $sessionCookieName;
|
||||||
|
$this->ApiKeyHeaderName = $apiKeyHeaderName;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $SessionCookieName;
|
||||||
|
protected $ApiKeyHeaderName;
|
||||||
|
|
||||||
|
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next)
|
||||||
|
{
|
||||||
|
$route = $request->getAttribute('route');
|
||||||
|
$routeName = $route->getName();
|
||||||
|
|
||||||
|
if ($this->ApplicationService->IsDemoInstallation())
|
||||||
|
{
|
||||||
|
$response = $next($request, $response);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$validSession = true;
|
||||||
|
$validApiKey = true;
|
||||||
|
|
||||||
|
$sessionService = new SessionService();
|
||||||
|
if (!isset($_COOKIE[$this->SessionCookieName]) || !$sessionService->IsValidSession($_COOKIE[$this->SessionCookieName]))
|
||||||
|
{
|
||||||
|
$validSession = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$apiKeyService = new ApiKeyService();
|
||||||
|
if (!$request->hasHeader($this->ApiKeyHeaderName) || !$apiKeyService->IsValidApiKey($request->getHeaderLine($this->ApiKeyHeaderName)))
|
||||||
|
{
|
||||||
|
$validApiKey = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$validSession && !$validApiKey)
|
||||||
|
{
|
||||||
|
$response = $response->withStatus(401);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$response = $next($request, $response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
@@ -2,11 +2,16 @@
|
|||||||
|
|
||||||
namespace Grocy\Middleware;
|
namespace Grocy\Middleware;
|
||||||
|
|
||||||
|
use \Grocy\Services\ApplicationService;
|
||||||
|
|
||||||
class BaseMiddleware
|
class BaseMiddleware
|
||||||
{
|
{
|
||||||
public function __construct(\Slim\Container $container) {
|
public function __construct(\Slim\Container $container)
|
||||||
$this->container = $container;
|
{
|
||||||
|
$this->AppContainer = $container;
|
||||||
|
$this->ApplicationService = new ApplicationService();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected $container;
|
protected $AppContainer;
|
||||||
|
protected $ApplicationService;
|
||||||
}
|
}
|
||||||
|
@@ -13,7 +13,7 @@ class CliMiddleware extends BaseMiddleware
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$response = $next($request, $response, $next);
|
$response = $next($request, $response);
|
||||||
return $response->withHeader('Content-Type', 'text/plain');
|
return $response->withHeader('Content-Type', 'text/plain');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,7 +6,7 @@ class JsonMiddleware extends BaseMiddleware
|
|||||||
{
|
{
|
||||||
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next)
|
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next)
|
||||||
{
|
{
|
||||||
$response = $next($request, $response, $next);
|
$response = $next($request, $response);
|
||||||
return $response->withHeader('Content-Type', 'application/json');
|
return $response->withHeader('Content-Type', 'application/json');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,24 +6,35 @@ use \Grocy\Services\SessionService;
|
|||||||
|
|
||||||
class SessionAuthMiddleware extends BaseMiddleware
|
class SessionAuthMiddleware extends BaseMiddleware
|
||||||
{
|
{
|
||||||
|
public function __construct(\Slim\Container $container, string $sessionCookieName)
|
||||||
|
{
|
||||||
|
parent::__construct($container);
|
||||||
|
$this->SessionCookieName = $sessionCookieName;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected $SessionCookieName;
|
||||||
|
|
||||||
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next)
|
public function __invoke(\Slim\Http\Request $request, \Slim\Http\Response $response, callable $next)
|
||||||
{
|
{
|
||||||
$route = $request->getAttribute('route');
|
$route = $request->getAttribute('route');
|
||||||
$routeName = $route->getName();
|
$routeName = $route->getName();
|
||||||
|
|
||||||
if ($routeName === 'root')
|
if ($routeName === 'root' || $this->ApplicationService->IsDemoInstallation())
|
||||||
{
|
{
|
||||||
|
define('AUTHENTICATED', $this->ApplicationService->IsDemoInstallation());
|
||||||
$response = $next($request, $response);
|
$response = $next($request, $response);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$sessionService = new SessionService();
|
$sessionService = new SessionService();
|
||||||
if ((!isset($_COOKIE['grocy_session']) || !$sessionService->IsValidSession($_COOKIE['grocy_session'])) && $routeName !== 'login')
|
if ((!isset($_COOKIE[$this->SessionCookieName]) || !$sessionService->IsValidSession($_COOKIE[$this->SessionCookieName])) && $routeName !== 'login')
|
||||||
{
|
{
|
||||||
$response = $response->withRedirect('/login');
|
define('AUTHENTICATED', false);
|
||||||
|
$response = $response->withRedirect($this->AppContainer->UrlManager->ConstructUrl('/login'));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
define('AUTHENTICATED', $routeName !== 'login');
|
||||||
$response = $next($request, $response);
|
$response = $next($request, $response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
7
migrations/0022.sql
Normal file
7
migrations/0022.sql
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE api_keys (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||||
|
api_key TEXT NOT NULL UNIQUE,
|
||||||
|
expires DATETIME,
|
||||||
|
last_used DATETIME,
|
||||||
|
row_created_timestamp DATETIME DEFAULT (datetime('now', 'localtime'))
|
||||||
|
)
|
1
migrations/0023.sql
Normal file
1
migrations/0023.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
DELETE FROM sessions
|
2
migrations/0024.sql
Normal file
2
migrations/0024.sql
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE sessions
|
||||||
|
ADD COLUMN last_used DATETIME
|
@@ -4,82 +4,78 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.navbar-fixed-top {
|
.navbar-fixed-top {
|
||||||
border: 0;
|
background-color: #e5e5e5;
|
||||||
|
border-bottom: 2px solid;
|
||||||
|
border-color: #d6d6d6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.navbar-brand {
|
||||||
display: none;
|
font-weight: bold;
|
||||||
|
letter-spacing: -5px;
|
||||||
|
font-size: 2.2em;
|
||||||
|
color: #0b024c !important;
|
||||||
|
margin-left: 0 !important;
|
||||||
|
padding-left: 5px !important;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-fixed-side {
|
||||||
|
top: 51px;
|
||||||
|
padding-top: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
background-color: #e5e5e5;
|
||||||
|
border-right: 2px solid #d6d6d6;
|
||||||
|
max-width: 260px;
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar {
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
.sidebar {
|
|
||||||
position: fixed;
|
|
||||||
top: 51px;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 1000;
|
|
||||||
display: block;
|
|
||||||
padding: 20px;
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
background-color: #e5e5e5;
|
|
||||||
border-right: 2px solid #d6d6d6;
|
|
||||||
min-width: 220px;
|
|
||||||
max-width: 260px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#navbar-mobile {
|
#navbar-mobile {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-copyright {
|
||||||
|
padding-bottom: 100px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-sidebar {
|
@media (max-width: 768px) {
|
||||||
margin-right: -21px;
|
.navbar-brand {
|
||||||
margin-bottom: 20px;
|
margin-left: 25px !important;
|
||||||
margin-left: -20px;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-sidebar > li > a {
|
.sidebar-nav > li > a {
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-sidebar > li > a:hover {
|
.sidebar-nav > li > a:hover {
|
||||||
box-shadow: inset 5px 0 0 #337ab7;
|
box-shadow: inset 5px 0 0 #337ab7;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-sidebar > li > a:focus {
|
.sidebar-nav > li > a:focus {
|
||||||
box-shadow: inset 5px 0 0 #ab2230;
|
box-shadow: inset 5px 0 0 #ab2230;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-sidebar > .active > a,
|
.sidebar-nav > .active > a,
|
||||||
.nav-sidebar > .active > a:hover,
|
.sidebar-nav > .active > a:hover,
|
||||||
.nav-sidebar > .active > a:focus {
|
.sidebar-nav > .active > a:focus {
|
||||||
background-color: #d6d6d6;
|
background-color: #d6d6d6;
|
||||||
box-shadow: inset 5px 0 0 #ab2230;
|
box-shadow: inset 5px 0 0 #ab2230;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-default {
|
.nav > li.disabled > a,
|
||||||
background-color: #e5e5e5;
|
.navbar-default .navbar-nav > .disabled > a {
|
||||||
}
|
color: #a7a7a7;
|
||||||
|
|
||||||
.main {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.main {
|
|
||||||
padding-right: 40px;
|
|
||||||
padding-left: 40px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.main .page-header {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-copyright {
|
.nav-copyright {
|
||||||
@@ -88,6 +84,34 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-copyright > li > a {
|
||||||
|
padding-top: 0 !important;
|
||||||
|
padding-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-default .navbar-nav > .open > a {
|
||||||
|
background-color: #d6d6d6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu > li > a:hover,
|
||||||
|
.dropdown-menu > li > a:focus {
|
||||||
|
background-color: #e5e5e5 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.well {
|
||||||
|
background-color: #e5e5e5;
|
||||||
|
padding-right: 25px;
|
||||||
|
padding-left: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
vertical-align: middle !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.responsive-button {
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
.discrete-link {
|
.discrete-link {
|
||||||
color: inherit !important;
|
color: inherit !important;
|
||||||
transition: all 0.3s !important;
|
transition: all 0.3s !important;
|
||||||
@@ -105,21 +129,6 @@ a.discrete-link:focus {
|
|||||||
transition: all 0.3s !important;
|
transition: all 0.3s !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-fixed-top {
|
|
||||||
border-bottom: 2px solid;
|
|
||||||
border-color: #d6d6d6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-brand {
|
|
||||||
font-weight: bold;
|
|
||||||
letter-spacing: -5px;
|
|
||||||
font-size: 2.2em;
|
|
||||||
color: #0b024c !important;
|
|
||||||
margin-left: 0 !important;
|
|
||||||
padding-left: 5px !important;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.table td.fit-content,
|
.table td.fit-content,
|
||||||
.table th.fit-content {
|
.table th.fit-content {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@@ -144,6 +153,11 @@ a.discrete-link:focus {
|
|||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.discrete-content-separator-2x {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.warning-bg {
|
.warning-bg {
|
||||||
background-color: #fcf8e3 !important;
|
background-color: #fcf8e3 !important;
|
||||||
}
|
}
|
||||||
@@ -156,16 +170,15 @@ a.discrete-link:focus {
|
|||||||
background-color: #afd9ee !important;
|
background-color: #afd9ee !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discrete-content-separator {
|
#toast-container > div {
|
||||||
padding-top: 5px;
|
opacity: 1;
|
||||||
padding-bottom: 5px;
|
filter: alpha(opacity=100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.discrete-content-separator-2x {
|
.toast-success {
|
||||||
padding-top: 10px;
|
background-color: #4c994c;
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.well {
|
#toast-container > div {
|
||||||
background-color: #e5e5e5;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
@@ -14,6 +14,11 @@
|
|||||||
return localizedText;
|
return localizedText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
U = function(relativePath)
|
||||||
|
{
|
||||||
|
return Grocy.BaseUrl.replace(/\/$/, '') + relativePath;
|
||||||
|
}
|
||||||
|
|
||||||
if (!Grocy.ActiveNav.isEmpty())
|
if (!Grocy.ActiveNav.isEmpty())
|
||||||
{
|
{
|
||||||
var menuItem = $('.nav').find("[data-nav-for-page='" + Grocy.ActiveNav + "']");
|
var menuItem = $('.nav').find("[data-nav-for-page='" + Grocy.ActiveNav + "']");
|
||||||
@@ -21,11 +26,24 @@ if (!Grocy.ActiveNav.isEmpty())
|
|||||||
}
|
}
|
||||||
|
|
||||||
$.timeago.settings.allowFuture = true;
|
$.timeago.settings.allowFuture = true;
|
||||||
$('time.timeago').timeago();
|
RefreshContextualTimeago = function()
|
||||||
|
{
|
||||||
|
$('time.timeago').timeago();
|
||||||
|
}
|
||||||
|
RefreshContextualTimeago();
|
||||||
|
|
||||||
Grocy.FetchJson = function(url, success, error)
|
toastr.options = {
|
||||||
|
toastClass: 'alert',
|
||||||
|
closeButton: true,
|
||||||
|
timeOut: 20000,
|
||||||
|
extendedTimeOut: 5000
|
||||||
|
};
|
||||||
|
|
||||||
|
Grocy.Api = { };
|
||||||
|
Grocy.Api.Get = function(apiFunction, success, error)
|
||||||
{
|
{
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
|
var url = U('/api/' + apiFunction);
|
||||||
|
|
||||||
xhr.onreadystatechange = function()
|
xhr.onreadystatechange = function()
|
||||||
{
|
{
|
||||||
@@ -52,9 +70,10 @@ Grocy.FetchJson = function(url, success, error)
|
|||||||
xhr.send();
|
xhr.send();
|
||||||
};
|
};
|
||||||
|
|
||||||
Grocy.PostJson = function(url, jsonData, success, error)
|
Grocy.Api.Post = function(apiFunction, jsonData, success, error)
|
||||||
{
|
{
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
|
var url = U('/api/' + apiFunction);
|
||||||
|
|
||||||
xhr.onreadystatechange = function()
|
xhr.onreadystatechange = function()
|
||||||
{
|
{
|
||||||
|
@@ -1,14 +1,17 @@
|
|||||||
$(document).on('click', '.battery-delete-button', function(e)
|
$(document).on('click', '.battery-delete-button', function(e)
|
||||||
{
|
{
|
||||||
|
var objectName = $(e.currentTarget).attr('data-battery-name');
|
||||||
|
var objectId = $(e.currentTarget).attr('data-battery-id');
|
||||||
|
|
||||||
bootbox.confirm({
|
bootbox.confirm({
|
||||||
message: 'Delete battery <strong>' + $(e.target).attr('data-battery-name') + '</strong>?',
|
message: L('Are you sure to delete battery "#1"?', objectName),
|
||||||
buttons: {
|
buttons: {
|
||||||
confirm: {
|
confirm: {
|
||||||
label: 'Yes',
|
label: L('Yes'),
|
||||||
className: 'btn-success'
|
className: 'btn-success'
|
||||||
},
|
},
|
||||||
cancel: {
|
cancel: {
|
||||||
label: 'No',
|
label: L('No'),
|
||||||
className: 'btn-danger'
|
className: 'btn-danger'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -16,10 +19,10 @@
|
|||||||
{
|
{
|
||||||
if (result === true)
|
if (result === true)
|
||||||
{
|
{
|
||||||
Grocy.FetchJson('/api/delete-object/batteries/' + $(e.target).attr('data-battery-id'),
|
Grocy.Api.Get('delete-object/batteries/' + objectId,
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
window.location.href = '/batteries';
|
window.location.href = U('/batteries');
|
||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
|
@@ -1,5 +1,33 @@
|
|||||||
$('#batteries-overview-table').DataTable({
|
$('#batteries-overview-table').DataTable({
|
||||||
'pageLength': 50,
|
'pageLength': 50,
|
||||||
'order': [[1, 'desc']],
|
'order': [[2, 'desc']],
|
||||||
|
'columnDefs': [
|
||||||
|
{ 'orderable': false, 'targets': 0 }
|
||||||
|
],
|
||||||
'language': JSON.parse(L('datatables_localization'))
|
'language': JSON.parse(L('datatables_localization'))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.track-charge-cycle-button', function(e)
|
||||||
|
{
|
||||||
|
var batteryId = $(e.currentTarget).attr('data-battery-id');
|
||||||
|
var batteryName = $(e.currentTarget).attr('data-battery-name');
|
||||||
|
var trackedTime = moment().format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
|
||||||
|
Grocy.Api.Get('batteries/track-charge-cycle/' + batteryId + '?tracked_time=' + trackedTime,
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
$('#battery-' + batteryId + '-last-tracked-time').parent().effect('highlight', {}, 500);
|
||||||
|
$('#battery-' + batteryId + '-last-tracked-time').fadeOut(500, function () {
|
||||||
|
$(this).text(trackedTime).fadeIn(500);
|
||||||
|
});
|
||||||
|
$('#battery-' + batteryId + '-last-tracked-time-timeago').attr('datetime', trackedTime);
|
||||||
|
RefreshContextualTimeago();
|
||||||
|
|
||||||
|
toastr.success(L('Tracked charge cylce of battery #1 on #2', batteryName, trackedTime));
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
@@ -4,10 +4,10 @@
|
|||||||
|
|
||||||
if (Grocy.EditMode === 'create')
|
if (Grocy.EditMode === 'create')
|
||||||
{
|
{
|
||||||
Grocy.PostJson('/api/add-object/batteries', $('#battery-form').serializeJSON(),
|
Grocy.Api.Post('add-object/batteries', $('#battery-form').serializeJSON(),
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
window.location.href = '/batteries';
|
window.location.href = U('/batteries');
|
||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
@@ -17,10 +17,10 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Grocy.PostJson('/api/edit-object/batteries/' + Grocy.EditObjectId, $('#battery-form').serializeJSON(),
|
Grocy.Api.Post('edit-object/batteries/' + Grocy.EditObjectId, $('#battery-form').serializeJSON(),
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
window.location.href = '/batteries';
|
window.location.href = U('/batteries');
|
||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
|
@@ -4,13 +4,13 @@
|
|||||||
|
|
||||||
var jsonForm = $('#batterytracking-form').serializeJSON();
|
var jsonForm = $('#batterytracking-form').serializeJSON();
|
||||||
|
|
||||||
Grocy.FetchJson('/api/batteries/get-battery-details/' + jsonForm.battery_id,
|
Grocy.Api.Get('batteries/get-battery-details/' + jsonForm.battery_id,
|
||||||
function (batteryDetails)
|
function (batteryDetails)
|
||||||
{
|
{
|
||||||
Grocy.FetchJson('/api/batteries/track-charge-cycle/' + jsonForm.battery_id + '?tracked_time=' + $('#tracked_time').val(),
|
Grocy.Api.Get('batteries/track-charge-cycle/' + jsonForm.battery_id + '?tracked_time=' + $('#tracked_time').val(),
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
toastr.success('Tracked charge cylce of battery ' + batteryDetails.battery.name + ' on ' + $('#tracked_time').val());
|
toastr.success(L('Tracked charge cylce of battery #1 on #2', batteryDetails.battery.name, $('#tracked_time').val()));
|
||||||
|
|
||||||
$('#battery_id').val('');
|
$('#battery_id').val('');
|
||||||
$('#battery_id_text_input').focus();
|
$('#battery_id_text_input').focus();
|
||||||
|
@@ -2,7 +2,7 @@ Grocy.Components.BatteryCard = { };
|
|||||||
|
|
||||||
Grocy.Components.BatteryCard.Refresh = function(batteryId)
|
Grocy.Components.BatteryCard.Refresh = function(batteryId)
|
||||||
{
|
{
|
||||||
Grocy.FetchJson('/api/batteries/get-battery-details/' + batteryId,
|
Grocy.Api.Get('batteries/get-battery-details/' + batteryId,
|
||||||
function(batteryDetails)
|
function(batteryDetails)
|
||||||
{
|
{
|
||||||
$('#batterycard-battery-name').text(batteryDetails.battery.name);
|
$('#batterycard-battery-name').text(batteryDetails.battery.name);
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
Grocy.Components.HabitCard = { };
|
Grocy.Components.HabitCard = { };
|
||||||
|
|
||||||
Grocy.Components.HabitCard.Refresh = function (habitId)
|
Grocy.Components.HabitCard.Refresh = function(habitId)
|
||||||
{
|
{
|
||||||
Grocy.FetchJson('/api/habits/get-habit-details/' + habitId,
|
Grocy.Api.Get('habits/get-habit-details/' + habitId,
|
||||||
function(habitDetails)
|
function(habitDetails)
|
||||||
{
|
{
|
||||||
$('#habitcard-habit-name').text(habitDetails.habit.name);
|
$('#habitcard-habit-name').text(habitDetails.habit.name);
|
||||||
|
@@ -2,7 +2,7 @@ Grocy.Components.ProductCard = { };
|
|||||||
|
|
||||||
Grocy.Components.ProductCard.Refresh = function(productId)
|
Grocy.Components.ProductCard.Refresh = function(productId)
|
||||||
{
|
{
|
||||||
Grocy.FetchJson('/api/stock/get-product-details/' + productId,
|
Grocy.Api.Get('stock/get-product-details/' + productId,
|
||||||
function(productDetails)
|
function(productDetails)
|
||||||
{
|
{
|
||||||
$('#productcard-product-name').text(productDetails.product.name);
|
$('#productcard-product-name').text(productDetails.product.name);
|
||||||
|
@@ -10,13 +10,13 @@
|
|||||||
spoiled = 1;
|
spoiled = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Grocy.FetchJson('/api/stock/get-product-details/' + jsonForm.product_id,
|
Grocy.Api.Get('stock/get-product-details/' + jsonForm.product_id,
|
||||||
function (productDetails)
|
function (productDetails)
|
||||||
{
|
{
|
||||||
Grocy.FetchJson('/api/stock/consume-product/' + jsonForm.product_id + '/' + jsonForm.amount + '?spoiled=' + spoiled,
|
Grocy.Api.Get('stock/consume-product/' + jsonForm.product_id + '/' + jsonForm.amount + '?spoiled=' + spoiled,
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
toastr.success('Removed ' + jsonForm.amount + ' ' + productDetails.quantity_unit_stock.name + ' of ' + productDetails.product.name + ' from stock');
|
toastr.success(L('Removed #1 #2 of #3 from stock', jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.product.name));
|
||||||
|
|
||||||
$('#amount').val(1);
|
$('#amount').val(1);
|
||||||
$('#product_id').val('');
|
$('#product_id').val('');
|
||||||
@@ -46,7 +46,7 @@ $('#product_id').on('change', function(e)
|
|||||||
{
|
{
|
||||||
Grocy.Components.ProductCard.Refresh(productId);
|
Grocy.Components.ProductCard.Refresh(productId);
|
||||||
|
|
||||||
Grocy.FetchJson('/api/stock/get-product-details/' + productId,
|
Grocy.Api.Get('stock/get-product-details/' + productId,
|
||||||
function (productDetails)
|
function (productDetails)
|
||||||
{
|
{
|
||||||
$('#amount').attr('max', productDetails.stock_amount);
|
$('#amount').attr('max', productDetails.stock_amount);
|
||||||
@@ -60,7 +60,7 @@ $('#product_id').on('change', function(e)
|
|||||||
$('#product_id_text_input').addClass('has-error');
|
$('#product_id_text_input').addClass('has-error');
|
||||||
$('#product_id_text_input').parent('.input-group').addClass('has-error');
|
$('#product_id_text_input').parent('.input-group').addClass('has-error');
|
||||||
$('#product_id_text_input').closest('.form-group').addClass('has-error');
|
$('#product_id_text_input').closest('.form-group').addClass('has-error');
|
||||||
$('#product-error').text('This product is not in stock.');
|
$('#product-error').text(L('This product is not in stock'));
|
||||||
$('#product-error').show();
|
$('#product-error').show();
|
||||||
$('#product_id_text_input').focus();
|
$('#product_id_text_input').focus();
|
||||||
}
|
}
|
||||||
|
@@ -4,10 +4,10 @@
|
|||||||
|
|
||||||
if (Grocy.EditMode === 'create')
|
if (Grocy.EditMode === 'create')
|
||||||
{
|
{
|
||||||
Grocy.PostJson('/api/add-object/habits', $('#habit-form').serializeJSON(),
|
Grocy.Api.Post('add-object/habits', $('#habit-form').serializeJSON(),
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
window.location.href = '/habits';
|
window.location.href = U('/habits');
|
||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
@@ -17,10 +17,10 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Grocy.PostJson('/api/edit-object/habits/' + Grocy.EditObjectId, $('#habit-form').serializeJSON(),
|
Grocy.Api.Post('edit-object/habits/' + Grocy.EditObjectId, $('#habit-form').serializeJSON(),
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
window.location.href = '/habits';
|
window.location.href = U('/habits');
|
||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
@@ -41,7 +41,7 @@ $('.input-group-habit-period-type').on('change', function(e)
|
|||||||
|
|
||||||
if (periodType === 'dynamic-regular')
|
if (periodType === 'dynamic-regular')
|
||||||
{
|
{
|
||||||
$('#habit-period-type-info').text('This means it is estimated that a new "execution" of this habit is tracked ' + periodDays.toString() + ' days after the last was tracked.');
|
$('#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').show();
|
$('#habit-period-type-info').show();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@@ -1,14 +1,17 @@
|
|||||||
$(document).on('click', '.habit-delete-button', function(e)
|
$(document).on('click', '.habit-delete-button', function(e)
|
||||||
{
|
{
|
||||||
|
var objectName = $(e.currentTarget).attr('data-habit-name');
|
||||||
|
var objectId = $(e.currentTarget).attr('data-habit-id');
|
||||||
|
|
||||||
bootbox.confirm({
|
bootbox.confirm({
|
||||||
message: 'Delete habit <strong>' + $(e.target).attr('data-habit-name') + '</strong>?',
|
message: L('Are you sure to delete habit "#1"?', objectName),
|
||||||
buttons: {
|
buttons: {
|
||||||
confirm: {
|
confirm: {
|
||||||
label: 'Yes',
|
label: L('Yes'),
|
||||||
className: 'btn-success'
|
className: 'btn-success'
|
||||||
},
|
},
|
||||||
cancel: {
|
cancel: {
|
||||||
label: 'No',
|
label: L('No'),
|
||||||
className: 'btn-danger'
|
className: 'btn-danger'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -16,10 +19,10 @@
|
|||||||
{
|
{
|
||||||
if (result === true)
|
if (result === true)
|
||||||
{
|
{
|
||||||
Grocy.FetchJson('/api/delete-object/habits/' + $(e.target).attr('data-habit-id'),
|
Grocy.Api.Get('delete-object/habits/' + objectId,
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
window.location.href = '/habits';
|
window.location.href = U('/habits');
|
||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
|
@@ -1,5 +1,33 @@
|
|||||||
$('#habits-overview-table').DataTable({
|
$('#habits-overview-table').DataTable({
|
||||||
'pageLength': 50,
|
'pageLength': 50,
|
||||||
'order': [[1, 'desc']],
|
'order': [[2, 'desc']],
|
||||||
|
'columnDefs': [
|
||||||
|
{ 'orderable': false, 'targets': 0 }
|
||||||
|
],
|
||||||
'language': JSON.parse(L('datatables_localization'))
|
'language': JSON.parse(L('datatables_localization'))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(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(result)
|
||||||
|
{
|
||||||
|
$('#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);
|
||||||
|
RefreshContextualTimeago();
|
||||||
|
|
||||||
|
toastr.success(L('Tracked execution of habit #1 on #2', habitName, trackedTime));
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
@@ -4,13 +4,13 @@
|
|||||||
|
|
||||||
var jsonForm = $('#habittracking-form').serializeJSON();
|
var jsonForm = $('#habittracking-form').serializeJSON();
|
||||||
|
|
||||||
Grocy.FetchJson('/api/habits/get-habit-details/' + jsonForm.habit_id,
|
Grocy.Api.Get('habits/get-habit-details/' + jsonForm.habit_id,
|
||||||
function (habitDetails)
|
function (habitDetails)
|
||||||
{
|
{
|
||||||
Grocy.FetchJson('/api/habits/track-habit-execution/' + jsonForm.habit_id + '?tracked_time=' + $('#tracked_time').val(),
|
Grocy.Api.Get('habits/track-habit-execution/' + jsonForm.habit_id + '?tracked_time=' + $('#tracked_time').val(),
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
toastr.success('Tracked execution of habit ' + habitDetails.habit.name + ' on ' + $('#tracked_time').val());
|
toastr.success(L('Tracked execution of habit #1 on #2', habitDetails.habit.name, $('#tracked_time').val()));
|
||||||
|
|
||||||
$('#habit_id').val('');
|
$('#habit_id').val('');
|
||||||
$('#habit_id_text_input').focus();
|
$('#habit_id_text_input').focus();
|
||||||
|
@@ -4,10 +4,10 @@
|
|||||||
|
|
||||||
var jsonForm = $('#inventory-form').serializeJSON();
|
var jsonForm = $('#inventory-form').serializeJSON();
|
||||||
|
|
||||||
Grocy.FetchJson('/api/stock/get-product-details/' + jsonForm.product_id,
|
Grocy.Api.Get('stock/get-product-details/' + jsonForm.product_id,
|
||||||
function (productDetails)
|
function (productDetails)
|
||||||
{
|
{
|
||||||
Grocy.FetchJson('/api/stock/inventory-product/' + jsonForm.product_id + '/' + jsonForm.new_amount + '?bestbeforedate=' + $('#best_before_date').val(),
|
Grocy.Api.Get('stock/inventory-product/' + jsonForm.product_id + '/' + jsonForm.new_amount + '?bestbeforedate=' + $('#best_before_date').val(),
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
var addBarcode = GetUriParam('addbarcodetoselection');
|
var addBarcode = GetUriParam('addbarcodetoselection');
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
productDetails.product.barcode += ',' + addBarcode;
|
productDetails.product.barcode += ',' + addBarcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
Grocy.PostJson('/api/edit-object/products/' + productDetails.product.id, productDetails.product,
|
Grocy.Api.Get('edit-object/products/' + productDetails.product.id, productDetails.product,
|
||||||
function (result) { },
|
function (result) { },
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
@@ -32,11 +32,11 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
toastr.success('Stock amount of ' + productDetails.product.name + ' is now ' + jsonForm.new_amount.toString() + ' ' + productDetails.quantity_unit_stock.name);
|
toastr.success(L('Stock amount of #1 is now #2 #3', productDetails.product.name, jsonForm.new_amount, productDetails.quantity_unit_stock.name));
|
||||||
|
|
||||||
if (addBarcode !== undefined)
|
if (addBarcode !== undefined)
|
||||||
{
|
{
|
||||||
window.location.href = '/inventory';
|
window.location.href = U('/inventory');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -71,7 +71,7 @@ $('#product_id').on('change', function(e)
|
|||||||
{
|
{
|
||||||
Grocy.Components.ProductCard.Refresh(productId);
|
Grocy.Components.ProductCard.Refresh(productId);
|
||||||
|
|
||||||
Grocy.FetchJson('/api/stock/get-product-details/' + productId,
|
Grocy.Api.Get('stock/get-product-details/' + productId,
|
||||||
function(productDetails)
|
function(productDetails)
|
||||||
{
|
{
|
||||||
$('#new_amount').attr('not-equal', productDetails.stock_amount);
|
$('#new_amount').attr('not-equal', productDetails.stock_amount);
|
||||||
@@ -108,39 +108,39 @@ $('#product_id_text_input').on('change', function(e)
|
|||||||
if (input.length > 0 && optionElement.length === 0 && GetUriParam('addbarcodetoselection') === undefined )
|
if (input.length > 0 && optionElement.length === 0 && GetUriParam('addbarcodetoselection') === undefined )
|
||||||
{
|
{
|
||||||
bootbox.dialog({
|
bootbox.dialog({
|
||||||
message: '<strong>' + input + '</strong> could not be resolved to a product, how do you want to proceed?',
|
message: L('#1 could not be resolved to a product, how do you want to proceed?', input),
|
||||||
title: 'Create or assign product',
|
title: L('Create or assign product'),
|
||||||
onEscape: function() { },
|
onEscape: function() { },
|
||||||
size: 'large',
|
size: 'large',
|
||||||
backdrop: true,
|
backdrop: true,
|
||||||
buttons: {
|
buttons: {
|
||||||
cancel: {
|
cancel: {
|
||||||
label: 'Cancel',
|
label: L('Cancel'),
|
||||||
className: 'btn-default',
|
className: 'btn-default',
|
||||||
callback: function() { }
|
callback: function() { }
|
||||||
},
|
},
|
||||||
addnewproduct: {
|
addnewproduct: {
|
||||||
label: 'Add as new <u><strong>p</strong></u>roduct',
|
label: '<strong>P</strong> ' + L('Add as new product'),
|
||||||
className: 'btn-success add-new-product-dialog-button',
|
className: 'btn-success add-new-product-dialog-button',
|
||||||
callback: function()
|
callback: function()
|
||||||
{
|
{
|
||||||
window.location.href = '/product/new?prefillname=' + encodeURIComponent(input) + '&returnto=' + encodeURIComponent(window.location.pathname);
|
window.location.href = U('/product/new?prefillname=' + encodeURIComponent(input) + '&returnto=' + encodeURIComponent(window.location.pathname));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addbarcode: {
|
addbarcode: {
|
||||||
label: 'Add as <u><strong>b</strong></u>arcode to existing product',
|
label: '<strong>B</strong> ' + L('Add as barcode to existing product'),
|
||||||
className: 'btn-info add-new-barcode-dialog-button',
|
className: 'btn-info add-new-barcode-dialog-button',
|
||||||
callback: function()
|
callback: function()
|
||||||
{
|
{
|
||||||
window.location.href = '/inventory?addbarcodetoselection=' + encodeURIComponent(input);
|
window.location.href = U('/inventory?addbarcodetoselection=' + encodeURIComponent(input));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addnewproductwithbarcode: {
|
addnewproductwithbarcode: {
|
||||||
label: '<u><strong>A</strong></u>dd as new product + prefill barcode',
|
label: '<strong>A</strong> ' + L('Add as new product and prefill barcode'),
|
||||||
className: 'btn-warning add-new-product-with-barcode-dialog-button',
|
className: 'btn-warning add-new-product-with-barcode-dialog-button',
|
||||||
callback: function()
|
callback: function()
|
||||||
{
|
{
|
||||||
window.location.href = '/product/new?prefillbarcode=' + encodeURIComponent(input) + '&returnto=' + encodeURIComponent(window.location.pathname);
|
window.location.href = U('/product/new?prefillbarcode=' + encodeURIComponent(input) + '&returnto=' + encodeURIComponent(window.location.pathname));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -283,7 +283,7 @@ $('#new_amount').on('change', function(e)
|
|||||||
|
|
||||||
if (productId)
|
if (productId)
|
||||||
{
|
{
|
||||||
Grocy.FetchJson('/api/stock/get-product-details/' + productId,
|
Grocy.Api.Get('stock/get-product-details/' + productId,
|
||||||
function(productDetails)
|
function(productDetails)
|
||||||
{
|
{
|
||||||
var productStockAmount = productDetails.stock_amount || '0';
|
var productStockAmount = productDetails.stock_amount || '0';
|
||||||
@@ -291,14 +291,14 @@ $('#new_amount').on('change', function(e)
|
|||||||
if (newAmount > productStockAmount)
|
if (newAmount > productStockAmount)
|
||||||
{
|
{
|
||||||
var amountToAdd = newAmount - productDetails.stock_amount;
|
var amountToAdd = newAmount - productDetails.stock_amount;
|
||||||
$('#inventory-change-info').text('This means ' + amountToAdd.toString() + ' ' + productDetails.quantity_unit_stock.name + ' will be added to stock');
|
$('#inventory-change-info').text(L('This means #1 will be added to stock', amountToAdd.toString() + ' ' + productDetails.quantity_unit_stock.name));
|
||||||
$('#inventory-change-info').show();
|
$('#inventory-change-info').show();
|
||||||
$('#best_before_date').attr('required', 'required');
|
$('#best_before_date').attr('required', 'required');
|
||||||
}
|
}
|
||||||
else if (newAmount < productStockAmount)
|
else if (newAmount < productStockAmount)
|
||||||
{
|
{
|
||||||
var amountToRemove = productStockAmount - newAmount;
|
var amountToRemove = productStockAmount - newAmount;
|
||||||
$('#inventory-change-info').text('This means ' + amountToRemove.toString() + ' ' + productDetails.quantity_unit_stock.name + ' will be removed from stock');
|
$('#inventory-change-info').text(L('This means #1 will be removed from stock', amountToRemove.toString() + ' ' + productDetails.quantity_unit_stock.name));
|
||||||
$('#inventory-change-info').show();
|
$('#inventory-change-info').show();
|
||||||
$('#best_before_date').removeAttr('required');
|
$('#best_before_date').removeAttr('required');
|
||||||
}
|
}
|
||||||
|
@@ -4,10 +4,10 @@
|
|||||||
|
|
||||||
if (Grocy.EditMode === 'create')
|
if (Grocy.EditMode === 'create')
|
||||||
{
|
{
|
||||||
Grocy.PostJson('/api/add-object/locations', $('#location-form').serializeJSON(),
|
Grocy.Api.Post('add-object/locations', $('#location-form').serializeJSON(),
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
window.location.href = '/locations';
|
window.location.href = U('/locations');
|
||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
@@ -17,10 +17,10 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Grocy.PostJson('/api/edit-object/locations/' + Grocy.EditObjectId, $('#location-form').serializeJSON(),
|
Grocy.Api.Post('edit-object/locations/' + Grocy.EditObjectId, $('#location-form').serializeJSON(),
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
window.location.href = '/locations';
|
window.location.href = U('/locations');
|
||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
|
@@ -1,14 +1,17 @@
|
|||||||
$(document).on('click', '.location-delete-button', function(e)
|
$(document).on('click', '.location-delete-button', function(e)
|
||||||
{
|
{
|
||||||
|
var objectName = $(e.currentTarget).attr('data-location-name');
|
||||||
|
var objectId = $(e.currentTarget).attr('data-location-id');
|
||||||
|
|
||||||
bootbox.confirm({
|
bootbox.confirm({
|
||||||
message: 'Delete location <strong>' + $(e.target).attr('data-location-name') + '</strong>?',
|
message: L('Are you sure to delete location "#1"?', objectName),
|
||||||
buttons: {
|
buttons: {
|
||||||
confirm: {
|
confirm: {
|
||||||
label: 'Yes',
|
label: L('Yes'),
|
||||||
className: 'btn-success'
|
className: 'btn-success'
|
||||||
},
|
},
|
||||||
cancel: {
|
cancel: {
|
||||||
label: 'No',
|
label: L('No'),
|
||||||
className: 'btn-danger'
|
className: 'btn-danger'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -16,10 +19,10 @@
|
|||||||
{
|
{
|
||||||
if (result === true)
|
if (result === true)
|
||||||
{
|
{
|
||||||
Grocy.FetchJson('/api/delete-object/locations/' + $(e.target).attr('data-location-id'),
|
Grocy.Api.Get('delete-object/locations/' + objectId,
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
window.location.href = '/locations';
|
window.location.href = U('/locations');
|
||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
|
50
public/viewjs/manageapikeys.js
Normal file
50
public/viewjs/manageapikeys.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
$(document).on('click', '.apikey-delete-button', function(e)
|
||||||
|
{
|
||||||
|
var objectName = $(e.currentTarget).attr('data-apikey-apikey');
|
||||||
|
var objectId = $(e.currentTarget).attr('data-apikey-id');
|
||||||
|
|
||||||
|
bootbox.confirm({
|
||||||
|
message: L('Are you sure to delete API key "#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/api_keys/' + objectId,
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
window.location.href = U('/manageapikeys');
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#apikeys-table').DataTable({
|
||||||
|
'pageLength': 50,
|
||||||
|
'order': [[4, 'desc']],
|
||||||
|
'columnDefs': [
|
||||||
|
{ 'orderable': false, 'targets': 0 }
|
||||||
|
],
|
||||||
|
'language': JSON.parse(L('datatables_localization'))
|
||||||
|
});
|
||||||
|
|
||||||
|
var createdApiKeyId = GetUriParam('CreatedApiKeyId');
|
||||||
|
if (createdApiKeyId !== undefined)
|
||||||
|
{
|
||||||
|
$('#apiKeyRow_' + createdApiKeyId).effect('highlight', { }, 3000);
|
||||||
|
}
|
26
public/viewjs/openapiui.js
Normal file
26
public/viewjs/openapiui.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
function HideTopbarPlugin()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
components: {
|
||||||
|
Topbar: function () { return null }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const swaggerUi = SwaggerUIBundle({
|
||||||
|
url: Grocy.OpenApi.SpecUrl,
|
||||||
|
dom_id: '#swagger-ui',
|
||||||
|
deepLinking: true,
|
||||||
|
presets: [
|
||||||
|
SwaggerUIBundle.presets.apis,
|
||||||
|
SwaggerUIStandalonePreset
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
SwaggerUIBundle.plugins.DownloadUrl,
|
||||||
|
HideTopbarPlugin
|
||||||
|
],
|
||||||
|
layout: 'StandaloneLayout',
|
||||||
|
docExpansion: "list"
|
||||||
|
});
|
||||||
|
|
||||||
|
window.ui = swaggerUi;
|
@@ -2,7 +2,7 @@
|
|||||||
{
|
{
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
var redirectDestination = '/products';
|
var redirectDestination = U('/products');
|
||||||
var returnTo = GetUriParam('returnto');
|
var returnTo = GetUriParam('returnto');
|
||||||
if (returnTo !== undefined)
|
if (returnTo !== undefined)
|
||||||
{
|
{
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
if (Grocy.EditMode === 'create')
|
if (Grocy.EditMode === 'create')
|
||||||
{
|
{
|
||||||
Grocy.PostJson('/api/add-object/products', $('#product-form').serializeJSON(),
|
Grocy.Api.Post('add-object/products', $('#product-form').serializeJSON(),
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
window.location.href = redirectDestination;
|
window.location.href = redirectDestination;
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Grocy.PostJson('/api/edit-object/products/' + Grocy.EditObjectId, $('#product-form').serializeJSON(),
|
Grocy.Api.Post('edit-object/products/' + Grocy.EditObjectId, $('#product-form').serializeJSON(),
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
window.location.href = redirectDestination;
|
window.location.href = redirectDestination;
|
||||||
@@ -44,7 +44,7 @@ $('#barcode-taginput').tagsManager({
|
|||||||
|
|
||||||
if (Grocy.EditMode === 'edit')
|
if (Grocy.EditMode === 'edit')
|
||||||
{
|
{
|
||||||
Grocy.FetchJson('/api/get-object/products/' + Grocy.EditObjectId,
|
Grocy.Api.Get('get-object/products/' + Grocy.EditObjectId,
|
||||||
function (product)
|
function (product)
|
||||||
{
|
{
|
||||||
if (product.barcode !== null && product.barcode.length > 0)
|
if (product.barcode !== null && product.barcode.length > 0)
|
||||||
|
@@ -1,14 +1,17 @@
|
|||||||
$(document).on('click', '.product-delete-button', function(e)
|
$(document).on('click', '.product-delete-button', function(e)
|
||||||
{
|
{
|
||||||
|
var objectName = $(e.currentTarget).attr('data-product-name');
|
||||||
|
var objectId = $(e.currentTarget).attr('data-product-id');
|
||||||
|
|
||||||
bootbox.confirm({
|
bootbox.confirm({
|
||||||
message: 'Delete product <strong>' + $(e.target).attr('data-product-name') + '</strong>?',
|
message: L('Are you sure to delete product "#1"?', objectName),
|
||||||
buttons: {
|
buttons: {
|
||||||
confirm: {
|
confirm: {
|
||||||
label: 'Yes',
|
label: L('Yes'),
|
||||||
className: 'btn-success'
|
className: 'btn-success'
|
||||||
},
|
},
|
||||||
cancel: {
|
cancel: {
|
||||||
label: 'No',
|
label: L('No'),
|
||||||
className: 'btn-danger'
|
className: 'btn-danger'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -16,10 +19,10 @@
|
|||||||
{
|
{
|
||||||
if (result === true)
|
if (result === true)
|
||||||
{
|
{
|
||||||
Grocy.FetchJson('/api/delete-object/products/' + $(e.target).attr('data-product-id'),
|
Grocy.Api.Get('delete-object/products/' + objectId,
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
window.location.href = '/products';
|
window.location.href = U('/products');
|
||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
|
@@ -4,12 +4,12 @@
|
|||||||
|
|
||||||
var jsonForm = $('#purchase-form').serializeJSON();
|
var jsonForm = $('#purchase-form').serializeJSON();
|
||||||
|
|
||||||
Grocy.FetchJson('/api/stock/get-product-details/' + jsonForm.product_id,
|
Grocy.Api.Get('stock/get-product-details/' + jsonForm.product_id,
|
||||||
function (productDetails)
|
function (productDetails)
|
||||||
{
|
{
|
||||||
var amount = jsonForm.amount * productDetails.product.qu_factor_purchase_to_stock;
|
var amount = jsonForm.amount * productDetails.product.qu_factor_purchase_to_stock;
|
||||||
|
|
||||||
Grocy.FetchJson('/api/stock/add-product/' + jsonForm.product_id + '/' + amount + '?bestbeforedate=' + $('#best_before_date').val(),
|
Grocy.Api.Get('stock/add-product/' + jsonForm.product_id + '/' + amount + '?bestbeforedate=' + $('#best_before_date').val(),
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
var addBarcode = GetUriParam('addbarcodetoselection');
|
var addBarcode = GetUriParam('addbarcodetoselection');
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
productDetails.product.barcode += ',' + addBarcode;
|
productDetails.product.barcode += ',' + addBarcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
Grocy.PostJson('/api/edit-object/products/' + productDetails.product.id, productDetails.product,
|
Grocy.Api.Post('edit-object/products/' + productDetails.product.id, productDetails.product,
|
||||||
function (result) { },
|
function (result) { },
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
@@ -34,11 +34,11 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
toastr.success('Added ' + amount + ' ' + productDetails.quantity_unit_stock.name + ' of ' + productDetails.product.name + ' to stock');
|
toastr.success(L('Added #1 #2 of #3 to stock', amount, productDetails.quantity_unit_stock.name, productDetails.product.name));
|
||||||
|
|
||||||
if (addBarcode !== undefined)
|
if (addBarcode !== undefined)
|
||||||
{
|
{
|
||||||
window.location.href = '/purchase';
|
window.location.href = U('/purchase');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -72,7 +72,7 @@ $('#product_id').on('change', function(e)
|
|||||||
{
|
{
|
||||||
Grocy.Components.ProductCard.Refresh(productId);
|
Grocy.Components.ProductCard.Refresh(productId);
|
||||||
|
|
||||||
Grocy.FetchJson('/api/stock/get-product-details/' + productId,
|
Grocy.Api.Get('stock/get-product-details/' + productId,
|
||||||
function(productDetails)
|
function(productDetails)
|
||||||
{
|
{
|
||||||
$('#amount_qu_unit').text(productDetails.quantity_unit_purchase.name);
|
$('#amount_qu_unit').text(productDetails.quantity_unit_purchase.name);
|
||||||
@@ -117,8 +117,8 @@ $('#product_id_text_input').on('change', function(e)
|
|||||||
if (input.length > 0 && optionElement.length === 0 && GetUriParam('addbarcodetoselection') === undefined )
|
if (input.length > 0 && optionElement.length === 0 && GetUriParam('addbarcodetoselection') === undefined )
|
||||||
{
|
{
|
||||||
bootbox.dialog({
|
bootbox.dialog({
|
||||||
message: '<strong>' + input + '</strong> could not be resolved to a product, how do you want to proceed?',
|
message: L('"#1" could not be resolved to a product, how do you want to proceed?', input),
|
||||||
title: 'Create or assign product',
|
title: L('Create or assign product'),
|
||||||
onEscape: function() { },
|
onEscape: function() { },
|
||||||
size: 'large',
|
size: 'large',
|
||||||
backdrop: true,
|
backdrop: true,
|
||||||
@@ -129,27 +129,27 @@ $('#product_id_text_input').on('change', function(e)
|
|||||||
callback: function() { }
|
callback: function() { }
|
||||||
},
|
},
|
||||||
addnewproduct: {
|
addnewproduct: {
|
||||||
label: 'Add as new <u><strong>p</strong></u>roduct',
|
label: '<strong>P</strong> ' + L('Add as new product'),
|
||||||
className: 'btn-success add-new-product-dialog-button',
|
className: 'btn-success add-new-product-dialog-button',
|
||||||
callback: function()
|
callback: function()
|
||||||
{
|
{
|
||||||
window.location.href = '/product/new?prefillname=' + encodeURIComponent(input) + '&returnto=' + encodeURIComponent(window.location.pathname);
|
window.location.href = U('/product/new?prefillname=' + encodeURIComponent(input) + '&returnto=' + encodeURIComponent(window.location.pathname));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addbarcode: {
|
addbarcode: {
|
||||||
label: 'Add as <u><strong>b</strong></u>arcode to existing product',
|
label: '<strong>B</strong> ' + L('Add as barcode to existing product'),
|
||||||
className: 'btn-info add-new-barcode-dialog-button',
|
className: 'btn-info add-new-barcode-dialog-button',
|
||||||
callback: function()
|
callback: function()
|
||||||
{
|
{
|
||||||
window.location.href = '/purchase?addbarcodetoselection=' + encodeURIComponent(input);
|
window.location.href = U('/purchase?addbarcodetoselection=' + encodeURIComponent(input));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addnewproductwithbarcode: {
|
addnewproductwithbarcode: {
|
||||||
label: '<u><strong>A</strong></u>dd as new product + prefill barcode',
|
label: '<strong>A</strong> ' + L('Add as new product and prefill barcode'),
|
||||||
className: 'btn-warning add-new-product-with-barcode-dialog-button',
|
className: 'btn-warning add-new-product-with-barcode-dialog-button',
|
||||||
callback: function()
|
callback: function()
|
||||||
{
|
{
|
||||||
window.location.href = '/product/new?prefillbarcode=' + encodeURIComponent(input) + '&returnto=' + encodeURIComponent(window.location.pathname);
|
window.location.href = U('/product/new?prefillbarcode=' + encodeURIComponent(input) + '&returnto=' + encodeURIComponent(window.location.pathname));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,10 +4,10 @@
|
|||||||
|
|
||||||
if (Grocy.EditMode === 'create')
|
if (Grocy.EditMode === 'create')
|
||||||
{
|
{
|
||||||
Grocy.PostJson('/api/add-object/quantity_units', $('#quantityunit-form').serializeJSON(),
|
Grocy.Api.Post('add-object/quantity_units', $('#quantityunit-form').serializeJSON(),
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
window.location.href = '/quantityunits';
|
window.location.href = U('/quantityunits');
|
||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
@@ -17,10 +17,10 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Grocy.PostJson('/api/edit-object/quantity_units/' + Grocy.EditObjectId, $('#quantityunit-form').serializeJSON(),
|
Grocy.Api.Post('edit-object/quantity_units/' + Grocy.EditObjectId, $('#quantityunit-form').serializeJSON(),
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
window.location.href = '/quantityunits';
|
window.location.href = U('/quantityunits');
|
||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
|
@@ -1,7 +1,10 @@
|
|||||||
$(document).on('click', '.quantityunit-delete-button', function(e)
|
$(document).on('click', '.quantityunit-delete-button', function(e)
|
||||||
{
|
{
|
||||||
|
var objectName = $(e.currentTarget).attr('data-quantityunit-name');
|
||||||
|
var objectId = $(e.currentTarget).attr('data-quantityunit-id');
|
||||||
|
|
||||||
bootbox.confirm({
|
bootbox.confirm({
|
||||||
message: 'Delete quantity unit <strong>' + $(e.target).attr('data-quantityunit-name') + '</strong>?',
|
message: L('Are you sure to delete quantity unit "#1"?', objectName),
|
||||||
buttons: {
|
buttons: {
|
||||||
confirm: {
|
confirm: {
|
||||||
label: 'Yes',
|
label: 'Yes',
|
||||||
@@ -16,10 +19,10 @@
|
|||||||
{
|
{
|
||||||
if (result === true)
|
if (result === true)
|
||||||
{
|
{
|
||||||
Grocy.FetchJson('/api/delete-object/quantity_units/' + $(e.target).attr('data-quantityunit-id'),
|
Grocy.Api.Get('delete-object/quantity_units/' + objectId,
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
window.location.href = '/quantityunits';
|
window.location.href = U('/quantityunits');
|
||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
$(document).on('click', '.shoppinglist-delete-button', function(e)
|
$(document).on('click', '.shoppinglist-delete-button', function(e)
|
||||||
{
|
{
|
||||||
Grocy.FetchJson('/api/delete-object/shopping_list/' + $(e.target).attr('data-shoppinglist-id'),
|
Grocy.Api.Get('delete-object/shopping_list/' + $(e.currentTarget).attr('data-shoppinglist-id'),
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
window.location.href = '/shoppinglist';
|
window.location.href = U('/shoppinglist');
|
||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
@@ -14,10 +14,10 @@
|
|||||||
|
|
||||||
$(document).on('click', '#add-products-below-min-stock-amount', function(e)
|
$(document).on('click', '#add-products-below-min-stock-amount', function(e)
|
||||||
{
|
{
|
||||||
Grocy.FetchJson('/api/stock/add-missing-products-to-shoppinglist',
|
Grocy.Api.Get('stock/add-missing-products-to-shoppinglist',
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
window.location.href = '/shoppinglist';
|
window.location.href = U('/shoppinglist');
|
||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
|
@@ -4,10 +4,10 @@
|
|||||||
|
|
||||||
if (Grocy.EditMode === 'create')
|
if (Grocy.EditMode === 'create')
|
||||||
{
|
{
|
||||||
Grocy.PostJson('/api/add-object/shopping_list', $('#shoppinglist-form').serializeJSON(),
|
Grocy.Api.Post('add-object/shopping_list', $('#shoppinglist-form').serializeJSON(),
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
window.location.href = '/shoppinglist';
|
window.location.href = U('/shoppinglist');
|
||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
@@ -17,10 +17,10 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Grocy.PostJson('/api/edit-object/shopping_list/' + Grocy.EditObjectId, $('#shoppinglist-form').serializeJSON(),
|
Grocy.Api.Post('edit-object/shopping_list/' + Grocy.EditObjectId, $('#shoppinglist-form').serializeJSON(),
|
||||||
function(result)
|
function(result)
|
||||||
{
|
{
|
||||||
window.location.href = '/shoppinglist';
|
window.location.href = U('/shoppinglist');
|
||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
@@ -38,7 +38,7 @@ $('#product_id').on('change', function(e)
|
|||||||
{
|
{
|
||||||
Grocy.Components.ProductCard.Refresh(productId);
|
Grocy.Components.ProductCard.Refresh(productId);
|
||||||
|
|
||||||
Grocy.FetchJson('/api/stock/get-product-details/' + productId,
|
Grocy.Api.Get('stock/get-product-details/' + productId,
|
||||||
function (productDetails)
|
function (productDetails)
|
||||||
{
|
{
|
||||||
$('#amount_qu_unit').text(productDetails.quantity_unit_purchase.name);
|
$('#amount_qu_unit').text(productDetails.quantity_unit_purchase.name);
|
||||||
@@ -49,7 +49,7 @@ $('#product_id').on('change', function(e)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Grocy.FetchJson('/api/get-objects/shopping_list',
|
Grocy.Api.Get('get-objects/shopping_list',
|
||||||
function (currentShoppingListItems)
|
function (currentShoppingListItems)
|
||||||
{
|
{
|
||||||
if (currentShoppingListItems.filter(function (e) { return e.product_id === productId; }).length > 0)
|
if (currentShoppingListItems.filter(function (e) { return e.product_id === productId; }).length > 0)
|
||||||
|
@@ -1,5 +1,46 @@
|
|||||||
$('#stock-overview-table').DataTable({
|
$('#stock-overview-table').DataTable({
|
||||||
'pageLength': 50,
|
'pageLength': 50,
|
||||||
'order': [[2, 'asc']],
|
'order': [[3, 'asc']],
|
||||||
|
'columnDefs': [
|
||||||
|
{ 'orderable': false, 'targets': 0 }
|
||||||
|
],
|
||||||
'language': JSON.parse(L('datatables_localization'))
|
'language': JSON.parse(L('datatables_localization'))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.product-consume-button', function(e)
|
||||||
|
{
|
||||||
|
var productId = $(e.currentTarget).attr('data-product-id');
|
||||||
|
var productName = $(e.currentTarget).attr('data-product-name');
|
||||||
|
var productQuName = $(e.currentTarget).attr('data-product-qu-name');
|
||||||
|
var consumeAmount = $(e.currentTarget).attr('data-consume-amount');
|
||||||
|
|
||||||
|
Grocy.Api.Get('stock/consume-product/' + productId + '/' + consumeAmount,
|
||||||
|
function(result)
|
||||||
|
{
|
||||||
|
var oldAmount = parseInt($('#product-' + productId + '-amount').text());
|
||||||
|
var newAmount = oldAmount - consumeAmount;
|
||||||
|
if (newAmount === 0)
|
||||||
|
{
|
||||||
|
$('#product-' + productId + '-row').fadeOut(500, function()
|
||||||
|
{
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$('#product-' + productId + '-amount').parent().effect('highlight', { }, 500);
|
||||||
|
$('#product-' + productId + '-amount').fadeOut(500, function()
|
||||||
|
{
|
||||||
|
$(this).text(newAmount).fadeIn(500);
|
||||||
|
});
|
||||||
|
$('#product-' + productId + '-consume-all-button').attr('data-consume-amount', newAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
toastr.success(L('Removed #1 #2 of #3 from stock', consumeAmount, productQuName, productName));
|
||||||
|
},
|
||||||
|
function(xhr)
|
||||||
|
{
|
||||||
|
console.error(xhr);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 79 KiB |
Binary file not shown.
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 80 KiB |
Binary file not shown.
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 84 KiB |
83
routes.php
83
routes.php
@@ -2,51 +2,62 @@
|
|||||||
|
|
||||||
use \Grocy\Middleware\JsonMiddleware;
|
use \Grocy\Middleware\JsonMiddleware;
|
||||||
use \Grocy\Middleware\CliMiddleware;
|
use \Grocy\Middleware\CliMiddleware;
|
||||||
|
use \Grocy\Middleware\SessionAuthMiddleware;
|
||||||
|
use \Grocy\Middleware\ApiKeyAuthMiddleware;
|
||||||
|
use \Tuupola\Middleware\CorsMiddleware;
|
||||||
|
|
||||||
// Base route
|
$app->group('', function()
|
||||||
$app->get('/', 'Grocy\Controllers\LoginController:Root')->setName('root');
|
{
|
||||||
|
// Base route
|
||||||
|
$this->get('/', 'LoginControllerInstance:Root')->setName('root');
|
||||||
|
|
||||||
// Login routes
|
// Login routes
|
||||||
$app->get('/login', 'Grocy\Controllers\LoginController:LoginPage')->setName('login');
|
$this->get('/login', 'LoginControllerInstance:LoginPage')->setName('login');
|
||||||
$app->post('/login', 'Grocy\Controllers\LoginController:ProcessLogin')->setName('login');
|
$this->post('/login', 'LoginControllerInstance:ProcessLogin')->setName('login');
|
||||||
$app->get('/logout', 'Grocy\Controllers\LoginController:Logout');
|
$this->get('/logout', 'LoginControllerInstance:Logout');
|
||||||
|
|
||||||
// Stock routes
|
// Stock routes
|
||||||
$app->get('/stockoverview', 'Grocy\Controllers\StockController:Overview');
|
$this->get('/stockoverview', 'Grocy\Controllers\StockController:Overview');
|
||||||
$app->get('/purchase', 'Grocy\Controllers\StockController:Purchase');
|
$this->get('/purchase', 'Grocy\Controllers\StockController:Purchase');
|
||||||
$app->get('/consume', 'Grocy\Controllers\StockController:Consume');
|
$this->get('/consume', 'Grocy\Controllers\StockController:Consume');
|
||||||
$app->get('/inventory', 'Grocy\Controllers\StockController:Inventory');
|
$this->get('/inventory', 'Grocy\Controllers\StockController:Inventory');
|
||||||
|
|
||||||
$app->get('/products', 'Grocy\Controllers\StockController:ProductsList');
|
$this->get('/products', 'Grocy\Controllers\StockController:ProductsList');
|
||||||
$app->get('/product/{productId}', 'Grocy\Controllers\StockController:ProductEditForm');
|
$this->get('/product/{productId}', 'Grocy\Controllers\StockController:ProductEditForm');
|
||||||
|
|
||||||
$app->get('/locations', 'Grocy\Controllers\StockController:LocationsList');
|
$this->get('/locations', 'Grocy\Controllers\StockController:LocationsList');
|
||||||
$app->get('/location/{locationId}', 'Grocy\Controllers\StockController:LocationEditForm');
|
$this->get('/location/{locationId}', 'Grocy\Controllers\StockController:LocationEditForm');
|
||||||
|
|
||||||
$app->get('/quantityunits', 'Grocy\Controllers\StockController:QuantityUnitsList');
|
$this->get('/quantityunits', 'Grocy\Controllers\StockController:QuantityUnitsList');
|
||||||
$app->get('/quantityunit/{quantityunitId}', 'Grocy\Controllers\StockController:QuantityUnitEditForm');
|
$this->get('/quantityunit/{quantityunitId}', 'Grocy\Controllers\StockController:QuantityUnitEditForm');
|
||||||
|
|
||||||
$app->get('/shoppinglist', 'Grocy\Controllers\StockController:ShoppingList');
|
$this->get('/shoppinglist', 'Grocy\Controllers\StockController:ShoppingList');
|
||||||
$app->get('/shoppinglistitem/{itemId}', 'Grocy\Controllers\StockController:ShoppingListItemEditForm');
|
$this->get('/shoppinglistitem/{itemId}', 'Grocy\Controllers\StockController:ShoppingListItemEditForm');
|
||||||
|
|
||||||
|
// Habit routes
|
||||||
|
$this->get('/habitsoverview', 'Grocy\Controllers\HabitsController:Overview');
|
||||||
|
$this->get('/habittracking', 'Grocy\Controllers\HabitsController:TrackHabitExecution');
|
||||||
|
|
||||||
// Habit routes
|
$this->get('/habits', 'Grocy\Controllers\HabitsController:HabitsList');
|
||||||
$app->get('/habitsoverview', 'Grocy\Controllers\HabitsController:Overview');
|
$this->get('/habit/{habitId}', 'Grocy\Controllers\HabitsController:HabitEditForm');
|
||||||
$app->get('/habittracking', 'Grocy\Controllers\HabitsController:TrackHabitExecution');
|
|
||||||
|
|
||||||
$app->get('/habits', 'Grocy\Controllers\HabitsController:HabitsList');
|
// Batterry routes
|
||||||
$app->get('/habit/{habitId}', 'Grocy\Controllers\HabitsController:HabitEditForm');
|
$this->get('/batteriesoverview', 'Grocy\Controllers\BatteriesController:Overview');
|
||||||
|
$this->get('/batterytracking', 'Grocy\Controllers\BatteriesController:TrackChargeCycle');
|
||||||
|
|
||||||
// Batterry routes
|
$this->get('/batteries', 'Grocy\Controllers\BatteriesController:BatteriesList');
|
||||||
$app->get('/batteriesoverview', 'Grocy\Controllers\BatteriesController:Overview');
|
$this->get('/battery/{batteryId}', 'Grocy\Controllers\BatteriesController:BatteryEditForm');
|
||||||
$app->get('/batterytracking', 'Grocy\Controllers\BatteriesController:TrackChargeCycle');
|
|
||||||
|
|
||||||
$app->get('/batteries', 'Grocy\Controllers\BatteriesController:BatteriesList');
|
|
||||||
$app->get('/battery/{batteryId}', 'Grocy\Controllers\BatteriesController:BatteryEditForm');
|
|
||||||
|
|
||||||
|
// Other routes
|
||||||
|
$this->get('/api', 'Grocy\Controllers\OpenApiController:DocumentationUi');
|
||||||
|
$this->get('/manageapikeys', 'Grocy\Controllers\OpenApiController:ApiKeysList');
|
||||||
|
$this->get('/manageapikeys/new', 'Grocy\Controllers\OpenApiController:CreateNewApiKey');
|
||||||
|
})->add(new SessionAuthMiddleware($appContainer, $appContainer->LoginControllerInstance->GetSessionCookieName()));
|
||||||
|
|
||||||
$app->group('/api', function()
|
$app->group('/api', function()
|
||||||
{
|
{
|
||||||
|
$this->get('/get-openapi-specification', 'Grocy\Controllers\OpenApiController:DocumentationSpec');
|
||||||
|
|
||||||
$this->get('/get-objects/{entity}', 'Grocy\Controllers\GenericEntityApiController:GetObjects');
|
$this->get('/get-objects/{entity}', 'Grocy\Controllers\GenericEntityApiController:GetObjects');
|
||||||
$this->get('/get-object/{entity}/{objectId}', 'Grocy\Controllers\GenericEntityApiController:GetObject');
|
$this->get('/get-object/{entity}/{objectId}', 'Grocy\Controllers\GenericEntityApiController:GetObject');
|
||||||
$this->post('/add-object/{entity}', 'Grocy\Controllers\GenericEntityApiController:AddObject');
|
$this->post('/add-object/{entity}', 'Grocy\Controllers\GenericEntityApiController:AddObject');
|
||||||
@@ -59,13 +70,23 @@ $app->group('/api', function()
|
|||||||
$this->get('/stock/get-product-details/{productId}', 'Grocy\Controllers\StockApiController:ProductDetails');
|
$this->get('/stock/get-product-details/{productId}', 'Grocy\Controllers\StockApiController:ProductDetails');
|
||||||
$this->get('/stock/get-current-stock', 'Grocy\Controllers\StockApiController:CurrentStock');
|
$this->get('/stock/get-current-stock', 'Grocy\Controllers\StockApiController:CurrentStock');
|
||||||
$this->get('/stock/add-missing-products-to-shoppinglist', 'Grocy\Controllers\StockApiController:AddMissingProductsToShoppingList');
|
$this->get('/stock/add-missing-products-to-shoppinglist', 'Grocy\Controllers\StockApiController:AddMissingProductsToShoppingList');
|
||||||
|
$this->get('/stock/external-barcode-lookup/{barcode}', 'Grocy\Controllers\StockApiController:ExternalBarcodeLookup');
|
||||||
|
|
||||||
$this->get('/habits/track-habit-execution/{habitId}', 'Grocy\Controllers\HabitsApiController:TrackHabitExecution');
|
$this->get('/habits/track-habit-execution/{habitId}', 'Grocy\Controllers\HabitsApiController:TrackHabitExecution');
|
||||||
$this->get('/habits/get-habit-details/{habitId}', 'Grocy\Controllers\HabitsApiController:HabitDetails');
|
$this->get('/habits/get-habit-details/{habitId}', 'Grocy\Controllers\HabitsApiController:HabitDetails');
|
||||||
|
|
||||||
$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');
|
||||||
})->add(JsonMiddleware::class);
|
})->add(new ApiKeyAuthMiddleware($appContainer, $appContainer->LoginControllerInstance->GetSessionCookieName(), $appContainer->ApiKeyHeaderName))
|
||||||
|
->add(JsonMiddleware::class)
|
||||||
|
->add(new CorsMiddleware([
|
||||||
|
'origin' => ["*"],
|
||||||
|
'methods' => ["GET", "POST"],
|
||||||
|
'headers.allow' => [ $appContainer->ApiKeyHeaderName ],
|
||||||
|
'headers.expose' => [ ],
|
||||||
|
'credentials' => false,
|
||||||
|
'cache' => 0,
|
||||||
|
]));
|
||||||
|
|
||||||
$app->group('/cli', function()
|
$app->group('/cli', function()
|
||||||
{
|
{
|
||||||
|
64
services/ApiKeyService.php
Normal file
64
services/ApiKeyService.php
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Services;
|
||||||
|
|
||||||
|
class ApiKeyService extends BaseService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function IsValidApiKey($apiKey)
|
||||||
|
{
|
||||||
|
if ($apiKey === null || empty($apiKey))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$apiKeyRow = $this->Database->api_keys()->where('api_key = :1 AND expires > :2', $apiKey, date('Y-m-d H:i:s', time()))->fetch();
|
||||||
|
if ($apiKeyRow !== null)
|
||||||
|
{
|
||||||
|
$apiKeyRow->update(array(
|
||||||
|
'last_used' => date('Y-m-d H:i:s', time())
|
||||||
|
));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function CreateApiKey()
|
||||||
|
{
|
||||||
|
$newApiKey = $this->GenerateApiKey();
|
||||||
|
|
||||||
|
$apiKeyRow = $this->Database->api_keys()->createRow(array(
|
||||||
|
'api_key' => $newApiKey,
|
||||||
|
'expires' => '2999-12-31 23:59:59' // Default is that API keys expire never
|
||||||
|
));
|
||||||
|
$apiKeyRow->save();
|
||||||
|
|
||||||
|
return $newApiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function RemoveApiKey($apiKey)
|
||||||
|
{
|
||||||
|
$this->Database->api_keys()->where('api_key', $apiKey)->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetApiKeyId($apiKey)
|
||||||
|
{
|
||||||
|
$apiKey = $this->Database->api_keys()->where('api_key', $apiKey)->fetch();
|
||||||
|
return $apiKey->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function GenerateApiKey()
|
||||||
|
{
|
||||||
|
return RandomString(50);
|
||||||
|
}
|
||||||
|
}
|
@@ -13,14 +13,11 @@ class ApplicationService extends BaseService
|
|||||||
}
|
}
|
||||||
|
|
||||||
private $InstalledVersion;
|
private $InstalledVersion;
|
||||||
/**
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
public function GetInstalledVersion()
|
public function GetInstalledVersion()
|
||||||
{
|
{
|
||||||
if ($this->InstalledVersion == null)
|
if ($this->InstalledVersion == null)
|
||||||
{
|
{
|
||||||
$this->InstalledVersion = preg_replace("/\r|\n/", '', file_get_contents(__DIR__ . '/../version.txt'));
|
$this->InstalledVersion = json_decode(file_get_contents(__DIR__ . '/../version.json'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->InstalledVersion;
|
return $this->InstalledVersion;
|
||||||
|
@@ -12,6 +12,11 @@ class BatteriesService extends BaseService
|
|||||||
|
|
||||||
public function GetNextChargeTime(int $batteryId)
|
public function GetNextChargeTime(int $batteryId)
|
||||||
{
|
{
|
||||||
|
if (!$this->BatteryExists($batteryId))
|
||||||
|
{
|
||||||
|
throw new \Exception('Battery does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
$battery = $this->Database->batteries($batteryId);
|
$battery = $this->Database->batteries($batteryId);
|
||||||
$batteryLastLogRow = $this->DatabaseService->ExecuteDbQuery("SELECT * from batteries_current WHERE battery_id = $batteryId LIMIT 1")->fetch(\PDO::FETCH_OBJ);
|
$batteryLastLogRow = $this->DatabaseService->ExecuteDbQuery("SELECT * from batteries_current WHERE battery_id = $batteryId LIMIT 1")->fetch(\PDO::FETCH_OBJ);
|
||||||
|
|
||||||
@@ -21,7 +26,7 @@ class BatteriesService extends BaseService
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return date('Y-m-d H:i:s');
|
return date('2999-12-31 23:59:59');
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -29,6 +34,11 @@ class BatteriesService extends BaseService
|
|||||||
|
|
||||||
public function GetBatteryDetails(int $batteryId)
|
public function GetBatteryDetails(int $batteryId)
|
||||||
{
|
{
|
||||||
|
if (!$this->BatteryExists($batteryId))
|
||||||
|
{
|
||||||
|
throw new \Exception('Battery does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
$battery = $this->Database->batteries($batteryId);
|
$battery = $this->Database->batteries($batteryId);
|
||||||
$batteryChargeCylcesCount = $this->Database->battery_charge_cycles()->where('battery_id', $batteryId)->count();
|
$batteryChargeCylcesCount = $this->Database->battery_charge_cycles()->where('battery_id', $batteryId)->count();
|
||||||
$batteryLastChargedTime = $this->Database->battery_charge_cycles()->where('battery_id', $batteryId)->max('tracked_time');
|
$batteryLastChargedTime = $this->Database->battery_charge_cycles()->where('battery_id', $batteryId)->max('tracked_time');
|
||||||
@@ -42,6 +52,11 @@ class BatteriesService extends BaseService
|
|||||||
|
|
||||||
public function TrackChargeCycle(int $batteryId, string $trackedTime)
|
public function TrackChargeCycle(int $batteryId, string $trackedTime)
|
||||||
{
|
{
|
||||||
|
if (!$this->BatteryExists($batteryId))
|
||||||
|
{
|
||||||
|
throw new \Exception('Battery does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
$logRow = $this->Database->battery_charge_cycles()->createRow(array(
|
$logRow = $this->Database->battery_charge_cycles()->createRow(array(
|
||||||
'battery_id' => $batteryId,
|
'battery_id' => $batteryId,
|
||||||
'tracked_time' => $trackedTime
|
'tracked_time' => $trackedTime
|
||||||
@@ -50,4 +65,10 @@ class BatteriesService extends BaseService
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function BatteryExists($batteryId)
|
||||||
|
{
|
||||||
|
$batteryRow = $this->Database->batteries()->where('id = :1', $batteryId)->fetch();
|
||||||
|
return $batteryRow !== null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,13 +15,18 @@ class HabitsService extends BaseService
|
|||||||
|
|
||||||
public function GetNextHabitTime(int $habitId)
|
public function GetNextHabitTime(int $habitId)
|
||||||
{
|
{
|
||||||
|
if (!$this->HabitExists($habitId))
|
||||||
|
{
|
||||||
|
throw new \Exception('Habit does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
$habit = $this->Database->habits($habitId);
|
$habit = $this->Database->habits($habitId);
|
||||||
$habitLastLogRow = $this->DatabaseService->ExecuteDbQuery("SELECT * from habits_current WHERE habit_id = $habitId LIMIT 1")->fetch(\PDO::FETCH_OBJ);
|
$habitLastLogRow = $this->DatabaseService->ExecuteDbQuery("SELECT * from habits_current WHERE habit_id = $habitId LIMIT 1")->fetch(\PDO::FETCH_OBJ);
|
||||||
|
|
||||||
switch($habit->period_type)
|
switch($habit->period_type)
|
||||||
{
|
{
|
||||||
case self::HABIT_TYPE_MANUALLY:
|
case self::HABIT_TYPE_MANUALLY:
|
||||||
return date('Y-m-d H:i:s');
|
return date('2999-12-31 23:59:59');
|
||||||
case self::HABIT_TYPE_DYNAMIC_REGULAR:
|
case self::HABIT_TYPE_DYNAMIC_REGULAR:
|
||||||
return date('Y-m-d H:i:s', strtotime('+' . $habit->period_days . ' day', strtotime($habitLastLogRow->last_tracked_time)));
|
return date('Y-m-d H:i:s', strtotime('+' . $habit->period_days . ' day', strtotime($habitLastLogRow->last_tracked_time)));
|
||||||
}
|
}
|
||||||
@@ -31,6 +36,11 @@ class HabitsService extends BaseService
|
|||||||
|
|
||||||
public function GetHabitDetails(int $habitId)
|
public function GetHabitDetails(int $habitId)
|
||||||
{
|
{
|
||||||
|
if (!$this->HabitExists($habitId))
|
||||||
|
{
|
||||||
|
throw new \Exception('Habit does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
$habit = $this->Database->habits($habitId);
|
$habit = $this->Database->habits($habitId);
|
||||||
$habitTrackedCount = $this->Database->habits_log()->where('habit_id', $habitId)->count();
|
$habitTrackedCount = $this->Database->habits_log()->where('habit_id', $habitId)->count();
|
||||||
$habitLastTrackedTime = $this->Database->habits_log()->where('habit_id', $habitId)->max('tracked_time');
|
$habitLastTrackedTime = $this->Database->habits_log()->where('habit_id', $habitId)->max('tracked_time');
|
||||||
@@ -44,6 +54,11 @@ class HabitsService extends BaseService
|
|||||||
|
|
||||||
public function TrackHabit(int $habitId, string $trackedTime)
|
public function TrackHabit(int $habitId, string $trackedTime)
|
||||||
{
|
{
|
||||||
|
if (!$this->HabitExists($habitId))
|
||||||
|
{
|
||||||
|
throw new \Exception('Habit does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
$logRow = $this->Database->habits_log()->createRow(array(
|
$logRow = $this->Database->habits_log()->createRow(array(
|
||||||
'habit_id' => $habitId,
|
'habit_id' => $habitId,
|
||||||
'tracked_time' => $trackedTime
|
'tracked_time' => $trackedTime
|
||||||
@@ -52,4 +67,10 @@ class HabitsService extends BaseService
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function HabitExists($habitId)
|
||||||
|
{
|
||||||
|
$habitRow = $this->Database->habits()->where('id = :1', $habitId)->fetch();
|
||||||
|
return $habitRow !== null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,18 @@ class SessionService extends BaseService
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return $this->Database->sessions()->where('session_key = :1 AND expires > :2', $sessionKey, time())->count() === 1;
|
$sessionRow = $this->Database->sessions()->where('session_key = :1 AND expires > :2', $sessionKey, date('Y-m-d H:i:s', time()))->fetch();
|
||||||
|
if ($sessionRow !== null)
|
||||||
|
{
|
||||||
|
$sessionRow->update(array(
|
||||||
|
'last_used' => date('Y-m-d H:i:s', time())
|
||||||
|
));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,11 +35,11 @@ class SessionService extends BaseService
|
|||||||
*/
|
*/
|
||||||
public function CreateSession()
|
public function CreateSession()
|
||||||
{
|
{
|
||||||
$newSessionKey = uniqid() . uniqid() . uniqid();
|
$newSessionKey = $this->GenerateSessionKey();
|
||||||
|
|
||||||
$sessionRow = $this->Database->sessions()->createRow(array(
|
$sessionRow = $this->Database->sessions()->createRow(array(
|
||||||
'session_key' => $newSessionKey,
|
'session_key' => $newSessionKey,
|
||||||
'expires' => time() + 2592000 //30 days
|
'expires' => date('Y-m-d H:i:s', time() + 2592000) // Default is that sessions expire in 30 days
|
||||||
));
|
));
|
||||||
$sessionRow->save();
|
$sessionRow->save();
|
||||||
|
|
||||||
@@ -39,4 +50,9 @@ class SessionService extends BaseService
|
|||||||
{
|
{
|
||||||
$this->Database->sessions()->where('session_key', $sessionKey)->delete();
|
$this->Database->sessions()->where('session_key', $sessionKey)->delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function GenerateSessionKey()
|
||||||
|
{
|
||||||
|
return RandomString(50);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,6 +22,11 @@ class StockService extends BaseService
|
|||||||
|
|
||||||
public function GetProductDetails(int $productId)
|
public function GetProductDetails(int $productId)
|
||||||
{
|
{
|
||||||
|
if (!$this->ProductExists($productId))
|
||||||
|
{
|
||||||
|
throw new \Exception('Product does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
$product = $this->Database->products($productId);
|
$product = $this->Database->products($productId);
|
||||||
$productStockAmount = $this->Database->stock()->where('product_id', $productId)->sum('amount');
|
$productStockAmount = $this->Database->stock()->where('product_id', $productId)->sum('amount');
|
||||||
$productLastPurchased = $this->Database->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_PURCHASE)->max('purchased_date');
|
$productLastPurchased = $this->Database->stock_log()->where('product_id', $productId)->where('transaction_type', self::TRANSACTION_TYPE_PURCHASE)->max('purchased_date');
|
||||||
@@ -41,6 +46,11 @@ class StockService extends BaseService
|
|||||||
|
|
||||||
public function AddProduct(int $productId, int $amount, string $bestBeforeDate, $transactionType)
|
public function AddProduct(int $productId, int $amount, string $bestBeforeDate, $transactionType)
|
||||||
{
|
{
|
||||||
|
if (!$this->ProductExists($productId))
|
||||||
|
{
|
||||||
|
throw new \Exception('Product does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
if ($transactionType === self::TRANSACTION_TYPE_CONSUME || $transactionType === self::TRANSACTION_TYPE_PURCHASE || $transactionType === self::TRANSACTION_TYPE_INVENTORY_CORRECTION)
|
if ($transactionType === self::TRANSACTION_TYPE_CONSUME || $transactionType === self::TRANSACTION_TYPE_PURCHASE || $transactionType === self::TRANSACTION_TYPE_INVENTORY_CORRECTION)
|
||||||
{
|
{
|
||||||
$stockId = uniqid();
|
$stockId = uniqid();
|
||||||
@@ -68,12 +78,17 @@ class StockService extends BaseService
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new Exception("Transaction type $transactionType is not valid (StockService.AddProduct)");
|
throw new \Exception("Transaction type $transactionType is not valid (StockService.AddProduct)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function ConsumeProduct(int $productId, int $amount, bool $spoiled, $transactionType)
|
public function ConsumeProduct(int $productId, int $amount, bool $spoiled, $transactionType)
|
||||||
{
|
{
|
||||||
|
if (!$this->ProductExists($productId))
|
||||||
|
{
|
||||||
|
throw new \Exception('Product does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
if ($transactionType === self::TRANSACTION_TYPE_CONSUME || $transactionType === self::TRANSACTION_TYPE_PURCHASE || $transactionType === self::TRANSACTION_TYPE_INVENTORY_CORRECTION)
|
if ($transactionType === self::TRANSACTION_TYPE_CONSUME || $transactionType === self::TRANSACTION_TYPE_PURCHASE || $transactionType === self::TRANSACTION_TYPE_INVENTORY_CORRECTION)
|
||||||
{
|
{
|
||||||
$productStockAmount = $this->Database->stock()->where('product_id', $productId)->sum('amount');
|
$productStockAmount = $this->Database->stock()->where('product_id', $productId)->sum('amount');
|
||||||
@@ -141,6 +156,11 @@ class StockService extends BaseService
|
|||||||
|
|
||||||
public function InventoryProduct(int $productId, int $newAmount, string $bestBeforeDate)
|
public function InventoryProduct(int $productId, int $newAmount, string $bestBeforeDate)
|
||||||
{
|
{
|
||||||
|
if (!$this->ProductExists($productId))
|
||||||
|
{
|
||||||
|
throw new \Exception('Product does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
$productStockAmount = $this->Database->stock()->where('product_id', $productId)->sum('amount');
|
$productStockAmount = $this->Database->stock()->where('product_id', $productId)->sum('amount');
|
||||||
|
|
||||||
if ($newAmount > $productStockAmount)
|
if ($newAmount > $productStockAmount)
|
||||||
@@ -182,4 +202,50 @@ class StockService extends BaseService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function ProductExists($productId)
|
||||||
|
{
|
||||||
|
$productRow = $this->Database->products()->where('id = :1', $productId)->fetch();
|
||||||
|
return $productRow !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function LoadBarcodeLookupPlugin()
|
||||||
|
{
|
||||||
|
$pluginName = defined('STOCK_BARCODE_LOOKUP_PLUGIN') ? STOCK_BARCODE_LOOKUP_PLUGIN : '';
|
||||||
|
if (empty($pluginName))
|
||||||
|
{
|
||||||
|
throw new \Exception('No barcode lookup plugin defined');
|
||||||
|
}
|
||||||
|
|
||||||
|
$path = __DIR__ . "/../data/plugins/$pluginName.php";
|
||||||
|
if (file_exists($path))
|
||||||
|
{
|
||||||
|
require_once $path;
|
||||||
|
return new $pluginName($this->Database->locations()->fetchAll(), $this->Database->quantity_units()->fetchAll());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new \Exception("Plugin $pluginName was not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ExternalBarcodeLookup($barcode, $addFoundProduct)
|
||||||
|
{
|
||||||
|
$plugin = $this->LoadBarcodeLookupPlugin();
|
||||||
|
$pluginOutput = $plugin->Lookup($barcode);
|
||||||
|
|
||||||
|
if ($pluginOutput !== null) // Lookup was successful
|
||||||
|
{
|
||||||
|
if ($addFoundProduct === true)
|
||||||
|
{
|
||||||
|
// Add product to database and include new product id in output
|
||||||
|
$newRow = $this->Database->products()->createRow($pluginOutput);
|
||||||
|
$newRow->save();
|
||||||
|
|
||||||
|
$pluginOutput['id'] = $newRow->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $pluginOutput;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
4
version.json
Normal file
4
version.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"Version": "1.11.0",
|
||||||
|
"ReleaseDate": "2018-06-15"
|
||||||
|
}
|
@@ -1 +0,0 @@
|
|||||||
1.8.0
|
|
@@ -5,50 +5,46 @@
|
|||||||
@section('viewJsName', 'batteries')
|
@section('viewJsName', 'batteries')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2">
|
<h1 class="page-header">
|
||||||
|
@yield('title')
|
||||||
<h1 class="page-header">
|
<a class="btn btn-default" href="{{ $U('/battery/new') }}" role="button">
|
||||||
@yield('title')
|
<i class="fa fa-plus"></i> {{ $L('Add') }}
|
||||||
<a class="btn btn-default" href="/battery/new" role="button">
|
</a>
|
||||||
<i class="fa fa-plus"></i> {{ $L('Add') }}
|
</h1>
|
||||||
</a>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table id="batteries-table" class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>#</th>
|
|
||||||
<th>{{ $L('Name') }}</th>
|
|
||||||
<th>{{ $L('Description') }}</th>
|
|
||||||
<th>{{ $L('Used in') }}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@foreach($batteries as $battery)
|
|
||||||
<tr>
|
|
||||||
<td class="fit-content">
|
|
||||||
<a class="btn btn-info" href="/battery/{{ $battery->id }}" role="button">
|
|
||||||
<i class="fa fa-pencil"></i>
|
|
||||||
</a>
|
|
||||||
<a class="btn btn-danger battery-delete-button" href="#" role="button" data-battery-id="{{ $battery->id }}" data-battery-name="{{ $battery->name }}">
|
|
||||||
<i class="fa fa-trash"></i>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ $battery->name }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ $battery->description }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ $battery->used_in }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@endforeach
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="batteries-table" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>{{ $L('Name') }}</th>
|
||||||
|
<th>{{ $L('Description') }}</th>
|
||||||
|
<th>{{ $L('Used in') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($batteries as $battery)
|
||||||
|
<tr>
|
||||||
|
<td class="fit-content">
|
||||||
|
<a class="btn btn-info" href="{{ $U('/battery/') }}{{ $battery->id }}" role="button">
|
||||||
|
<i class="fa fa-pencil"></i>
|
||||||
|
</a>
|
||||||
|
<a class="btn btn-danger battery-delete-button" href="#" role="button" data-battery-id="{{ $battery->id }}" data-battery-name="{{ $battery->name }}">
|
||||||
|
<i class="fa fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $battery->name }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $battery->description }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $battery->used_in }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@stop
|
@stop
|
||||||
|
@@ -4,43 +4,60 @@
|
|||||||
@section('activeNav', 'batteriesoverview')
|
@section('activeNav', 'batteriesoverview')
|
||||||
@section('viewJsName', 'batteriesoverview')
|
@section('viewJsName', 'batteriesoverview')
|
||||||
|
|
||||||
|
@push('pageScripts')
|
||||||
|
<script src="{{ $U('/bower_components/jquery-ui/jquery-ui.min.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
@endpush
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2">
|
<h1 class="page-header">@yield('title')</h1>
|
||||||
|
|
||||||
<h1 class="page-header">@yield('title')</h1>
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
<div class="table-responsive">
|
<p class="btn btn-lg btn-warning no-real-button responsive-button">{{ $L('#1 batteries are due to be charged within the next #2 days', $countDueNextXDays, $nextXDays) }}</p>
|
||||||
<table id="batteries-overview-table" class="table table-striped">
|
<p class="btn btn-lg btn-danger no-real-button responsive-button">{{ $L('#1 batteries are overdue to be charged', $countOverdue) }}</p>
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{{ $L('Battery') }}</th>
|
|
||||||
<th>{{ $L('Last charged') }}</th>
|
|
||||||
<th>{{ $L('Next planned charge cycle') }}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@foreach($current as $curentBatteryEntry)
|
|
||||||
<tr class="@if(FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->charge_interval_days > 0 && $nextChargeTimes[$curentBatteryEntry->battery_id] < date('Y-m-d H:i:s')) error-bg @endif">
|
|
||||||
<td>
|
|
||||||
{{ FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->name }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ $curentBatteryEntry->last_tracked_time }}
|
|
||||||
<time class="timeago timeago-contextual" datetime="{{ $curentBatteryEntry->last_tracked_time }}"></time>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@if(FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->charge_interval_days > 0)
|
|
||||||
{{ $nextChargeTimes[$curentBatteryEntry->battery_id] }}
|
|
||||||
<time class="timeago timeago-contextual" datetime="{{ $nextChargeTimes[$curentBatteryEntry->battery_id] }}"></time>
|
|
||||||
@else
|
|
||||||
...
|
|
||||||
@endif
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@endforeach
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="discrete-content-separator-2x"></div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="batteries-overview-table" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>{{ $L('Battery') }}</th>
|
||||||
|
<th>{{ $L('Last charged') }}</th>
|
||||||
|
<th>{{ $L('Next planned charge cycle') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($current as $curentBatteryEntry)
|
||||||
|
<tr class="@if(FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->charge_interval_days > 0 && $nextChargeTimes[$curentBatteryEntry->battery_id] < date('Y-m-d H:i:s')) error-bg @endif">
|
||||||
|
<td class="fit-content">
|
||||||
|
<a class="btn btn-success btn-xs track-charge-cycle-button" href="#" title="{{ $L('Track charge cycle of battery #1', FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->name) }}"
|
||||||
|
data-battery-id="{{ $curentBatteryEntry->battery_id }}"
|
||||||
|
data-battery-name="{{ FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->name }}">
|
||||||
|
<i class="fa fa-fire"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->name }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span id="battery-{{ $curentBatteryEntry->battery_id }}-last-tracked-time">{{ $curentBatteryEntry->last_tracked_time }}</span>
|
||||||
|
<time id="battery-{{ $curentBatteryEntry->battery_id }}-last-tracked-time-timeago" class="timeago timeago-contextual" datetime="{{ $curentBatteryEntry->last_tracked_time }}"></time>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@if(FindObjectInArrayByPropertyValue($batteries, 'id', $curentBatteryEntry->battery_id)->charge_interval_days > 0)
|
||||||
|
{{ $nextChargeTimes[$curentBatteryEntry->battery_id] }}
|
||||||
|
<time class="timeago timeago-contextual" datetime="{{ $nextChargeTimes[$curentBatteryEntry->battery_id] }}"></time>
|
||||||
|
@else
|
||||||
|
...
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@stop
|
@stop
|
||||||
|
@@ -9,8 +9,7 @@
|
|||||||
@section('viewJsName', 'batteryform')
|
@section('viewJsName', 'batteryform')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="col-sm-3 col-sm-offset-3 col-md-4 col-md-offset-2">
|
<div class="col-lg-4 col-xs-12">
|
||||||
|
|
||||||
<h1 class="page-header">@yield('title')</h1>
|
<h1 class="page-header">@yield('title')</h1>
|
||||||
|
|
||||||
<script>Grocy.EditMode = '{{ $mode }}';</script>
|
<script>Grocy.EditMode = '{{ $mode }}';</script>
|
||||||
@@ -40,6 +39,5 @@
|
|||||||
<button id="save-battery-button" type="submit" class="btn btn-default">{{ $L('Save') }}</button>
|
<button id="save-battery-button" type="submit" class="btn btn-default">{{ $L('Save') }}</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@stop
|
@stop
|
||||||
|
@@ -5,8 +5,7 @@
|
|||||||
@section('viewJsName', 'batterytracking')
|
@section('viewJsName', 'batterytracking')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="col-sm-3 col-sm-offset-3 col-md-3 col-md-offset-2">
|
<div class="col-lg-4 col-xs-12">
|
||||||
|
|
||||||
<h1 class="page-header">@yield('title')</h1>
|
<h1 class="page-header">@yield('title')</h1>
|
||||||
|
|
||||||
<form id="batterytracking-form">
|
<form id="batterytracking-form">
|
||||||
@@ -36,10 +35,9 @@
|
|||||||
<button id="save-batterytracking-button" type="submit" class="btn btn-default">{{ $L('OK') }}</button>
|
<button id="save-batterytracking-button" type="submit" class="btn btn-default">{{ $L('OK') }}</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-6 col-md-5 col-lg-3">
|
<div class="col-lg-4 col-xs-12">
|
||||||
@include('components.batterycard')
|
@include('components.batterycard')
|
||||||
</div>
|
</div>
|
||||||
@stop
|
@stop
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
@push('componentScripts')
|
@push('componentScripts')
|
||||||
<script src="/viewjs/components/batterycard.js"></script>
|
<script src="{{ $U('/viewjs/components/batterycard.js', true) }}?v={{ $version }}"></script>
|
||||||
@endpush
|
@endpush
|
||||||
|
|
||||||
<div class="main well">
|
<div class="main well">
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
@push('componentScripts')
|
@push('componentScripts')
|
||||||
<script src="/viewjs/components/datepicker.js"></script>
|
<script src="{{ $U('/viewjs/components/datepicker.js', true) }}?v={{ $version }}"></script>
|
||||||
@endpush
|
@endpush
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
@push('componentScripts')
|
@push('componentScripts')
|
||||||
<script src="/viewjs/components/datetimepicker.js"></script>
|
<script src="{{ $U('/viewjs/components/datetimepicker.js', true) }}?v={{ $version }}"></script>
|
||||||
@endpush
|
@endpush
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
@push('componentScripts')
|
@push('componentScripts')
|
||||||
<script src="/viewjs/components/habitcard.js"></script>
|
<script src="{{ $U('/viewjs/components/habitcard.js', true) }}?v={{ $version }}"></script>
|
||||||
@endpush
|
@endpush
|
||||||
|
|
||||||
<div class="main well">
|
<div class="main well">
|
||||||
|
66
views/components/menu.blade.php
Normal file
66
views/components/menu.blade.php
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
|
||||||
|
<ul class="nav navbar-nav sidebar-nav">
|
||||||
|
<li data-nav-for-page="stockoverview">
|
||||||
|
<a class="discrete-link" href="{{ $U('/stockoverview') }}"><i class="fa fa-tachometer fa-fw"></i> {{ $L('Stock overview') }}</a>
|
||||||
|
</li>
|
||||||
|
<li data-nav-for-page="habitsoverview">
|
||||||
|
<a class="discrete-link" href="{{ $U('/habitsoverview') }}"><i class="fa fa-tachometer fa-fw"></i> {{ $L('Habits overview') }}</a>
|
||||||
|
</li>
|
||||||
|
<li data-nav-for-page="batteriesoverview">
|
||||||
|
<a class="discrete-link" href="{{ $U('/batteriesoverview') }}"><i class="fa fa-tachometer fa-fw"></i> {{ $L('Batteries overview') }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="discrete-content-separator-2x"></div>
|
||||||
|
|
||||||
|
<ul class="nav navbar-nav sidebar-nav">
|
||||||
|
<li class="disabled"><a href="#"><strong>{{ $L('Record data') }}</strong></a></li>
|
||||||
|
<li data-nav-for-page="purchase">
|
||||||
|
<a class="discrete-link" href="{{ $U('/purchase') }}"><i class="fa fa-shopping-cart fa-fw"></i> {{ $L('Purchase') }}</a>
|
||||||
|
</li>
|
||||||
|
<li data-nav-for-page="consume">
|
||||||
|
<a class="discrete-link" href="{{ $U('/consume') }}"><i class="fa fa-cutlery fa-fw"></i> {{ $L('Consume') }}</a>
|
||||||
|
</li>
|
||||||
|
<li data-nav-for-page="shoppinglist">
|
||||||
|
<a class="discrete-link" href="{{ $U('/shoppinglist') }}"><i class="fa fa-shopping-bag fa-fw"></i> {{ $L('Shopping list') }}</a>
|
||||||
|
</li>
|
||||||
|
<li data-nav-for-page="inventory">
|
||||||
|
<a class="discrete-link" href="{{ $U('/inventory') }}"><i class="fa fa-list fa-fw"></i> {{ $L('Inventory') }}</a>
|
||||||
|
</li>
|
||||||
|
<li data-nav-for-page="habittracking">
|
||||||
|
<a class="discrete-link" href="{{ $U('/habittracking') }}"><i class="fa fa-play fa-fw"></i> {{ $L('Habit tracking') }}</a>
|
||||||
|
</li>
|
||||||
|
<li data-nav-for-page="batterytracking">
|
||||||
|
<a class="discrete-link" href="{{ $U('/batterytracking') }}"><i class="fa fa-fire fa-fw"></i> {{ $L('Battery tracking') }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="discrete-content-separator-2x"></div>
|
||||||
|
|
||||||
|
<ul class="nav navbar-nav sidebar-nav">
|
||||||
|
<li class="disabled"><a href="#"><strong>{{ $L('Manage master data') }}</strong></a></li>
|
||||||
|
<li data-nav-for-page="products">
|
||||||
|
<a class="discrete-link" href="{{ $U('/products') }}"><i class="fa fa-product-hunt fa-fw"></i> {{ $L('Products') }}</a>
|
||||||
|
</li>
|
||||||
|
<li data-nav-for-page="locations">
|
||||||
|
<a class="discrete-link" href="{{ $U('/locations') }}"><i class="fa fa-map-marker fa-fw"></i> {{ $L('Locations') }}</a>
|
||||||
|
</li>
|
||||||
|
<li data-nav-for-page="quantityunits">
|
||||||
|
<a class="discrete-link" href="{{ $U('/quantityunits') }}"><i class="fa fa-balance-scale fa-fw"></i> {{ $L('Quantity units') }}</a>
|
||||||
|
</li>
|
||||||
|
<li data-nav-for-page="habits">
|
||||||
|
<a class="discrete-link" href="{{ $U('/habits') }}"><i class="fa fa-refresh fa-fw"></i> {{ $L('Habits') }}</a>
|
||||||
|
</li>
|
||||||
|
<li data-nav-for-page="batteries">
|
||||||
|
<a class="discrete-link" href="{{ $U('/batteries') }}"><i class="fa fa-battery-three-quarters fa-fw"></i> {{ $L('Batteries') }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="discrete-content-separator-2x hidden-xs"></div>
|
||||||
|
|
||||||
|
<ul class="nav navbar-nav sidebar-nav nav-copyright">
|
||||||
|
<li>
|
||||||
|
Version {{ $version }}<br>
|
||||||
|
<a class="discrete-link" href="#" data-toggle="modal" data-target="#about-modal">{{ $L('About grocy') }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
@@ -1,5 +1,5 @@
|
|||||||
@push('componentScripts')
|
@push('componentScripts')
|
||||||
<script src="/viewjs/components/productcard.js"></script>
|
<script src="{{ $U('/viewjs/components/productcard.js', true) }}?v={{ $version }}"></script>
|
||||||
@endpush
|
@endpush
|
||||||
|
|
||||||
<div class="main well">
|
<div class="main well">
|
||||||
|
17
views/components/usermenu.blade.php
Normal file
17
views/components/usermenu.blade.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<ul class="nav navbar-nav navbar-right">
|
||||||
|
<li class="dropdown">
|
||||||
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown">@if(AUTHENTICATED === true){{ HTTP_USER }}@endif <span class="caret"></span></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li>
|
||||||
|
<a class="discrete-link logout-button" href="{{ $U('/logout') }}"><i class="fa fa-sign-out fa-fw"></i> {{ $L('Logout') }}</a>
|
||||||
|
</li>
|
||||||
|
<li class="divider logout-button"></li>
|
||||||
|
<li>
|
||||||
|
<a class="discrete-link" href="{{ $U('/manageapikeys') }}"><i class="fa fa-handshake-o fa-fw"></i> {{ $L('Manage API keys') }}</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="discrete-link" target="_blank" href="{{ $U('/api') }}"><i class="fa fa-book"></i> {{ $L('REST API & data model documentation') }}</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
@@ -5,8 +5,7 @@
|
|||||||
@section('viewJsName', 'consume')
|
@section('viewJsName', 'consume')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="col-sm-3 col-sm-offset-3 col-md-3 col-md-offset-2">
|
<div class="col-lg-4 col-xs-12">
|
||||||
|
|
||||||
<h1 class="page-header">@yield('title')</h1>
|
<h1 class="page-header">@yield('title')</h1>
|
||||||
|
|
||||||
<form id="consume-form">
|
<form id="consume-form">
|
||||||
@@ -37,10 +36,9 @@
|
|||||||
<button id="save-consume-button" type="submit" class="btn btn-default">{{ $L('OK') }}</button>
|
<button id="save-consume-button" type="submit" class="btn btn-default">{{ $L('OK') }}</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-6 col-md-5 col-lg-3">
|
<div class="col-lg-4 col-xs-12">
|
||||||
@include('components.productcard')
|
@include('components.productcard')
|
||||||
</div>
|
</div>
|
||||||
@stop
|
@stop
|
||||||
|
@@ -9,8 +9,7 @@
|
|||||||
@section('viewJsName', 'habitform')
|
@section('viewJsName', 'habitform')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="col-sm-3 col-sm-offset-3 col-md-4 col-md-offset-2">
|
<div class="col-lg-4 col-xs-12">
|
||||||
|
|
||||||
<h1 class="page-header">@yield('title')</h1>
|
<h1 class="page-header">@yield('title')</h1>
|
||||||
|
|
||||||
<script>Grocy.EditMode = '{{ $mode }}';</script>
|
<script>Grocy.EditMode = '{{ $mode }}';</script>
|
||||||
@@ -53,6 +52,5 @@
|
|||||||
<button id="save-habit-button" type="submit" class="btn btn-default">{{ $L('Save') }}</button>
|
<button id="save-habit-button" type="submit" class="btn btn-default">{{ $L('Save') }}</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@stop
|
@stop
|
||||||
|
@@ -5,54 +5,50 @@
|
|||||||
@section('viewJsName', 'habits')
|
@section('viewJsName', 'habits')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2">
|
<h1 class="page-header">
|
||||||
|
@yield('title')
|
||||||
<h1 class="page-header">
|
<a class="btn btn-default" href="{{ $U('/habit/new') }}" role="button">
|
||||||
@yield('title')
|
<i class="fa fa-plus"></i> {{ $L('Add') }}
|
||||||
<a class="btn btn-default" href="/habit/new" role="button">
|
</a>
|
||||||
<i class="fa fa-plus"></i> {{ $L('Add') }}
|
</h1>
|
||||||
</a>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table id="habits-table" class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>#</th>
|
|
||||||
<th>{{ $L('Name') }}</th>
|
|
||||||
<th>{{ $L('Period type') }}</th>
|
|
||||||
<th>{{ $L('Period days') }}</th>
|
|
||||||
<th>{{ $L('Description') }}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@foreach($habits as $habit)
|
|
||||||
<tr>
|
|
||||||
<td class="fit-content">
|
|
||||||
<a class="btn btn-info" href="/habit/{{ $habit->id }}" role="button">
|
|
||||||
<i class="fa fa-pencil"></i>
|
|
||||||
</a>
|
|
||||||
<a class="btn btn-danger habit-delete-button" href="#" role="button" data-habit-id="{{ $habit->id }}" data-habit-name="{{ $habit->name }}">
|
|
||||||
<i class="fa fa-trash"></i>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ $habit->name }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ $L($habit->period_type) }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ $habit->period_days }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ $habit->description }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@endforeach
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="habits-table" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>{{ $L('Name') }}</th>
|
||||||
|
<th>{{ $L('Period type') }}</th>
|
||||||
|
<th>{{ $L('Period days') }}</th>
|
||||||
|
<th>{{ $L('Description') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($habits as $habit)
|
||||||
|
<tr>
|
||||||
|
<td class="fit-content">
|
||||||
|
<a class="btn btn-info" href="{{ $U('/habit/') }}{{ $habit->id }}" role="button">
|
||||||
|
<i class="fa fa-pencil"></i>
|
||||||
|
</a>
|
||||||
|
<a class="btn btn-danger habit-delete-button" href="#" role="button" data-habit-id="{{ $habit->id }}" data-habit-name="{{ $habit->name }}">
|
||||||
|
<i class="fa fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $habit->name }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $L($habit->period_type) }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $habit->period_days }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $habit->description }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@stop
|
@stop
|
||||||
|
@@ -4,43 +4,60 @@
|
|||||||
@section('activeNav', 'habitsoverview')
|
@section('activeNav', 'habitsoverview')
|
||||||
@section('viewJsName', 'habitsoverview')
|
@section('viewJsName', 'habitsoverview')
|
||||||
|
|
||||||
|
@push('pageScripts')
|
||||||
|
<script src="{{ $U('/bower_components/jquery-ui/jquery-ui.min.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
@endpush
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2">
|
<h1 class="page-header">@yield('title')</h1>
|
||||||
|
|
||||||
<h1 class="page-header">@yield('title')</h1>
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
<div class="table-responsive">
|
<p class="btn btn-lg btn-warning no-real-button responsive-button">{{ $L('#1 habits are due to be done within the next #2 days', $countDueNextXDays, $nextXDays) }}</p>
|
||||||
<table id="habits-overview-table" class="table table-striped">
|
<p class="btn btn-lg btn-danger no-real-button responsive-button">{{ $L('#1 habits are overdue to be done', $countOverdue) }}</p>
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{{ $L('Habit') }}</th>
|
|
||||||
<th>{{ $L('Next estimated tracking') }}</th>
|
|
||||||
<th>{{ $L('Last tracked') }}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@foreach($currentHabits as $curentHabitEntry)
|
|
||||||
<tr class="@if(FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->period_type === \Grocy\Services\HabitsService::HABIT_TYPE_DYNAMIC_REGULAR && $nextHabitTimes[$curentHabitEntry->habit_id] < date('Y-m-d H:i:s')) error-bg @endif">
|
|
||||||
<td>
|
|
||||||
{{ FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->name }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@if(FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->period_type === \Grocy\Services\HabitsService::HABIT_TYPE_DYNAMIC_REGULAR)
|
|
||||||
{{ $nextHabitTimes[$curentHabitEntry->habit_id] }}
|
|
||||||
<time class="timeago timeago-contextual" datetime="{{ $nextHabitTimes[$curentHabitEntry->habit_id] }}"></time>
|
|
||||||
@else
|
|
||||||
...
|
|
||||||
@endif
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ $curentHabitEntry->last_tracked_time }}
|
|
||||||
<time class="timeago timeago-contextual" datetime="{{ $curentHabitEntry->last_tracked_time }}"></time>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@endforeach
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="discrete-content-separator-2x"></div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="habits-overview-table" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>{{ $L('Habit') }}</th>
|
||||||
|
<th>{{ $L('Next estimated tracking') }}</th>
|
||||||
|
<th>{{ $L('Last tracked') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($currentHabits as $curentHabitEntry)
|
||||||
|
<tr class="@if(FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->period_type === \Grocy\Services\HabitsService::HABIT_TYPE_DYNAMIC_REGULAR && $nextHabitTimes[$curentHabitEntry->habit_id] < date('Y-m-d H:i:s')) error-bg @endif">
|
||||||
|
<td class="fit-content">
|
||||||
|
<a class="btn btn-success btn-xs track-habit-button" href="#" title="{{ $L('Track execution of habit #1', FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->name) }}"
|
||||||
|
data-habit-id="{{ $curentHabitEntry->habit_id }}"
|
||||||
|
data-habit-name="{{ FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->name }}">
|
||||||
|
<i class="fa fa-play"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->name }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@if(FindObjectInArrayByPropertyValue($habits, 'id', $curentHabitEntry->habit_id)->period_type === \Grocy\Services\HabitsService::HABIT_TYPE_DYNAMIC_REGULAR)
|
||||||
|
{{ $nextHabitTimes[$curentHabitEntry->habit_id] }}
|
||||||
|
<time class="timeago timeago-contextual" datetime="{{ $nextHabitTimes[$curentHabitEntry->habit_id] }}"></time>
|
||||||
|
@else
|
||||||
|
...
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span id="habit-{{ $curentHabitEntry->habit_id }}-last-tracked-time">{{ $curentHabitEntry->last_tracked_time }}</span>
|
||||||
|
<time id="habit-{{ $curentHabitEntry->habit_id }}-last-tracked-time-timeago" class="timeago timeago-contextual" datetime="{{ $curentHabitEntry->last_tracked_time }}"></time>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@stop
|
@stop
|
||||||
|
@@ -5,8 +5,7 @@
|
|||||||
@section('viewJsName', 'habittracking')
|
@section('viewJsName', 'habittracking')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="col-sm-3 col-sm-offset-3 col-md-3 col-md-offset-2">
|
<div class="col-lg-4 col-xs-12">
|
||||||
|
|
||||||
<h1 class="page-header">@yield('title')</h1>
|
<h1 class="page-header">@yield('title')</h1>
|
||||||
|
|
||||||
<form id="habittracking-form">
|
<form id="habittracking-form">
|
||||||
@@ -30,10 +29,9 @@
|
|||||||
<button id="save-habittracking-button" type="submit" class="btn btn-default">{{ $L('OK') }}</button>
|
<button id="save-habittracking-button" type="submit" class="btn btn-default">{{ $L('OK') }}</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-6 col-md-5 col-lg-3">
|
<div class="col-lg-4 col-xs-12">
|
||||||
@include('components.habitcard')
|
@include('components.habitcard')
|
||||||
</div>
|
</div>
|
||||||
@stop
|
@stop
|
||||||
|
@@ -5,8 +5,7 @@
|
|||||||
@section('viewJsName', 'inventory')
|
@section('viewJsName', 'inventory')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="col-sm-4 col-sm-offset-3 col-md-3 col-md-offset-2">
|
<div class="col-lg-4 col-xs-12">
|
||||||
|
|
||||||
<h1 class="page-header">@yield('title')</h1>
|
<h1 class="page-header">@yield('title')</h1>
|
||||||
|
|
||||||
<form id="inventory-form">
|
<form id="inventory-form">
|
||||||
@@ -39,10 +38,9 @@
|
|||||||
<button id="save-inventory-button" type="submit" class="btn btn-default">{{ $L('OK') }}</button>
|
<button id="save-inventory-button" type="submit" class="btn btn-default">{{ $L('OK') }}</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-6 col-md-5 col-lg-3">
|
<div class="col-lg-4 col-xs-12">
|
||||||
@include('components.productcard')
|
@include('components.productcard')
|
||||||
</div>
|
</div>
|
||||||
@stop
|
@stop
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="{{ CULTURE }}">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||||
@@ -9,25 +9,28 @@
|
|||||||
<meta name="format-detection" content="telephone=no">
|
<meta name="format-detection" content="telephone=no">
|
||||||
|
|
||||||
<meta name="author" content="Bernd Bestel (bernd@berrnd.de)">
|
<meta name="author" content="Bernd Bestel (bernd@berrnd.de)">
|
||||||
<link rel="icon" type="image/png" sizes="200x200" href="/img/grocy.png">
|
<link rel="icon" type="image/png" sizes="200x200" href="{{ $U('/img/grocy.png?v=', true) }}{{ $version }}">
|
||||||
|
|
||||||
<title>@yield('title') | grocy</title>
|
<title>@yield('title') | grocy</title>
|
||||||
|
|
||||||
<link href="/bower_components/bootstrap/dist/css/bootstrap.min.css?v={{ $version }}" rel="stylesheet">
|
<link href="{{ $U('/bower_components/bootstrap/dist/css/bootstrap.min.css?v=', true) }}{{ $version }}" rel="stylesheet">
|
||||||
<link href="/bower_components/font-awesome/css/font-awesome.min.css?v={{ $version }}" rel="stylesheet">
|
<link href="{{ $U('/bower_components/font-awesome/css/font-awesome.min.css?v=', true) }}{{ $version }}" rel="stylesheet">
|
||||||
<link href="/bower_components/bootstrap-datepicker/dist/css/bootstrap-datepicker3.min.css?v={{ $version }}" rel="stylesheet">
|
<link href="{{ $U('/bower_components/bootstrap-datepicker/dist/css/bootstrap-datepicker3.min.css?v=', true) }}{{ $version }}" rel="stylesheet">
|
||||||
<link href="/bower_components/bootstrap-combobox/css/bootstrap-combobox.css?v={{ $version }}" rel="stylesheet">
|
<link href="{{ $U('/bower_components/bootstrap-combobox/css/bootstrap-combobox.css?v=', true) }}{{ $version }}" rel="stylesheet">
|
||||||
<link href="/bower_components/datatables.net-bs/css/dataTables.bootstrap.min.css?v={{ $version }}" rel="stylesheet">
|
<link href="{{ $U('/bower_components/datatables.net-bs/css/dataTables.bootstrap.min.css?v=', true) }}{{ $version }}" rel="stylesheet">
|
||||||
<link href="/bower_components/datatables.net-responsive-bs/css/responsive.bootstrap.min.css?v={{ $version }}" rel="stylesheet">
|
<link href="{{ $U('/bower_components/datatables.net-responsive-bs/css/responsive.bootstrap.min.css?v=', true) }}{{ $version }}" rel="stylesheet">
|
||||||
<link href="/bower_components/toastr/toastr.min.css?v={{ $version }}" rel="stylesheet">
|
<link href="{{ $U('/bower_components/toastr/toastr.min.css?v=', true) }}{{ $version }}" rel="stylesheet">
|
||||||
<link href="/bower_components/tagmanager/tagmanager.css?v={{ $version }}" rel="stylesheet">
|
<link href="{{ $U('/bower_components/tagmanager/tagmanager.css?v=', true) }}{{ $version }}" rel="stylesheet">
|
||||||
<link href="/bower_components/eonasdan-bootstrap-datetimepicker/build/css/bootstrap-datetimepicker.min.css?v={{ $version }}" rel="stylesheet">
|
<link href="{{ $U('/bower_components/eonasdan-bootstrap-datetimepicker/build/css/bootstrap-datetimepicker.min.css?v=', true) }}{{ $version }}" rel="stylesheet">
|
||||||
<link href="/components_unmanaged/noto-sans-v6-latin/noto-sans-v6-latin.css?v={{ $version }}" rel="stylesheet">
|
<link href="{{ $U('/bower_components/bootstrap-side-navbar/source/assets/stylesheets/navbar-fixed-side.css?v=', true) }}{{ $version }}" rel="stylesheet">
|
||||||
<link href="/css/grocy.css?v={{ $version }}" rel="stylesheet">
|
<link href="{{ $U('/components_unmanaged/noto-sans-v6-latin/noto-sans-v6-latin.css?v=', true) }}{{ $version }}" rel="stylesheet">
|
||||||
|
<link href="{{ $U('/css/grocy.css?v=', true) }}{{ $version }}" rel="stylesheet">
|
||||||
|
@stack('pageStyles')
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var Grocy = { };
|
var Grocy = { };
|
||||||
Grocy.Components = { };
|
Grocy.Components = { };
|
||||||
|
Grocy.BaseUrl = '{{ $U('/') }}';
|
||||||
Grocy.LocalizationStrings = {!! json_encode($localizationStrings) !!};
|
Grocy.LocalizationStrings = {!! json_encode($localizationStrings) !!};
|
||||||
Grocy.ActiveNav = '@yield('activeNav', '')';
|
Grocy.ActiveNav = '@yield('activeNav', '')';
|
||||||
</script>
|
</script>
|
||||||
@@ -42,93 +45,16 @@
|
|||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
</button>
|
</button>
|
||||||
<a class="navbar-brand" href="/">grocy</a>
|
<a class="navbar-brand" href="{{ $U('/') }}">grocy</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="navbar" class="navbar-collapse collapse">
|
<div id="navbar" class="navbar-collapse collapse">
|
||||||
<ul class="nav navbar-nav navbar-right">
|
@include('components.usermenu')
|
||||||
<li>
|
|
||||||
<a class="discrete-link logout-button" href="/logout"><i class="fa fa-sign-out fa-fw"></i> {{ $L('Logout') }}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="navbar-mobile" class="navbar-collapse collapse">
|
<div id="navbar-mobile" class="navbar-collapse collapse">
|
||||||
|
@include('components.menu')
|
||||||
<ul class="nav navbar-nav navbar-right">
|
@include('components.usermenu')
|
||||||
<li data-nav-for-page="stockoverview">
|
|
||||||
<a class="discrete-link" href="/stockoverview"><i class="fa fa-tachometer fa-fw"></i> {{ $L('Stock overview') }}</a>
|
|
||||||
</li>
|
|
||||||
<li data-nav-for-page="habitsoverview">
|
|
||||||
<a class="discrete-link" href="/habitsoverview"><i class="fa fa-tachometer fa-fw"></i> {{ $L('Habits overview') }}</a>
|
|
||||||
</li>
|
|
||||||
<li data-nav-for-page="batteriesoverview">
|
|
||||||
<a class="discrete-link" href="/batteriesoverview"><i class="fa fa-tachometer fa-fw"></i> {{ $L('Batteries overview') }}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<ul class="nav navbar-nav navbar-right">
|
|
||||||
<li class="disabled"><a href="#"><strong>{{ $L('Record data') }}</strong></a></li>
|
|
||||||
<li data-nav-for-page="purchase">
|
|
||||||
<a class="discrete-link" href="/purchase"><i class="fa fa-shopping-cart fa-fw"></i> {{ $L('Purchase') }}</a>
|
|
||||||
</li>
|
|
||||||
<li data-nav-for-page="consume">
|
|
||||||
<a class="discrete-link" href="/consume"><i class="fa fa-cutlery fa-fw"></i> {{ $L('Consume') }}</a>
|
|
||||||
</li>
|
|
||||||
<li data-nav-for-page="shoppinglist">
|
|
||||||
<a class="discrete-link" href="/shoppinglist"><i class="fa fa-shopping-bag fa-fw"></i> {{ $L('Shopping list') }}</a>
|
|
||||||
</li>
|
|
||||||
<li data-nav-for-page="inventory">
|
|
||||||
<a class="discrete-link" href="/inventory"><i class="fa fa-list fa-fw"></i> {{ $L('Inventory') }}</a>
|
|
||||||
</li>
|
|
||||||
<li data-nav-for-page="habittracking">
|
|
||||||
<a class="discrete-link" href="/habittracking"><i class="fa fa-play fa-fw"></i> {{ $L('Habit tracking') }}</a>
|
|
||||||
</li>
|
|
||||||
<li data-nav-for-page="batterytracking">
|
|
||||||
<a class="discrete-link" href="/batterytracking"><i class="fa fa-fire fa-fw"></i> {{ $L('Battery tracking') }}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<ul class="nav navbar-nav navbar-right">
|
|
||||||
<li class="disabled"><a href="#"><strong>{{ $L('Manage master data') }}</strong></a></li>
|
|
||||||
<li data-nav-for-page="products">
|
|
||||||
<a class="discrete-link" href="/products"><i class="fa fa-product-hunt fa-fw"></i> {{ $L('Products') }}</a>
|
|
||||||
</li>
|
|
||||||
<li data-nav-for-page="locations">
|
|
||||||
<a class="discrete-link" href="/locations"><i class="fa fa-map-marker fa-fw"></i> {{ $L('Locations') }}</a>
|
|
||||||
</li>
|
|
||||||
<li data-nav-for-page="quantityunits">
|
|
||||||
<a class="discrete-link" href="/quantityunits"><i class="fa fa-balance-scale fa-fw"></i> {{ $L('Quantity units') }}</a>
|
|
||||||
</li>
|
|
||||||
<li data-nav-for-page="habits">
|
|
||||||
<a class="discrete-link" href="/habits"><i class="fa fa-refresh fa-fw"></i> {{ $L('Habits') }}</a>
|
|
||||||
</li>
|
|
||||||
<li data-nav-for-page="batteries">
|
|
||||||
<a class="discrete-link" href="/batteries"><i class="fa fa-battery-three-quarters fa-fw"></i> {{ $L('Batteries') }}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<ul class="nav navbar-nav navbar-right">
|
|
||||||
<li>
|
|
||||||
<a class="discrete-link logout-button" href="/logout"><i class="fa fa-sign-out fa-fw"></i> {{ $L('Logout') }}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="nav-copyright nav nav-sidebar">
|
|
||||||
grocy is a project by
|
|
||||||
<a class="discrete-link" href="https://berrnd.de" target="_blank">Bernd Bestel</a>
|
|
||||||
<br>
|
|
||||||
Created with passion since 2017
|
|
||||||
<br>
|
|
||||||
Version {{ $version }}
|
|
||||||
<br>
|
|
||||||
Life runs on code
|
|
||||||
<br>
|
|
||||||
<a class="discrete-link" href="https://github.com/berrnd/grocy" target="_blank">
|
|
||||||
<i class="fa fa-github"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -136,107 +62,73 @@
|
|||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
<div class="col-sm-3 col-md-2 sidebar">
|
<div id="sidebar" class="col-sm-3 col-lg-2">
|
||||||
|
<nav class="navbar navbar-default navbar-fixed-side hidden-xs">
|
||||||
<ul class="nav nav-sidebar">
|
<div class="navbar-collapse collapse">
|
||||||
<li data-nav-for-page="stockoverview">
|
@include('components.menu')
|
||||||
<a class="discrete-link" href="/stockoverview"><i class="fa fa-tachometer fa-fw"></i> {{ $L('Stock overview') }}</a>
|
</div>
|
||||||
</li>
|
</nav>
|
||||||
<li data-nav-for-page="habitsoverview">
|
|
||||||
<a class="discrete-link" href="/habitsoverview"><i class="fa fa-tachometer fa-fw"></i> {{ $L('Habits overview') }}</a>
|
|
||||||
</li>
|
|
||||||
<li data-nav-for-page="batteriesoverview">
|
|
||||||
<a class="discrete-link" href="/batteriesoverview"><i class="fa fa-tachometer fa-fw"></i> {{ $L('Batteries overview') }}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<ul class="nav nav-sidebar">
|
|
||||||
<li class="disabled"><a href="#"><strong>{{ $L('Record data') }}</strong></a></li>
|
|
||||||
<li data-nav-for-page="purchase">
|
|
||||||
<a class="discrete-link" href="/purchase"><i class="fa fa-shopping-cart fa-fw"></i> {{ $L('Purchase') }}</a>
|
|
||||||
</li>
|
|
||||||
<li data-nav-for-page="consume">
|
|
||||||
<a class="discrete-link" href="/consume"><i class="fa fa-cutlery fa-fw"></i> {{ $L('Consume') }}</a>
|
|
||||||
</li>
|
|
||||||
<li data-nav-for-page="shoppinglist">
|
|
||||||
<a class="discrete-link" href="/shoppinglist"><i class="fa fa-shopping-bag fa-fw"></i> {{ $L('Shopping list') }}</a>
|
|
||||||
</li>
|
|
||||||
<li data-nav-for-page="inventory">
|
|
||||||
<a class="discrete-link" href="/inventory"><i class="fa fa-list fa-fw"></i> {{ $L('Inventory') }}</a>
|
|
||||||
</li>
|
|
||||||
<li data-nav-for-page="habittracking">
|
|
||||||
<a class="discrete-link" href="/habittracking"><i class="fa fa-play fa-fw"></i> {{ $L('Habit tracking') }}</a>
|
|
||||||
</li>
|
|
||||||
<li data-nav-for-page="batterytracking">
|
|
||||||
<a class="discrete-link" href="/batterytracking"><i class="fa fa-fire fa-fw"></i> {{ $L('Battery tracking') }}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<ul class="nav nav-sidebar">
|
|
||||||
<li class="disabled"><a href="#"><strong>{{ $L('Manage master data') }}</strong></a></li>
|
|
||||||
<li data-nav-for-page="products">
|
|
||||||
<a class="discrete-link" href="/products"><i class="fa fa-product-hunt fa-fw"></i> {{ $L('Products') }}</a>
|
|
||||||
</li>
|
|
||||||
<li data-nav-for-page="locations">
|
|
||||||
<a class="discrete-link" href="/locations"><i class="fa fa-map-marker fa-fw"></i> {{ $L('Locations') }}</a>
|
|
||||||
</li>
|
|
||||||
<li data-nav-for-page="quantityunits">
|
|
||||||
<a class="discrete-link" href="/quantityunits"><i class="fa fa-balance-scale fa-fw"></i> {{ $L('Quantity units') }}</a>
|
|
||||||
</li>
|
|
||||||
<li data-nav-for-page="habits">
|
|
||||||
<a class="discrete-link" href="/habits"><i class="fa fa-refresh fa-fw"></i> {{ $L('Habits') }}</a>
|
|
||||||
</li>
|
|
||||||
<li data-nav-for-page="batteries">
|
|
||||||
<a class="discrete-link" href="/batteries"><i class="fa fa-battery-three-quarters fa-fw"></i> {{ $L('Batteries') }}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="nav-copyright nav nav-sidebar">
|
|
||||||
grocy is a project by
|
|
||||||
<a class="discrete-link" href="https://berrnd.de" target="_blank">Bernd Bestel</a>
|
|
||||||
<br>
|
|
||||||
Created with passion since 2017
|
|
||||||
<br>
|
|
||||||
Version {{ $version }}
|
|
||||||
<br>
|
|
||||||
Life runs on code
|
|
||||||
<br>
|
|
||||||
<a class="discrete-link" href="https://github.com/berrnd/grocy" target="_blank">
|
|
||||||
<i class="fa fa-github"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@yield('content')
|
<div class="col-sm-9 col-lg-10">
|
||||||
|
@yield('content')
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/bower_components/jquery/dist/jquery.min.js?v={{ $version }}"></script>
|
<div class="modal fade" id="about-modal" tabindex="-1">
|
||||||
<script src="/bower_components/bootstrap/dist/js/bootstrap.min.js?v={{ $version }}"></script>
|
<div class="modal-dialog">
|
||||||
<script src="/bower_components/bootbox/bootbox.js?v={{ $version }}"></script>
|
<div class="modal-content text-center">
|
||||||
<script src="/bower_components/jquery.serializeJSON/jquery.serializejson.min.js?v={{ $version }}"></script>
|
<div class="modal-header">
|
||||||
<script src="/bower_components/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js?v={{ $version }}"></script>
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||||
@if(!empty($L('bootstrap_datepicker_locale')))<script src="/bower_components/bootstrap-datepicker/dist/locales/bootstrap-datepicker.{{ $L('bootstrap_datepicker_locale') }}.min.js?v={{ $version }}"></script>@endif
|
<h4 class="modal-title">{{ $L('About grocy') }}</h4>
|
||||||
<script src="/bower_components/moment/min/moment.min.js?v={{ $version }}"></script>
|
</div>
|
||||||
@if(!empty($L('moment_locale')))<script src="/bower_components/moment/locale/{{ $L('moment_locale') }}.js?v={{ $version }}"></script>@endif
|
<div class="modal-body">
|
||||||
<script src="/bower_components/bootstrap-validator/dist/validator.min.js?v={{ $version }}"></script>
|
grocy is a project by
|
||||||
<script src="/bower_components/bootstrap-combobox/js/bootstrap-combobox.js?v={{ $version }}"></script>
|
<a href="https://berrnd.de" target="_blank">Bernd Bestel</a><br>
|
||||||
<script src="/bower_components/datatables.net/js/jquery.dataTables.min.js?v={{ $version }}"></script>
|
Created with passion since 2017<br>
|
||||||
<script src="/bower_components/datatables.net-bs/js/dataTables.bootstrap.min.js?v={{ $version }}"></script>
|
<br>
|
||||||
<script src="/bower_components/datatables.net-responsive/js/dataTables.responsive.min.js?v={{ $version }}"></script>
|
Version {{ $version }}<br>
|
||||||
<script src="/bower_components/datatables.net-responsive-bs/js/responsive.bootstrap.min.js?v={{ $version }}"></script>
|
{{ $L('Released on') }} {{ $releaseDate }} <time class="timeago timeago-contextual" datetime="{{ $releaseDate }}"></time><br>
|
||||||
<script src="/bower_components/jquery-timeago/jquery.timeago.js?v={{ $version }}"></script>
|
<br>
|
||||||
<script src="/bower_components/jquery-timeago/locales/jquery.timeago.{{ $L('timeago_locale') }}.js?v={{ $version }}"></script>
|
Life runs on code<br>
|
||||||
<script src="/bower_components/toastr/toastr.min.js?v={{ $version }}"></script>
|
<a href="https://github.com/berrnd/grocy" target="_blank">
|
||||||
<script src="/bower_components/tagmanager/tagmanager.js?v={{ $version }}"></script>
|
<i class="fa fa-github"></i>
|
||||||
<script src="/bower_components/eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min.js?v={{ $version }}"></script>
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">{{ $L('Close') }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="/js/extensions.js?v={{ $version }}"></script>
|
<script src="{{ $U('/bower_components/jquery/dist/jquery.min.js?v=', true) }}{{ $version }}"></script>
|
||||||
<script src="/js/grocy.js?v={{ $version }}"></script>
|
<script src="{{ $U('/bower_components/bootstrap/dist/js/bootstrap.min.js?v=', true) }}{{ $version }}"></script>
|
||||||
<script src="/viewjs/@yield('viewJsName').js"></script>
|
<script src="{{ $U('/bower_components/bootbox/bootbox.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
<script src="{{ $U('/bower_components/jquery.serializeJSON/jquery.serializejson.min.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
<script src="{{ $U('/bower_components/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
@if(!empty($L('bootstrap_datepicker_locale')))<script src="{{ $U('/bower_components', true) }}/bootstrap-datepicker/dist/locales/bootstrap-datepicker.{{ $L('bootstrap_datepicker_locale') }}.min.js?v={{ $version }}"></script>@endif
|
||||||
|
<script src="{{ $U('/bower_components/moment/min/moment.min.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
@if(!empty($L('moment_locale')))<script src="{{ $U('/bower_components', true) }}/moment/locale/{{ $L('moment_locale') }}.js?v={{ $version }}"></script>@endif
|
||||||
|
<script src="{{ $U('/bower_components/bootstrap-validator/dist/validator.min.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
<script src="{{ $U('/bower_components/bootstrap-combobox/js/bootstrap-combobox.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
<script src="{{ $U('/bower_components/datatables.net/js/jquery.dataTables.min.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
<script src="{{ $U('/bower_components/datatables.net-bs/js/dataTables.bootstrap.min.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
<script src="{{ $U('/bower_components/datatables.net-responsive/js/dataTables.responsive.min.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
<script src="{{ $U('/bower_components/datatables.net-responsive-bs/js/responsive.bootstrap.min.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
<script src="{{ $U('/bower_components/jquery-timeago/jquery.timeago.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
<script src="{{ $U('/bower_components', true) }}/jquery-timeago/locales/jquery.timeago.{{ $L('timeago_locale') }}.js?v={{ $version }}"></script>
|
||||||
|
<script src="{{ $U('/bower_components/toastr/toastr.min.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
<script src="{{ $U('/bower_components/tagmanager/tagmanager.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
<script src="{{ $U('/bower_components/eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
|
||||||
|
<script src="{{ $U('/js/extensions.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
<script src="{{ $U('/js/grocy.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
@stack('pageScripts')
|
||||||
@stack('componentScripts')
|
@stack('componentScripts')
|
||||||
|
<script src="{{ $U('/viewjs', true) }}/@yield('viewJsName').js?v={{ $version }}"></script>
|
||||||
|
|
||||||
@if(file_exists(__DIR__ . '/../../data/add_before_end_body.html'))
|
@if(file_exists(__DIR__ . '/../../data/add_before_end_body.html'))
|
||||||
@php include __DIR__ . '/../../data/add_before_end_body.html' @endphp
|
@php include __DIR__ . '/../../data/add_before_end_body.html' @endphp
|
||||||
|
@@ -9,8 +9,7 @@
|
|||||||
@section('viewJsName', 'locationform')
|
@section('viewJsName', 'locationform')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="col-sm-3 col-sm-offset-3 col-md-4 col-md-offset-2">
|
<div class="col-lg-4 col-xs-12">
|
||||||
|
|
||||||
<h1 class="page-header">@yield('title')</h1>
|
<h1 class="page-header">@yield('title')</h1>
|
||||||
|
|
||||||
<script>Grocy.EditMode = '{{ $mode }}';</script>
|
<script>Grocy.EditMode = '{{ $mode }}';</script>
|
||||||
@@ -35,6 +34,5 @@
|
|||||||
<button id="save-location-button" type="submit" class="btn btn-default">{{ $L('Save') }}</button>
|
<button id="save-location-button" type="submit" class="btn btn-default">{{ $L('Save') }}</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@stop
|
@stop
|
||||||
|
@@ -5,46 +5,42 @@
|
|||||||
@section('viewJsName', 'locations')
|
@section('viewJsName', 'locations')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2">
|
<h1 class="page-header">
|
||||||
|
@yield('title')
|
||||||
<h1 class="page-header">
|
<a class="btn btn-default" href="{{ $U('/location/new') }}" role="button">
|
||||||
@yield('title')
|
<i class="fa fa-plus"></i> {{ $L('Add') }}
|
||||||
<a class="btn btn-default" href="/location/new" role="button">
|
</a>
|
||||||
<i class="fa fa-plus"></i> {{ $L('Add') }}
|
</h1>
|
||||||
</a>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table id="locations-table" class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>#</th>
|
|
||||||
<th>{{ $L('Name') }}</th>
|
|
||||||
<th>{{ $L('Description') }}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@foreach($locations as $location)
|
|
||||||
<tr>
|
|
||||||
<td class="fit-content">
|
|
||||||
<a class="btn btn-info" href="/location/{{ $location->id }}" role="button">
|
|
||||||
<i class="fa fa-pencil"></i>
|
|
||||||
</a>
|
|
||||||
<a class="btn btn-danger location-delete-button" href="#" role="button" data-location-id="{{ $location->id }}" data-location-name="{{ $location->name }}">
|
|
||||||
<i class="fa fa-trash"></i>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ $location->name }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ $location->description }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@endforeach
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="locations-table" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>{{ $L('Name') }}</th>
|
||||||
|
<th>{{ $L('Description') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($locations as $location)
|
||||||
|
<tr>
|
||||||
|
<td class="fit-content">
|
||||||
|
<a class="btn btn-info" href="{{ $U('/location/') }}{{ $location->id }}" role="button">
|
||||||
|
<i class="fa fa-pencil"></i>
|
||||||
|
</a>
|
||||||
|
<a class="btn btn-danger location-delete-button" href="#" role="button" data-location-id="{{ $location->id }}" data-location-name="{{ $location->name }}">
|
||||||
|
<i class="fa fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $location->name }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $location->description }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@stop
|
@stop
|
||||||
|
@@ -4,27 +4,25 @@
|
|||||||
@section('viewJsName', 'login')
|
@section('viewJsName', 'login')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="col-md-4 col-md-offset-5">
|
<div class="col-md-6 col-md-offset-3 col-xs-12">
|
||||||
|
<h1 class="page-header text-center">@yield('title')</h1>
|
||||||
|
|
||||||
<h1 class="page-header text-center">@yield('title')</h1>
|
<form method="post" action="{{ $U('/login') }}" id="login-form">
|
||||||
|
|
||||||
<form method="post" action="/login" id="login-form">
|
<div class="form-group">
|
||||||
|
<label for="name">{{ $L('Username') }}</label>
|
||||||
|
<input type="text" class="form-control" required id="username" name="username">
|
||||||
|
<div class="help-block with-errors"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="name">{{ $L('Username') }}</label>
|
<label for="name">{{ $L('Password') }}</label>
|
||||||
<input type="text" class="form-control" required id="username" name="username">
|
<input type="password" class="form-control" required id="password" name="password">
|
||||||
<div class="help-block with-errors"></div>
|
<div id="login-error" class="help-block with-errors"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<button id="login-button" type="submit" class="btn btn-default">{{ $L('OK') }}</button>
|
||||||
<label for="name">{{ $L('Password') }}</label>
|
|
||||||
<input type="password" class="form-control" required id="password" name="password">
|
|
||||||
<div id="login-error" class="help-block with-errors"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button id="login-button" type="submit" class="btn btn-default">{{ $L('OK') }}</button>
|
</form>
|
||||||
|
</div>
|
||||||
</form>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
@stop
|
@stop
|
||||||
|
60
views/manageapikeys.blade.php
Normal file
60
views/manageapikeys.blade.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
@extends('layout.default')
|
||||||
|
|
||||||
|
@section('title', $L('API keys'))
|
||||||
|
@section('activeNav', '')
|
||||||
|
@section('viewJsName', 'manageapikeys')
|
||||||
|
|
||||||
|
@push('pageScripts')
|
||||||
|
<script src="{{ $U('/bower_components/jquery-ui/jquery-ui.min.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<h1 class="page-header">
|
||||||
|
@yield('title')
|
||||||
|
<a class="btn btn-default" href="{{ $U('/manageapikeys/new') }}" role="button">
|
||||||
|
<i class="fa fa-plus"></i> {{ $L('Create new API key') }}
|
||||||
|
</a>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p class="lead"><a href="{{ $U('/api') }}" target="_blank">{{ $L('REST API & data model documentation') }}</a></p>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="apikeys-table" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>{{ $L('API key') }}</th>
|
||||||
|
<th>{{ $L('Expires') }}</th>
|
||||||
|
<th>{{ $L('Last used') }}</th>
|
||||||
|
<th>{{ $L('Created') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($apiKeys as $apiKey)
|
||||||
|
<tr id="apiKeyRow_{{ $apiKey->id }}">
|
||||||
|
<td class="fit-content">
|
||||||
|
<a class="btn btn-danger apikey-delete-button" href="#" role="button" data-apikey-id="{{ $apiKey->id }}" data-apikey-apikey="{{ $apiKey->api_key }}">
|
||||||
|
<i class="fa fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $apiKey->api_key }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $apiKey->expires }}
|
||||||
|
<time class="timeago timeago-contextual" datetime="{{ $apiKey->expires }}"></time>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@if(empty($apiKey->last_used)){{ $L('never') }}@else{{ $apiKey->last_used }}@endif
|
||||||
|
<time class="timeago timeago-contextual" datetime="{{ $apiKey->last_used }}"></time>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $apiKey->row_created_timestamp }}
|
||||||
|
<time class="timeago timeago-contextual" datetime="{{ $apiKey->row_created_timestamp }}"></time>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
@stop
|
36
views/openapiui.blade.php
Normal file
36
views/openapiui.blade.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
|
||||||
|
<meta name="robots" content="noindex,nofollow">
|
||||||
|
<meta name="format-detection" content="telephone=no">
|
||||||
|
|
||||||
|
<meta name="author" content="Bernd Bestel (bernd@berrnd.de)">
|
||||||
|
<link rel="icon" type="image/png" sizes="200x200" href="{{ $U('/img/grocy.png?v=', true) }}{{ $version }}">
|
||||||
|
|
||||||
|
<title>{{ $L('REST API & data model documentation') }} | grocy</title>
|
||||||
|
|
||||||
|
<link href="{{ $U('/bower_components/swagger-ui/dist/swagger-ui.css?v=', true) }}{{ $version }}" rel="stylesheet">
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var Grocy = { };
|
||||||
|
Grocy.OpenApi = { };
|
||||||
|
Grocy.OpenApi.SpecUrl = '{{ $U('/api/get-openapi-specification') }}';
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="swagger-ui"></div>
|
||||||
|
|
||||||
|
<script src="{{ $U('/bower_components/swagger-ui/dist/swagger-ui-bundle.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
<script src="{{ $U('/bower_components/swagger-ui/dist/swagger-ui-standalone-preset.js?v=', true) }}{{ $version }}"></script>
|
||||||
|
<script src="{{ $U('/viewjs', true) }}/openapiui.js?v={{ $version }}"></script>
|
||||||
|
|
||||||
|
@if(file_exists(__DIR__ . '/../../data/add_before_end_body.html'))
|
||||||
|
@php include __DIR__ . '/../../data/add_before_end_body.html' @endphp
|
||||||
|
@endif
|
||||||
|
</body>
|
||||||
|
</html>
|
@@ -9,8 +9,7 @@
|
|||||||
@section('viewJsName', 'productform')
|
@section('viewJsName', 'productform')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="col-sm-3 col-sm-offset-3 col-md-4 col-md-offset-2">
|
<div class="col-lg-4 col-xs-12">
|
||||||
|
|
||||||
<h1 class="page-header">@yield('title')</h1>
|
<h1 class="page-header">@yield('title')</h1>
|
||||||
|
|
||||||
<script>Grocy.EditMode = '{{ $mode }}';</script>
|
<script>Grocy.EditMode = '{{ $mode }}';</script>
|
||||||
@@ -90,6 +89,5 @@
|
|||||||
|
|
||||||
<button id="save-product-button" type="submit" class="btn btn-default">{{ $L('Save') }}</button>
|
<button id="save-product-button" type="submit" class="btn btn-default">{{ $L('Save') }}</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@stop
|
@stop
|
||||||
|
@@ -5,66 +5,62 @@
|
|||||||
@section('viewJsName', 'products')
|
@section('viewJsName', 'products')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2">
|
<h1 class="page-header">
|
||||||
|
@yield('title')
|
||||||
<h1 class="page-header">
|
<a class="btn btn-default" href="{{ $U('/product/new') }}" role="button">
|
||||||
@yield('title')
|
<i class="fa fa-plus"></i> {{ $L('Add') }}
|
||||||
<a class="btn btn-default" href="/product/new" role="button">
|
</a>
|
||||||
<i class="fa fa-plus"></i> {{ $L('Add') }}
|
</h1>
|
||||||
</a>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table id="products-table" class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>#</th>
|
|
||||||
<th>{{ $L('Name') }}</th>
|
|
||||||
<th>{{ $L('Location') }}</th>
|
|
||||||
<th>{{ $L('Min. stock amount') }}</th>
|
|
||||||
<th>{{ $L('QU purchase') }}</th>
|
|
||||||
<th>{{ $L('QU stock') }}</th>
|
|
||||||
<th>{{ $L('QU factor') }}</th>
|
|
||||||
<th>{{ $L('Description') }}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@foreach($products as $product)
|
|
||||||
<tr>
|
|
||||||
<td class="fit-content">
|
|
||||||
<a class="btn btn-info" href="/product/{{ $product->id }}" role="button">
|
|
||||||
<i class="fa fa-pencil"></i>
|
|
||||||
</a>
|
|
||||||
<a class="btn btn-danger product-delete-button" href="#" role="button" data-product-id="{{ $product->id }}" data-product-name="{{ $product->name }}">
|
|
||||||
<i class="fa fa-trash"></i>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ $product->name }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ FindObjectInArrayByPropertyValue($locations, 'id', $product->location_id)->name }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ $product->min_stock_amount }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', $product->qu_id_purchase)->name }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', $product->qu_id_stock)->name }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ $product->qu_factor_purchase_to_stock }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ $product->description }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@endforeach
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="products-table" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>{{ $L('Name') }}</th>
|
||||||
|
<th>{{ $L('Location') }}</th>
|
||||||
|
<th>{{ $L('Min. stock amount') }}</th>
|
||||||
|
<th>{{ $L('QU purchase') }}</th>
|
||||||
|
<th>{{ $L('QU stock') }}</th>
|
||||||
|
<th>{{ $L('QU factor') }}</th>
|
||||||
|
<th>{{ $L('Description') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($products as $product)
|
||||||
|
<tr>
|
||||||
|
<td class="fit-content">
|
||||||
|
<a class="btn btn-info" href="{{ $U('/product/') }}{{ $product->id }}" role="button">
|
||||||
|
<i class="fa fa-pencil"></i>
|
||||||
|
</a>
|
||||||
|
<a class="btn btn-danger product-delete-button" href="#" role="button" data-product-id="{{ $product->id }}" data-product-name="{{ $product->name }}">
|
||||||
|
<i class="fa fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $product->name }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ FindObjectInArrayByPropertyValue($locations, 'id', $product->location_id)->name }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $product->min_stock_amount }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', $product->qu_id_purchase)->name }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', $product->qu_id_stock)->name }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $product->qu_factor_purchase_to_stock }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $product->description }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@stop
|
@stop
|
||||||
|
@@ -5,8 +5,7 @@
|
|||||||
@section('viewJsName', 'purchase')
|
@section('viewJsName', 'purchase')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="col-sm-4 col-sm-offset-3 col-md-3 col-md-offset-2">
|
<div class="col-lg-4 col-xs-12">
|
||||||
|
|
||||||
<h1 class="page-header">@yield('title')</h1>
|
<h1 class="page-header">@yield('title')</h1>
|
||||||
|
|
||||||
<form id="purchase-form">
|
<form id="purchase-form">
|
||||||
@@ -20,7 +19,7 @@
|
|||||||
@endforeach
|
@endforeach
|
||||||
</select>
|
</select>
|
||||||
<div class="help-block with-errors"></div>
|
<div class="help-block with-errors"></div>
|
||||||
<div id="flow-info-addbarcodetoselection" class="text-muted small hide"><strong><span id="addbarcodetoselection"></span></strong> will be added to the list of barcodes for the selected product on submit.</div>
|
<div id="flow-info-addbarcodetoselection" class="text-muted small hide"><strong><span id="addbarcodetoselection"></span></strong> {{ $L('will be added to the list of barcodes for the selected product on submit') }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@include('components.datepicker', array(
|
@include('components.datepicker', array(
|
||||||
@@ -37,10 +36,9 @@
|
|||||||
<button id="save-purchase-button" type="submit" class="btn btn-default">{{ $L('OK') }}</button>
|
<button id="save-purchase-button" type="submit" class="btn btn-default">{{ $L('OK') }}</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-6 col-md-5 col-lg-3">
|
<div class="col-lg-4 col-xs-12">
|
||||||
@include('components.productcard')
|
@include('components.productcard')
|
||||||
</div>
|
</div>
|
||||||
@stop
|
@stop
|
||||||
|
@@ -9,8 +9,7 @@
|
|||||||
@section('viewJsName', 'quantityunitform')
|
@section('viewJsName', 'quantityunitform')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="col-sm-3 col-sm-offset-3 col-md-4 col-md-offset-2">
|
<div class="col-lg-4 col-xs-12">
|
||||||
|
|
||||||
<h1 class="page-header">@yield('title')</h1>
|
<h1 class="page-header">@yield('title')</h1>
|
||||||
|
|
||||||
<script>Grocy.EditMode = '{{ $mode }}';</script>
|
<script>Grocy.EditMode = '{{ $mode }}';</script>
|
||||||
@@ -35,6 +34,5 @@
|
|||||||
<button id="save-quantityunit-button" type="submit" class="btn btn-default">{{ $L('Save') }}</button>
|
<button id="save-quantityunit-button" type="submit" class="btn btn-default">{{ $L('Save') }}</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@stop
|
@stop
|
||||||
|
@@ -5,46 +5,42 @@
|
|||||||
@section('viewJsName', 'quantityunits')
|
@section('viewJsName', 'quantityunits')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2">
|
<h1 class="page-header">
|
||||||
|
@yield('title')
|
||||||
<h1 class="page-header">
|
<a class="btn btn-default" href="{{ $U('/quantityunit/new') }}" role="button">
|
||||||
@yield('title')
|
<i class="fa fa-plus"></i> {{ $L('Add') }}
|
||||||
<a class="btn btn-default" href="/quantityunit/new" role="button">
|
</a>
|
||||||
<i class="fa fa-plus"></i> Add
|
</h1>
|
||||||
</a>
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table id="quantityunits-table" class="table table-striped">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>#</th>
|
|
||||||
<th>{{ $L('Name') }}</th>
|
|
||||||
<th>{{ $L('Description') }}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@foreach($quantityunits as $quantityunit)
|
|
||||||
<tr>
|
|
||||||
<td class="fit-content">
|
|
||||||
<a class="btn btn-info" href="/quantityunit/{{ $quantityunit->id }}" role="button">
|
|
||||||
<i class="fa fa-pencil"></i>
|
|
||||||
</a>
|
|
||||||
<a class="btn btn-danger quantityunit-delete-button" href="#" role="button" data-quantityunit-id="{{ $quantityunit->id }}" data-quantityunit-name="{{ $quantityunit->name }}">
|
|
||||||
<i class="fa fa-trash"></i>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ $quantityunit->name }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ $quantityunit->description }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@endforeach
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="quantityunits-table" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>{{ $L('Name') }}</th>
|
||||||
|
<th>{{ $L('Description') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($quantityunits as $quantityunit)
|
||||||
|
<tr>
|
||||||
|
<td class="fit-content">
|
||||||
|
<a class="btn btn-info" href="{{ $U('/quantityunit/') }}{{ $quantityunit->id }}" role="button">
|
||||||
|
<i class="fa fa-pencil"></i>
|
||||||
|
</a>
|
||||||
|
<a class="btn btn-danger quantityunit-delete-button" href="#" role="button" data-quantityunit-id="{{ $quantityunit->id }}" data-quantityunit-name="{{ $quantityunit->name }}">
|
||||||
|
<i class="fa fa-trash"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $quantityunit->name }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ $quantityunit->description }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@stop
|
@stop
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user