Implement async loading of products, add TODOs

This commit is contained in:
Alexander Sagen 2022-03-12 21:50:51 +01:00
parent 7532626123
commit 94b6bf8eef
No known key found for this signature in database
GPG Key ID: C1B6BD677756F470
78 changed files with 9748 additions and 11044 deletions

View File

@ -6,7 +6,7 @@ use LessQL\Result;
class BaseApiController extends BaseController
{
const PATTERN_FIELD = '[A-Za-z_][A-Za-z0-9_]+';
const PATTERN_FIELD = '[A-Za-z_][A-Za-z0-9_\.]+';
const PATTERN_OPERATOR = '!?(=|~|<|>|(>=)|(<=)|(§))';
@ -16,8 +16,7 @@ class BaseApiController extends BaseController
protected function ApiResponse(\Psr\Http\Message\ResponseInterface $response, $data, $cache = false)
{
if ($cache)
{
if ($cache) {
$response = $response->withHeader('Cache-Control', 'max-age=2592000');
}
@ -39,38 +38,93 @@ class BaseApiController extends BaseController
public function FilteredApiResponse(\Psr\Http\Message\ResponseInterface $response, Result $data, array $query)
{
$data = $this->queryData($data, $query);
// get total row count
$response = $response->withHeader('x-rowcount-total', $data->count());
// apply filter, get filtered row count
$data = $this->applyQuery($data, $query);
$response = $response->withHeader('x-rowcount-filtered', $data->count());
// apply limit/order
$data = $this->applyOrder($data, $query);
$data = $this->applyLimit($data, $query);
return $this->ApiResponse($response, $data);
}
protected function queryData(Result $data, array $query)
protected function applyQuery(Result $data, array $query): Result
{
if (isset($query['query']))
{
if (isset($query['query'])) {
$data = $this->filter($data, $query['query']);
}
if (isset($query['limit']))
{
if (isset($query['search'])) {
// get list of fields from table
$stmt = $this->getDatabase()->prepare('SELECT `sql` FROM sqlite_master WHERE `name` = ? LIMIT 1');
$stmt->execute([$data->getTable()]);
$sql = $stmt->fetchColumn();
$sql = substr($sql, strpos($sql, '(') + 1);
$sql = substr($sql, 0, strrpos($sql, ')'));
$sql = trim($sql);
while (preg_match('/\(.*?\)/', $sql) === 1) {
$sql = preg_replace('/\([^\(\)]*\)/', '', $sql);
}
$fields = array_map(function ($field) {
preg_match('/\s*([^\s]+)/', $field, $matches);
return $matches[1];
}, explode(',', $sql));
$join_info = match ($data->getTable()) {
'products' => [
'product_group_id' => ['field' => 'name', 'table' => 'product_groups'],
'location_id' => ['field' => 'name', 'table' => 'locations'],
'shopping_location_id' => ['field' => 'name', 'table' => 'shopping_locations'],
'qu_id_purchase' => ['field' => 'name', 'table' => 'quantity_units'],
'qu_id_stock' => ['field' => 'name', 'table' => 'quantity_units'],
'parent_product_id' => ['field' => 'name', 'table' => 'products'],
],
default => [],
};
// create search query that matches any field
$fields_query = implode(' OR ', array_map(function ($field) use ($join_info) {
$field_escaped = '`' . str_replace('`', '``', $field) . '`';
if (array_key_exists($field, $join_info)) {
$table_escaped = '`' . str_replace('`', '``', $join_info[$field]['table']) . '`';
return $field_escaped . ' IN(SELECT id FROM ' . $table_escaped . ' WHERE `' . str_replace('`', '``', $join_info[$field]['field']) . '` LIKE ? ESCAPE \'\\\')';
}
return $field_escaped . ' LIKE ? ESCAPE \'\\\'';
}, $fields));
// apply search query
$data = $data->where($fields_query, array_fill(0, count($fields), '%' . str_replace(['\\', '%', '_', '*'], ['\\\\', '\\%', '\\_', '%'], $query['search']) . '%'));
}
return $data;
}
protected function applyLimit(Result $data, array $query): Result
{
if (isset($query['limit'])) {
$data = $data->limit(intval($query['limit']), intval($query['offset'] ?? 0));
}
if (isset($query['order']))
{
$parts = explode(':', $query['order']);
return $data;
}
if (count($parts) == 1)
{
$data = $data->orderBy($parts[0]);
}
else
{
if ($parts[1] != 'asc' && $parts[1] != 'desc')
{
throw new \Exception('Invalid sort order ' . $parts[1]);
protected function applyOrder(Result $data, array $query): Result
{
if (isset($query['order'])) {
$parts = explode(',', $query['order']);
foreach ($parts as $part) {
$col_dir = explode(':', $part, 2);
if (count($col_dir) == 1) {
$data = $data->orderBy($col_dir[0]);
} else {
if ($col_dir[1] != 'asc' && $col_dir[1] != 'desc' && $col_dir[1] != 'collate nocase') {
throw new \Exception('Invalid sort order ' . $col_dir[1]);
}
$data = $data->orderBy($col_dir[0], $col_dir[1]);
}
$data = $data->orderBy($parts[0], $parts[1]);
}
}
@ -79,58 +133,73 @@ class BaseApiController extends BaseController
protected function filter(Result $data, array $query): Result
{
foreach ($query as $q)
{
foreach ($query as $q) {
$matches = [];
preg_match(
'/(?P<field>' . self::PATTERN_FIELD . ')'
. '(?P<op>' . self::PATTERN_OPERATOR . ')'
. '(?P<value>' . self::PATTERN_VALUE . ')/u',
. '(?P<op>' . self::PATTERN_OPERATOR . ')'
. '(?P<value>' . self::PATTERN_VALUE . ')/u',
$q,
$matches
);
if (!array_key_exists('field', $matches) || !array_key_exists('op', $matches) || !array_key_exists('value', $matches))
{
if (!array_key_exists('field', $matches) || !array_key_exists('op', $matches) || !array_key_exists('value', $matches) || !in_array($matches['op'], ['=', '!=', '~', '!~', '<', '>', '>=', '<=', '§'], true)) {
throw new \Exception('Invalid query');
}
list('field' => $field, 'op' => $op, 'value' => $value) = $matches;
$sqlOrNull = '';
if (strtolower($matches['value']) == 'null')
{
$sqlOrNull = ' OR ' . $matches['field'] . ' IS NULL';
$params = match ($op) {
'=' => [$value],
'!=' => [$value],
'~' => ['%' . $value . '%'],
'!~' => ['%' . $value . '%'],
'<' => [$value],
'>' => [$value],
'>=' => [$value],
'<=' => [$value],
'§' => [$value],
default => [],
};
$where_prefix = '';
$where_suffix = '';
if (strpos($field, '.') !== false) {
list($join, $field) = explode('.', $field, 2);
$join_info = match ($data->getTable()) {
'products' => [
'product_group' => ['id_field' => 'product_group_id', 'table' => 'product_groups'],
'location' => ['id_field' => 'location_id', 'table' => 'locations'],
'shopping_location' => ['id_field' => 'shopping_location_id', 'table' => 'shopping_locations'],
'qu_purchase' => ['id_field' => 'qu_id_purchase', 'table' => 'quantity_units'],
'qu_stock' => ['id_field' => 'qu_id_stock', 'table' => 'quantity_units'],
'parent_product' => ['id_field' => 'parent_product_id', 'table' => 'products'],
],
default => [],
};
if (!array_key_exists($join, $join_info)) {
throw new \Exception('Invalid query');
}
$field_escaped = '`' . str_replace('`', '``', $join_info[$join]['id_field']) . '`';
$table_escaped = '`' . str_replace('`', '``', $join_info[$join]['table']) . '`';
$where_prefix = $field_escaped . ' IN(SELECT id FROM ' . $table_escaped . ' WHERE ';
$where_suffix = ')';
}
switch ($matches['op']) {
case '=':
$data = $data->where($matches['field'] . ' = ?' . $sqlOrNull, $matches['value']);
break;
case '!=':
$data = $data->where($matches['field'] . ' != ?' . $sqlOrNull, $matches['value']);
break;
case '~':
$data = $data->where($matches['field'] . ' LIKE ?', '%' . $matches['value'] . '%');
break;
case '!~':
$data = $data->where($matches['field'] . ' NOT LIKE ?', '%' . $matches['value'] . '%');
break;
case '<':
$data = $data->where($matches['field'] . ' < ?', $matches['value']);
break;
case '>':
$data = $data->where($matches['field'] . ' > ?', $matches['value']);
break;
case '>=':
$data = $data->where($matches['field'] . ' >= ?', $matches['value']);
break;
case '<=':
$data = $data->where($matches['field'] . ' <= ?', $matches['value']);
break;
case '§':
$data = $data->where($matches['field'] . ' REGEXP ?', $matches['value']);
break;
$field_escaped = '`' . str_replace('`', '``', $field) . '`';
$where = match ($op) {
'=' => $field_escaped . ' = ?' . (strtolower($value) === 'null' ? ' OR ' . $field_escaped . ' IS NULL' : ''),
'!=' => $field_escaped . ' != ?' . (strtolower($value) === 'null' ? ' OR ' . $field_escaped . ' IS NULL' : ''),
'~' => $field_escaped . ' LIKE ?',
'!~' => $field_escaped . ' NOT LIKE ?',
'<' => $field_escaped . ' < ?',
'>' => $field_escaped . ' > ?',
'>=' => $field_escaped . ' >= ?',
'<=' => $field_escaped . ' <= ?',
'§' => $field_escaped . ' REGEXP ?',
default => '',
};
}
$data = $data->where($where_prefix . $where . $where_suffix, $params);
}
return $data;
@ -138,8 +207,7 @@ class BaseApiController extends BaseController
protected function getOpenApispec()
{
if ($this->OpenApiSpec == null)
{
if ($this->OpenApiSpec == null) {
$this->OpenApiSpec = json_decode(file_get_contents(__DIR__ . '/../grocy.openapi.json'));
}

View File

@ -13,19 +13,15 @@ class ChoresController extends BaseController
$usersService = $this->getUsersService();
$users = $usersService->GetUsersAsDto();
if ($args['choreId'] == 'new')
{
if ($args['choreId'] == 'new') {
return $this->renderPage($response, 'choreform', [
'periodTypes' => GetClassConstants('\Grocy\Services\ChoresService', 'CHORE_PERIOD_TYPE_'),
'mode' => 'create',
'userfields' => $this->getUserfieldsService()->GetFields('chores'),
'assignmentTypes' => GetClassConstants('\Grocy\Services\ChoresService', 'CHORE_ASSIGNMENT_TYPE_'),
'users' => $users,
'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE')
]);
}
else
{
} else {
return $this->renderPage($response, 'choreform', [
'chore' => $this->getDatabase()->chores($args['choreId']),
'periodTypes' => GetClassConstants('\Grocy\Services\ChoresService', 'CHORE_PERIOD_TYPE_'),
@ -33,19 +29,15 @@ class ChoresController extends BaseController
'userfields' => $this->getUserfieldsService()->GetFields('chores'),
'assignmentTypes' => GetClassConstants('\Grocy\Services\ChoresService', 'CHORE_ASSIGNMENT_TYPE_'),
'users' => $users,
'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE')
]);
}
}
public function ChoresList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
if (isset($request->getQueryParams()['include_disabled']))
{
if (isset($request->getQueryParams()['include_disabled'])) {
$chores = $this->getDatabase()->chores()->orderBy('name', 'COLLATE NOCASE');
}
else
{
} else {
$chores = $this->getDatabase()->chores()->where('active = 1')->orderBy('name', 'COLLATE NOCASE');
}
@ -63,19 +55,15 @@ class ChoresController extends BaseController
public function Journal(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
if (isset($request->getQueryParams()['months']) && filter_var($request->getQueryParams()['months'], FILTER_VALIDATE_INT) !== false)
{
if (isset($request->getQueryParams()['months']) && filter_var($request->getQueryParams()['months'], FILTER_VALIDATE_INT) !== false) {
$months = $request->getQueryParams()['months'];
$where = "tracked_time > DATE(DATE('now', 'localtime'), '-$months months')";
}
else
{
} else {
// Default 1 year
$where = "tracked_time > DATE(DATE('now', 'localtime'), '-12 months')";
}
if (isset($request->getQueryParams()['chore']) && filter_var($request->getQueryParams()['chore'], FILTER_VALIDATE_INT) !== false)
{
if (isset($request->getQueryParams()['chore']) && filter_var($request->getQueryParams()['chore'], FILTER_VALIDATE_INT) !== false) {
$choreId = $request->getQueryParams()['chore'];
$where .= " AND chore_id = $choreId";
}
@ -96,20 +84,13 @@ class ChoresController extends BaseController
$chores = $this->getDatabase()->chores()->orderBy('name', 'COLLATE NOCASE');
$currentChores = $this->getChoresService()->GetCurrent();
foreach ($currentChores as $currentChore)
{
if (FindObjectInArrayByPropertyValue($chores, 'id', $currentChore->chore_id)->period_type !== \Grocy\Services\ChoresService::CHORE_PERIOD_TYPE_MANUALLY)
{
if ($currentChore->next_estimated_execution_time < date('Y-m-d H:i:s'))
{
foreach ($currentChores as $currentChore) {
if (FindObjectInArrayByPropertyValue($chores, 'id', $currentChore->chore_id)->period_type !== \Grocy\Services\ChoresService::CHORE_PERIOD_TYPE_MANUALLY) {
if ($currentChore->next_estimated_execution_time < date('Y-m-d H:i:s')) {
$currentChore->due_type = 'overdue';
}
elseif ($currentChore->next_estimated_execution_time <= date('Y-m-d 23:59:59'))
{
} elseif ($currentChore->next_estimated_execution_time <= date('Y-m-d 23:59:59')) {
$currentChore->due_type = 'duetoday';
}
elseif ($nextXDays > 0 && $currentChore->next_estimated_execution_time <= date('Y-m-d H:i:s', strtotime('+' . $nextXDays . ' days')))
{
} elseif ($nextXDays > 0 && $currentChore->next_estimated_execution_time <= date('Y-m-d H:i:s', strtotime('+' . $nextXDays . ' days'))) {
$currentChore->due_type = 'duesoon';
}
}

View File

@ -3,7 +3,7 @@
namespace Grocy\Controllers;
use Grocy\Controllers\Users\User;
use Slim\Exception\HttpBadRequestException;
use LessQL\Row;
class GenericEntityApiController extends BaseApiController
{
@ -11,37 +11,28 @@ class GenericEntityApiController extends BaseApiController
{
User::checkPermission($request, User::PERMISSION_MASTER_DATA_EDIT);
if ($this->IsValidExposedEntity($args['entity']) && !$this->IsEntityWithNoEdit($args['entity']))
{
if ($this->IsEntityWithEditRequiresAdmin($args['entity']))
{
if ($this->IsValidExposedEntity($args['entity']) && !$this->IsEntityWithNoEdit($args['entity'])) {
if ($this->IsEntityWithEditRequiresAdmin($args['entity'])) {
User::checkPermission($request, User::PERMISSION_ADMIN);
}
$requestBody = $this->GetParsedAndFilteredRequestBody($request);
try
{
if ($requestBody === null)
{
try {
if ($requestBody === null) {
throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)');
}
$newRow = $this->getDatabase()->{$args['entity']}()->createRow($requestBody);
$newRow->save();
$success = $newRow->isClean();
return $this->ApiResponse($response, [
'created_object_id' => $this->getDatabase()->lastInsertId()
]);
}
catch (\Exception $ex)
{
} catch (\Exception $ex) {
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
else
{
} else {
return $this->GenericErrorResponse($response, 'Entity does not exist or is not exposed');
}
}
@ -50,26 +41,20 @@ class GenericEntityApiController extends BaseApiController
{
User::checkPermission($request, User::PERMISSION_MASTER_DATA_EDIT);
if ($this->IsValidExposedEntity($args['entity']) && !$this->IsEntityWithNoDelete($args['entity']))
{
if ($this->IsEntityWithEditRequiresAdmin($args['entity']))
{
if ($this->IsValidExposedEntity($args['entity']) && !$this->IsEntityWithNoDelete($args['entity'])) {
if ($this->IsEntityWithEditRequiresAdmin($args['entity'])) {
User::checkPermission($request, User::PERMISSION_ADMIN);
}
$row = $this->getDatabase()->{$args['entity']}($args['objectId']);
if ($row == null)
{
if ($row == null) {
return $this->GenericErrorResponse($response, 'Object not found', 400);
}
$row->delete();
$success = $row->isClean();
return $this->EmptyApiResponse($response);
}
else
{
} else {
return $this->GenericErrorResponse($response, 'Invalid entity');
}
}
@ -78,102 +63,78 @@ class GenericEntityApiController extends BaseApiController
{
User::checkPermission($request, User::PERMISSION_MASTER_DATA_EDIT);
if ($this->IsValidExposedEntity($args['entity']) && !$this->IsEntityWithNoEdit($args['entity']))
{
if ($this->IsEntityWithEditRequiresAdmin($args['entity']))
{
if ($this->IsValidExposedEntity($args['entity']) && !$this->IsEntityWithNoEdit($args['entity'])) {
if ($this->IsEntityWithEditRequiresAdmin($args['entity'])) {
User::checkPermission($request, User::PERMISSION_ADMIN);
}
$requestBody = $this->GetParsedAndFilteredRequestBody($request);
try
{
if ($requestBody === null)
{
try {
if ($requestBody === null) {
throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)');
}
$row = $this->getDatabase()->{$args['entity']}($args['objectId']);
if ($row == null)
{
if ($row == null) {
return $this->GenericErrorResponse($response, 'Object not found', 400);
}
$row->update($requestBody);
$success = $row->isClean();
return $this->EmptyApiResponse($response);
}
catch (\Exception $ex)
{
} catch (\Exception $ex) {
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
else
{
} else {
return $this->GenericErrorResponse($response, 'Entity does not exist or is not exposed');
}
}
public function GetObject(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
if ($this->IsValidExposedEntity($args['entity']) && !$this->IsEntityWithNoListing($args['entity']))
{
$userfields = $this->getUserfieldsService()->GetValues($args['entity'], $args['objectId']);
if (count($userfields) === 0)
{
$userfields = null;
}
if ($this->IsValidExposedEntity($args['entity']) && !$this->IsEntityWithNoListing($args['entity'])) {
$object = $this->getDatabase()->{$args['entity']}($args['objectId']);
if ($object == null)
{
if ($object == null) {
return $this->GenericErrorResponse($response, 'Object not found', 404);
}
$object['userfields'] = $userfields;
$this->addUserfieldsAndJoinsToRow($object, $args);
return $this->ApiResponse($response, $object);
}
else
{
} else {
return $this->GenericErrorResponse($response, 'Entity does not exist or is not exposed');
}
}
public function GetObjects(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
if (!$this->IsValidExposedEntity($args['entity']) || $this->IsEntityWithNoListing($args['entity']))
{
if (!$this->IsValidExposedEntity($args['entity']) || $this->IsEntityWithNoListing($args['entity'])) {
return $this->GenericErrorResponse($response, 'Entity does not exist or is not exposed');
}
$objects = $this->queryData($this->getDatabase()->{$args['entity']}(), $request->getQueryParams());
$userfields = $this->getUserfieldsService()->GetFields($args['entity']);
$query = $request->getQueryParams();
if (count($userfields) > 0)
{
$allUserfieldValues = $this->getUserfieldsService()->GetAllValues($args['entity']);
// get result and total row count
$objects = $this->getDatabase()->{$args['entity']}();
$response = $response->withHeader('x-rowcount-total', $objects->count());
foreach ($objects as $object)
{
$userfieldKeyValuePairs = null;
foreach ($userfields as $userfield)
{
$value = FindObjectInArrayByPropertyValue(FindAllObjectsInArrayByPropertyValue($allUserfieldValues, 'object_id', $object->id), 'name', $userfield->name);
if ($value)
{
$userfieldKeyValuePairs[$userfield->name] = $value->value;
}
else
{
$userfieldKeyValuePairs[$userfield->name] = null;
}
}
// apply filter, get filtered row count
$objects = $this->applyQuery($objects, $query);
$response = $response->withHeader('x-rowcount-filtered', $objects->count());
$object->userfields = $userfieldKeyValuePairs;
}
// apply limit/order
$objects = $this->applyLimit($objects, $query);
$objects = $this->applyOrder($objects, $query);
// add entity-specific queries
if ($args['entity'] === 'products' && isset($query['only_in_stock'])) {
$objects = $objects->where('id IN (SELECT product_id from stock_current WHERE amount_aggregated > 0)');
}
// add userfields and joins to objects
foreach ($objects as $object) {
$this->addUserfieldsAndJoinsToRow($object, $args);
}
return $this->ApiResponse($response, $objects);
@ -181,12 +142,9 @@ class GenericEntityApiController extends BaseApiController
public function GetUserfields(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
try
{
try {
return $this->ApiResponse($response, $this->getUserfieldsService()->GetValues($args['entity'], $args['objectId']));
}
catch (\Exception $ex)
{
} catch (\Exception $ex) {
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
@ -197,22 +155,40 @@ class GenericEntityApiController extends BaseApiController
$requestBody = $this->GetParsedAndFilteredRequestBody($request);
try
{
if ($requestBody === null)
{
try {
if ($requestBody === null) {
throw new \Exception('Request body could not be parsed (probably invalid JSON format or missing/wrong Content-Type header)');
}
$this->getUserfieldsService()->SetValues($args['entity'], $args['objectId'], $requestBody);
return $this->EmptyApiResponse($response);
}
catch (\Exception $ex)
{
} catch (\Exception $ex) {
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}
private function addUserfieldsAndJoinsToRow(Row $object, array $args)
{
// add userfields
$userfields = $this->getUserfieldsService()->GetValues($args['entity'], $object->id);
if (count($userfields) === 0) {
$userfields = null;
}
$object->userfields = $userfields;
// add entity-specific joins
if ($args['entity'] === 'products') {
$object->product_group = $object->product_group_id !== null ? $this->getDatabase()->product_groups($object->product_group_id) : null;
$object->location = $object->location_id !== null ? $this->getDatabase()->locations($object->location_id) : null;
$object->shopping_location = $object->shopping_location_id !== null ? $this->getDatabase()->shopping_locations($object->shopping_location_id) : null;
$object->qu_purchase = $object->qu_id_purchase !== null ? $this->getDatabase()->quantity_units($object->qu_id_purchase) : null;
$object->qu_stock = $object->qu_id_stock !== null ? $this->getDatabase()->quantity_units($object->qu_id_stock) : null;
$object->parent_product = $object->parent_product_id !== null ? $this->getDatabase()->products($object->parent_product_id) : null;
}
return $object;
}
private function IsEntityWithEditRequiresAdmin($entity)
{
return in_array($entity, $this->getOpenApiSpec()->components->schemas->ExposedEntityEditRequiresAdmin->enum);

View File

@ -12,14 +12,12 @@ class RecipesController extends BaseController
public function MealPlan(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
$start = date('Y-m-d');
if (isset($request->getQueryParams()['start']) && IsIsoDate($request->getQueryParams()['start']))
{
if (isset($request->getQueryParams()['start']) && IsIsoDate($request->getQueryParams()['start'])) {
$start = $request->getQueryParams()['start'];
}
$days = 6;
if (isset($request->getQueryParams()['days']) && filter_var($request->getQueryParams()['days'], FILTER_VALIDATE_INT) !== false)
{
if (isset($request->getQueryParams()['days']) && filter_var($request->getQueryParams()['days'], FILTER_VALIDATE_INT) !== false) {
$days = $request->getQueryParams()['days'];
}
@ -27,19 +25,16 @@ class RecipesController extends BaseController
$recipes = $this->getDatabase()->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->fetchAll();
$events = [];
foreach ($this->getDatabase()->meal_plan()->where($mealPlanWhereTimespan) as $mealPlanEntry)
{
foreach ($this->getDatabase()->meal_plan()->where($mealPlanWhereTimespan) as $mealPlanEntry) {
$recipe = FindObjectInArrayByPropertyValue($recipes, 'id', $mealPlanEntry['recipe_id']);
$title = '';
if ($recipe !== null)
{
if ($recipe !== null) {
$title = $recipe->name;
}
$productDetails = null;
if ($mealPlanEntry['product_id'] !== null)
{
if ($mealPlanEntry['product_id'] !== null) {
$productDetails = $this->getStockService()->GetProductDetails($mealPlanEntry['product_id']);
}
@ -60,7 +55,6 @@ class RecipesController extends BaseController
'recipes' => $recipes,
'internalRecipes' => $this->getDatabase()->recipes()->where("id IN (SELECT recipe_id FROM meal_plan_internal_recipe_relation WHERE $mealPlanWhereTimespan)")->fetchAll(),
'recipesResolved' => $this->getRecipesService()->GetRecipesResolved("recipe_id IN (SELECT recipe_id FROM meal_plan_internal_recipe_relation WHERE $mealPlanWhereTimespan)"),
'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved(),
'mealplanSections' => $this->getDatabase()->meal_plan_sections()->orderBy('sort_number'),
@ -74,14 +68,10 @@ class RecipesController extends BaseController
$recipesResolved = $this->getRecipesService()->GetRecipesResolved('recipe_id > 0');
$selectedRecipe = null;
if (isset($request->getQueryParams()['recipe']))
{
if (isset($request->getQueryParams()['recipe'])) {
$selectedRecipe = $this->getDatabase()->recipes($request->getQueryParams()['recipe']);
}
else
{
foreach ($recipes as $recipe)
{
} else {
foreach ($recipes as $recipe) {
$selectedRecipe = $recipe;
break;
}
@ -89,8 +79,7 @@ class RecipesController extends BaseController
$totalCosts = null;
$totalCalories = null;
if ($selectedRecipe)
{
if ($selectedRecipe) {
$totalCosts = FindObjectInArrayByPropertyValue($recipesResolved, 'recipe_id', $selectedRecipe->id)->costs;
$totalCalories = FindObjectInArrayByPropertyValue($recipesResolved, 'recipe_id', $selectedRecipe->id)->calories;
}
@ -109,27 +98,22 @@ class RecipesController extends BaseController
'selectedRecipeTotalCalories' => $totalCalories
];
if ($selectedRecipe)
{
if ($selectedRecipe) {
$selectedRecipeSubRecipes = $this->getDatabase()->recipes()->where('id IN (SELECT includes_recipe_id FROM recipes_nestings_resolved WHERE recipe_id = :1 AND includes_recipe_id != :1)', $selectedRecipe->id)->orderBy('name', 'COLLATE NOCASE')->fetchAll();
$includedRecipeIdsAbsolute = [];
$includedRecipeIdsAbsolute[] = $selectedRecipe->id;
foreach ($selectedRecipeSubRecipes as $subRecipe)
{
foreach ($selectedRecipeSubRecipes as $subRecipe) {
$includedRecipeIdsAbsolute[] = $subRecipe->id;
}
// TODO: Why not directly use recipes_pos_resolved for all recipe positions here (parent and child)?
// This view already correctly recolves child recipe amounts...
$allRecipePositions = [];
foreach ($includedRecipeIdsAbsolute as $id)
{
foreach ($includedRecipeIdsAbsolute as $id) {
$allRecipePositions[$id] = $this->getDatabase()->recipes_pos_resolved()->where('recipe_id = :1 AND is_nested_recipe_pos = 0', $id)->orderBy('ingredient_group', 'ASC', 'product_group', 'ASC');
foreach ($allRecipePositions[$id] as $pos)
{
if ($id != $selectedRecipe->id)
{
foreach ($allRecipePositions[$id] as $pos) {
if ($id != $selectedRecipe->id) {
$pos2 = $this->getDatabase()->recipes_pos_resolved()->where('recipe_id = :1 AND recipe_pos_id = :2 AND is_nested_recipe_pos = 1', $selectedRecipe->id, $pos->recipe_pos_id)->fetch();
$pos->recipe_amount = $pos2->recipe_amount;
$pos->missing_amount = $pos2->missing_amount;
@ -153,6 +137,7 @@ class RecipesController extends BaseController
'recipe' => $this->getDatabase()->recipes($recipeId),
'recipePositions' => $this->getDatabase()->recipes_pos()->where('recipe_id', $recipeId),
'mode' => $recipeId == 'new' ? 'create' : 'edit',
// TODO: remove 'products' after converting DataTable
'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE'),
'quantityunits' => $this->getDatabase()->quantity_units(),
'recipes' => $this->getDatabase()->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->orderBy('name', 'COLLATE NOCASE'),
@ -164,24 +149,21 @@ class RecipesController extends BaseController
public function RecipePosEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
if ($args['recipePosId'] == 'new')
{
if ($args['recipePosId'] == 'new') {
return $this->renderPage($response, 'recipeposform', [
'mode' => 'create',
'recipe' => $this->getDatabase()->recipes($args['recipeId']),
'recipePos' => new \stdClass(),
'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'),
'recipePosId' => $args['recipePosId'],
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved()
]);
}
else
{
} else {
return $this->renderPage($response, 'recipeposform', [
'mode' => 'edit',
'recipe' => $this->getDatabase()->recipes($args['recipeId']),
'recipePos' => $this->getDatabase()->recipes_pos($args['recipePosId']),
'products' => $this->getDatabase()->products()->orderBy('name', 'COLLATE NOCASE'),
'recipePosId' => $args['recipePosId'],
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved()
]);
@ -195,14 +177,11 @@ class RecipesController extends BaseController
public function MealPlanSectionEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
if ($args['sectionId'] == 'new')
{
if ($args['sectionId'] == 'new') {
return $this->renderPage($response, 'mealplansectionform', [
'mode' => 'create'
]);
}
else
{
} else {
return $this->renderPage($response, 'mealplansectionform', [
'mealplanSection' => $this->getDatabase()->meal_plan_sections($args['sectionId']),
'mode' => 'edit'

View File

@ -12,8 +12,6 @@ class StockController extends BaseController
public function Consume(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
return $this->renderPage($response, 'consume', [
'products' => $this->getDatabase()->products()->where('active = 1')->where('id IN (SELECT product_id from stock_current WHERE amount_aggregated > 0)')->orderBy('name'),
'barcodes' => $this->getDatabase()->product_barcodes_comma_separated(),
'recipes' => $this->getDatabase()->recipes()->where('type', RecipesService::RECIPE_TYPE_NORMAL)->orderBy('name', 'COLLATE NOCASE'),
'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
@ -24,8 +22,6 @@ class StockController extends BaseController
public function Inventory(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
return $this->renderPage($response, 'inventory', [
'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'),
'barcodes' => $this->getDatabase()->product_barcodes_comma_separated(),
'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name', 'COLLATE NOCASE'),
'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
@ -35,19 +31,15 @@ class StockController extends BaseController
public function Journal(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
if (isset($request->getQueryParams()['months']) && filter_var($request->getQueryParams()['months'], FILTER_VALIDATE_INT) !== false)
{
if (isset($request->getQueryParams()['months']) && filter_var($request->getQueryParams()['months'], FILTER_VALIDATE_INT) !== false) {
$months = $request->getQueryParams()['months'];
$where = "row_created_timestamp > DATE(DATE('now', 'localtime'), '-$months months')";
}
else
{
} else {
// Default 6 months
$where = "row_created_timestamp > DATE(DATE('now', 'localtime'), '-6 months')";
}
if (isset($request->getQueryParams()['product']) && filter_var($request->getQueryParams()['product'], FILTER_VALIDATE_INT) !== false)
{
if (isset($request->getQueryParams()['product']) && filter_var($request->getQueryParams()['product'], FILTER_VALIDATE_INT) !== false) {
$productId = $request->getQueryParams()['product'];
$where .= " AND product_id = $productId";
}
@ -56,7 +48,6 @@ class StockController extends BaseController
return $this->renderPage($response, 'stockjournal', [
'stockLog' => $this->getDatabase()->uihelper_stock_journal()->where($where)->orderBy('row_created_timestamp', 'DESC'),
'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'),
'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
'users' => $usersService->GetUsersAsDto(),
'transactionTypes' => GetClassConstants('\Grocy\Services\StockService', 'TRANSACTION_TYPE_')
@ -75,15 +66,12 @@ class StockController extends BaseController
public function LocationEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
if ($args['locationId'] == 'new')
{
if ($args['locationId'] == 'new') {
return $this->renderPage($response, 'locationform', [
'mode' => 'create',
'userfields' => $this->getUserfieldsService()->GetFields('locations')
]);
}
else
{
} else {
return $this->renderPage($response, 'locationform', [
'location' => $this->getDatabase()->locations($args['locationId']),
'mode' => 'edit',
@ -121,13 +109,11 @@ class StockController extends BaseController
{
$product = null;
if (isset($request->getQueryParams()['product']))
{
if (isset($request->getQueryParams()['product'])) {
$product = $this->getDatabase()->products($request->getQueryParams()['product']);
}
if ($args['productBarcodeId'] == 'new')
{
if ($args['productBarcodeId'] == 'new') {
return $this->renderPage($response, 'productbarcodeform', [
'mode' => 'create',
'barcodes' => $this->getDatabase()->product_barcodes()->orderBy('barcode'),
@ -137,9 +123,7 @@ class StockController extends BaseController
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved(),
'userfields' => $this->getUserfieldsService()->GetFields('product_barcodes')
]);
}
else
{
} else {
return $this->renderPage($response, 'productbarcodeform', [
'mode' => 'edit',
'barcode' => $this->getDatabase()->product_barcodes($args['productBarcodeId']),
@ -154,8 +138,7 @@ class StockController extends BaseController
public function ProductEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
if ($args['productId'] == 'new')
{
if ($args['productId'] == 'new') {
return $this->renderPage($response, 'productform', [
'locations' => $this->getDatabase()->locations()->orderBy('name'),
'barcodes' => $this->getDatabase()->product_barcodes()->orderBy('barcode'),
@ -163,13 +146,10 @@ class StockController extends BaseController
'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name', 'COLLATE NOCASE'),
'productgroups' => $this->getDatabase()->product_groups()->orderBy('name', 'COLLATE NOCASE'),
'userfields' => $this->getUserfieldsService()->GetFields('products'),
'products' => $this->getDatabase()->products()->where('parent_product_id IS NULL and active = 1')->orderBy('name', 'COLLATE NOCASE'),
'isSubProductOfOthers' => false,
'mode' => 'create'
]);
}
else
{
} else {
$product = $this->getDatabase()->products($args['productId']);
return $this->renderPage($response, 'productform', [
@ -180,7 +160,6 @@ class StockController extends BaseController
'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name', 'COLLATE NOCASE'),
'productgroups' => $this->getDatabase()->product_groups()->orderBy('name', 'COLLATE NOCASE'),
'userfields' => $this->getUserfieldsService()->GetFields('products'),
'products' => $this->getDatabase()->products()->where('id != :1 AND parent_product_id IS NULL and active = 1', $product->id)->orderBy('name', 'COLLATE NOCASE'),
'isSubProductOfOthers' => $this->getDatabase()->products()->where('parent_product_id = :1', $product->id)->count() !== 0,
'mode' => 'edit',
'quConversions' => $this->getDatabase()->quantity_unit_conversions(),
@ -198,15 +177,12 @@ class StockController extends BaseController
public function ProductGroupEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
if ($args['productGroupId'] == 'new')
{
if ($args['productGroupId'] == 'new') {
return $this->renderPage($response, 'productgroupform', [
'mode' => 'create',
'userfields' => $this->getUserfieldsService()->GetFields('product_groups')
]);
}
else
{
} else {
return $this->renderPage($response, 'productgroupform', [
'group' => $this->getDatabase()->product_groups($args['productGroupId']),
'mode' => 'edit',
@ -227,21 +203,21 @@ class StockController extends BaseController
public function ProductsList(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
$products = $this->getDatabase()->products();
if (!isset($request->getQueryParams()['include_disabled']))
{
$products = $products->where('active = 1');
}
// $products = $this->getDatabase()->products();
// if (!isset($request->getQueryParams()['include_disabled']))
// {
// $products = $products->where('active = 1');
// }
if (isset($request->getQueryParams()['only_in_stock']))
{
$products = $products->where('id IN (SELECT product_id from stock_current WHERE amount_aggregated > 0)');
}
// if (isset($request->getQueryParams()['only_in_stock']))
// {
// $products = $products->where('id IN (SELECT product_id from stock_current WHERE amount_aggregated > 0)');
// }
$products = $products->orderBy('name', 'COLLATE NOCASE');
// $products = $products->orderBy('name', 'COLLATE NOCASE');
return $this->renderPage($response, 'products', [
'products' => $products,
// 'products' => $products,
'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'productGroups' => $this->getDatabase()->product_groups()->orderBy('name', 'COLLATE NOCASE'),
@ -254,8 +230,6 @@ class StockController extends BaseController
public function Purchase(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
return $this->renderPage($response, 'purchase', [
'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'),
'barcodes' => $this->getDatabase()->product_barcodes_comma_separated(),
'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name', 'COLLATE NOCASE'),
'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
@ -267,20 +241,17 @@ class StockController extends BaseController
{
$product = null;
if (isset($request->getQueryParams()['product']))
{
if (isset($request->getQueryParams()['product'])) {
$product = $this->getDatabase()->products($request->getQueryParams()['product']);
}
$defaultQuUnit = null;
if (isset($request->getQueryParams()['qu-unit']))
{
if (isset($request->getQueryParams()['qu-unit'])) {
$defaultQuUnit = $this->getDatabase()->quantity_units($request->getQueryParams()['qu-unit']);
}
if ($args['quConversionId'] == 'new')
{
if ($args['quConversionId'] == 'new') {
return $this->renderPage($response, 'quantityunitconversionform', [
'mode' => 'create',
'userfields' => $this->getUserfieldsService()->GetFields('quantity_unit_conversions'),
@ -288,9 +259,7 @@ class StockController extends BaseController
'product' => $product,
'defaultQuUnit' => $defaultQuUnit
]);
}
else
{
} else {
return $this->renderPage($response, 'quantityunitconversionform', [
'quConversion' => $this->getDatabase()->quantity_unit_conversions($args['quConversionId']),
'mode' => 'edit',
@ -304,17 +273,14 @@ class StockController extends BaseController
public function QuantityUnitEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
if ($args['quantityunitId'] == 'new')
{
if ($args['quantityunitId'] == 'new') {
return $this->renderPage($response, 'quantityunitform', [
'mode' => 'create',
'userfields' => $this->getUserfieldsService()->GetFields('quantity_units'),
'pluralCount' => $this->getLocalizationService()->GetPluralCount(),
'pluralRule' => $this->getLocalizationService()->GetPluralDefinition()
]);
}
else
{
} else {
$quantityUnit = $this->getDatabase()->quantity_units($args['quantityunitId']);
return $this->renderPage($response, 'quantityunitform', [
@ -349,8 +315,7 @@ class StockController extends BaseController
{
$listId = 1;
if (isset($request->getQueryParams()['list']))
{
if (isset($request->getQueryParams()['list'])) {
$listId = $request->getQueryParams()['list'];
}
@ -371,15 +336,12 @@ class StockController extends BaseController
public function ShoppingListEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
if ($args['listId'] == 'new')
{
if ($args['listId'] == 'new') {
return $this->renderPage($response, 'shoppinglistform', [
'mode' => 'create',
'userfields' => $this->getUserfieldsService()->GetFields('shopping_lists')
]);
}
else
{
} else {
return $this->renderPage($response, 'shoppinglistform', [
'shoppingList' => $this->getDatabase()->shopping_lists($args['listId']),
'mode' => 'edit',
@ -390,8 +352,7 @@ class StockController extends BaseController
public function ShoppingListItemEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
if ($args['itemId'] == 'new')
{
if ($args['itemId'] == 'new') {
return $this->renderPage($response, 'shoppinglistitemform', [
'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'),
'shoppingLists' => $this->getDatabase()->shopping_lists()->orderBy('name', 'COLLATE NOCASE'),
@ -400,12 +361,9 @@ class StockController extends BaseController
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved(),
'userfields' => $this->getUserfieldsService()->GetFields('shopping_list')
]);
}
else
{
} else {
return $this->renderPage($response, 'shoppinglistitemform', [
'listItem' => $this->getDatabase()->shopping_list($args['itemId']),
'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'),
'shoppingLists' => $this->getDatabase()->shopping_lists()->orderBy('name', 'COLLATE NOCASE'),
'mode' => 'edit',
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
@ -422,15 +380,12 @@ class StockController extends BaseController
public function ShoppingLocationEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
if ($args['shoppingLocationId'] == 'new')
{
if ($args['shoppingLocationId'] == 'new') {
return $this->renderPage($response, 'shoppinglocationform', [
'mode' => 'create',
'userfields' => $this->getUserfieldsService()->GetFields('shopping_locations')
]);
}
else
{
} else {
return $this->renderPage($response, 'shoppinglocationform', [
'shoppinglocation' => $this->getDatabase()->shopping_locations($args['shoppingLocationId']),
'mode' => 'edit',
@ -489,6 +444,7 @@ class StockController extends BaseController
$nextXDays = $usersService->GetUserSettings(GROCY_USER_ID)['stock_due_soon_days'];
return $this->renderPage($response, 'stockentries', [
// TODO: remove 'products' after converting DataTable
'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'),
'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
@ -504,8 +460,6 @@ class StockController extends BaseController
public function Transfer(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
return $this->renderPage($response, 'transfer', [
'products' => $this->getDatabase()->products()->where('active = 1')->where('id IN (SELECT product_id from stock_current WHERE amount_aggregated > 0)')->orderBy('name', 'COLLATE NOCASE'),
'barcodes' => $this->getDatabase()->product_barcodes_comma_separated(),
'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
'quantityUnitConversionsResolved' => $this->getDatabase()->quantity_unit_conversions_resolved()
@ -515,23 +469,19 @@ class StockController extends BaseController
public function JournalSummary(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
$entries = $this->getDatabase()->uihelper_stock_journal_summary();
if (isset($request->getQueryParams()['product_id']))
{
if (isset($request->getQueryParams()['product_id'])) {
$entries = $entries->where('product_id', $request->getQueryParams()['product_id']);
}
if (isset($request->getQueryParams()['user_id']))
{
if (isset($request->getQueryParams()['user_id'])) {
$entries = $entries->where('user_id', $request->getQueryParams()['user_id']);
}
if (isset($request->getQueryParams()['transaction_type']))
{
if (isset($request->getQueryParams()['transaction_type'])) {
$entries = $entries->where('transaction_type', $request->getQueryParams()['transaction_type']);
}
$usersService = $this->getUsersService();
return $this->renderPage($response, 'stockjournalsummary', [
'entries' => $entries,
'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'),
'users' => $usersService->GetUsersAsDto(),
'transactionTypes' => GetClassConstants('\Grocy\Services\StockService', 'TRANSACTION_TYPE_')
]);

View File

@ -2308,3 +2308,18 @@ msgstr ""
msgid "Average execution frequency"
msgstr ""
msgid "Pakke"
msgstr ""
msgid "Del"
msgstr ""
msgid "Boks"
msgstr ""
msgid "Bunt"
msgstr ""
msgid "Flaske"
msgstr ""

View File

@ -12,15 +12,15 @@
"bootstrap-select": "^1.13.18",
"bwip-js": "^3.0.1",
"chart.js": "^2.8.0",
"datatables.net": "^1.10.22",
"datatables.net-bs4": "^1.10.22",
"datatables.net-colreorder": "^1.5.2",
"datatables.net-colreorder-bs4": "^1.5.2",
"datatables.net-plugins": "^1.10.20",
"datatables.net-rowgroup": "^1.1.2",
"datatables.net-rowgroup-bs4": "^1.1.2",
"datatables.net-select": "^1.3.1",
"datatables.net-select-bs4": "^1.3.1",
"datatables.net": "^1.11.5",
"datatables.net-bs4": "^1.11.5",
"datatables.net-colreorder": "^1.5.5",
"datatables.net-colreorder-bs4": "^1.5.5",
"datatables.net-plugins": "^1.11.5",
"datatables.net-rowgroup": "^1.1.4",
"datatables.net-rowgroup-bs4": "^1.1.4",
"datatables.net-select": "^1.3.4",
"datatables.net-select-bs4": "^1.3.4",
"fullcalendar": "^3.10.1",
"gettext-translator": "2.1.0",
"jquery": "3.5.1",
@ -28,6 +28,8 @@
"jquery-serializejson": "^2.9.0",
"moment": "^2.27.0",
"nosleep.js": "^0.12.0",
"select2": "^4.0.13",
"select2-theme-bootstrap4": "^1.0.1",
"sprintf-js": "^1.1.2",
"startbootstrap-sb-admin": "4.0.0",
"summernote": "^0.8.18",

View File

@ -94,10 +94,10 @@ body.fullscreen-card {
margin: 0 auto;
}
.form-check-input.is-valid ~ .form-check-label,
.was-validated .form-check-input:valid ~ .form-check-label,
.custom-control-input.is-valid ~ .custom-control-label,
.was-validated .custom-control-input:valid ~ .custom-control-label {
.form-check-input.is-valid~.form-check-label,
.was-validated .form-check-input:valid~.form-check-label,
.custom-control-input.is-valid~.custom-control-label,
.was-validated .custom-control-input:valid~.custom-control-label {
color: inherit;
}
@ -192,20 +192,20 @@ form.has-sticky-form-footer .form-group:nth-last-child(2) {
border-color: #d6d6d6 !important;
}
.navbar-sidenav > li,
.sidenav-second-level > li {
.navbar-sidenav>li,
.sidenav-second-level>li {
transition: all 0.3s !important;
}
.navbar-sidenav > li:hover,
.sidenav-second-level > li:hover,
.navbar-sidenav>li:hover,
.sidenav-second-level>li:hover,
.navbar-nav .dropdown-item:hover {
box-shadow: inset 5px 0 0 #337ab7 !important;
background-color: #d6d6d6 !important;
}
.navbar-sidenav > li > a:focus,
.sidenav-second-level > li > a:focus,
.navbar-sidenav>li>a:focus,
.sidenav-second-level>li>a:focus,
.navbar-nav .dropdown-item:focus {
box-shadow: inset 5px 0 0 #ab2230 !important;
background-color: #d6d6d6 !important;
@ -228,7 +228,8 @@ form.has-sticky-form-footer .form-group:nth-last-child(2) {
cursor: wait;
}
.expandable-text .collapse, .module .collapsing {
.expandable-text .collapse,
.module .collapsing {
height: 2.4rem;
}
@ -255,7 +256,8 @@ form.has-sticky-form-footer .form-group:nth-last-child(2) {
.table-inline-menu.dropdown-menu {
padding-left: 12px;
padding-right: 12px;
width: 96vw; /* Set width of popup menu to screen size */
width: 96vw;
/* Set width of popup menu to screen size */
}
a:not([href]) {
@ -326,12 +328,39 @@ a:not([href]) {
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px;
}
.select2-hidden-accessible.custom-select.is-invalid+.select2-container--bootstrap:not(:last-child)>.selection>.select2-selection,
.was-validated .select2-hidden-accessible.custom-select:invalid+.select2-container--bootstrap:not(:last-child)>.selection>.select2-selection {
border-color: #dc3545;
}
.select2-hidden-accessible.custom-select.is-invalid+.select2-container--focus.select2-container--bootstrap:not(:last-child)>.selection>.select2-selection,
.was-validated .select2-hidden-accessible.custom-select:invalid+.select2-container--focus.select2-container--bootstrap:not(:last-child)>.selection>.select2-selection,
.select2-hidden-accessible.custom-select.is-invalid+.select2-container--open.select2-container--bootstrap:not(:last-child)>.selection>.select2-selection,
.was-validated .select2-hidden-accessible.custom-select:invalid+.select2-container--open.select2-container--bootstrap:not(:last-child)>.selection>.select2-selection {
border-color: #dc3545;
box-shadow: 0 0 0 0.2rem rgb(220 53 69 / 25%);
}
.select2-hidden-accessible.custom-select.is-valid+.select2-container--bootstrap:not(:last-child)>.selection>.select2-selection,
.was-validated .select2-hidden-accessible.custom-select:valid+.select2-container--bootstrap:not(:last-child)>.selection>.select2-selection {
border-color: #28a745;
}
.select2-hidden-accessible.custom-select.is-valid+.select2-container--focus.select2-container--bootstrap:not(:last-child)>.selection>.select2-selection,
.was-validated .select2-hidden-accessible.custom-select:valid+.select2-container--focus.select2-container--bootstrap:not(:last-child)>.selection>.select2-selection,
.select2-hidden-accessible.custom-select.is-valid+.select2-container--open.select2-container--bootstrap:not(:last-child)>.selection>.select2-selection,
.was-validated .select2-hidden-accessible.custom-select:valid+.select2-container--open.select2-container--bootstrap:not(:last-child)>.selection>.select2-selection {
border-color: #28a745;
box-shadow: 0 0 0 0.2rem rgb(40 167 69 / 25%);
}
/* There is a little too much padding on form inputs */
.form-control {
padding-right: 0.75rem !important;
}
.btn-group-xs > .btn, .btn-xs {
.btn-group-xs>.btn,
.btn-xs {
padding: 0.25rem 0.4rem;
font-size: 0.875rem;
line-height: 0.5;
@ -346,7 +375,7 @@ a:not([href]) {
font-size: 125%;
}
.input-group > .form-control:focus {
.input-group>.form-control:focus {
z-index: inherit;
}
@ -384,7 +413,7 @@ tr.dtrg-group {
}
/* Third party component customizations - toastr */
#toast-container > div {
#toast-container>div {
opacity: 1;
filter: alpha(opacity=100);
}
@ -397,7 +426,7 @@ tr.dtrg-group {
background-color: #dc3545;
}
#toast-container > div {
#toast-container>div {
box-shadow: none;
}
@ -408,19 +437,19 @@ tr.dtrg-group {
}
/* Third party component customizations - SB Admin 2 */
#mainNav .navbar-collapse .navbar-nav > .nav-item.dropdown > .nav-link:after,
#mainNav .navbar-collapse .navbar-nav>.nav-item.dropdown>.nav-link:after,
#mainNav .navbar-collapse .navbar-sidenav .nav-link-collapse:after {
font-family: 'Font Awesome 5 Free';
font-weight: 900;
}
#mainNav .navbar-collapse .navbar-nav > .nav-item.dropdown > .nav-link.py-0:after,
#mainNav .navbar-collapse .navbar-nav>.nav-item.dropdown>.nav-link.py-0:after,
#mainNav .navbar-collapse .navbar-sidenav .nav-link-collapse.py-0:after {
padding-top: 8px;
}
#mainNav .navbar-collapse .navbar-sidenav > .nav-item > .nav-link,
#mainNav .navbar-collapse .navbar-sidenav > .nav-item .sidenav-second-level > li > a {
#mainNav .navbar-collapse .navbar-sidenav>.nav-item>.nav-link,
#mainNav .navbar-collapse .navbar-sidenav>.nav-item .sidenav-second-level>li>a {
padding: 0.75em;
}
@ -460,7 +489,7 @@ html {
margin-left: -0.15em !important;
}
#mainNav .navbar-collapse .navbar-sidenav > .nav-item > .nav-link {
#mainNav .navbar-collapse .navbar-sidenav>.nav-item>.nav-link {
padding-right: 1.25em !important;
}
@ -603,7 +632,7 @@ canvas.drawingBuffer {
margin-bottom: -.65rem;
}
.grocy-tabs.tab-content > .active {
.grocy-tabs.tab-content>.active {
display: flex;
flex-direction: column;
}
@ -615,7 +644,7 @@ canvas.drawingBuffer {
}
@media print {
.grocy-tabs.print.tab-content > .tab-pane {
.grocy-tabs.print.tab-content>.tab-pane {
display: flex !important;
flex-direction: column !important;
opacity: 1 !important;
@ -625,7 +654,7 @@ canvas.drawingBuffer {
height: auto !important;
}
.grocy-tabs.break > .tab-pane {
.grocy-tabs.break>.tab-pane {
page-break-after: always;
}
@ -656,3 +685,9 @@ canvas.drawingBuffer {
pointer-events: none;
opacity: 0.5;
}
/* Fix Select2 in .input-group */
.input-group>.select2-hidden-accessible+.select2-container--bootstrap>.selection>.select2-selection,
.input-group>.select2-hidden-accessible+.select2-container--bootstrap>.selection>.select2-selection.form-control {
width: 100%;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,72 +1,60 @@
$('#save-chore-button').on('click', function(e)
{
$('#save-chore-button').on('click', function(e) {
e.preventDefault();
if ($(".combobox-menu-visible").length)
{
if ($(".combobox-menu-visible").length) {
return;
}
var jsonData = $('#chore-form').serializeJSON();
jsonData.start_date = Grocy.Components.DateTimePicker.GetValue();
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS) {
jsonData.assignment_config = $("#assignment_config").val().join(",");
}
Grocy.FrontendHelpers.BeginUiBusy("chore-form");
if (Grocy.EditMode === 'create')
{
if (Grocy.EditMode === 'create') {
Grocy.Api.Post('objects/chores', jsonData,
function(result)
{
function(result) {
Grocy.EditObjectId = result.created_object_id;
Grocy.Components.UserfieldsForm.Save(function()
{
Grocy.Api.Post('chores/executions/calculate-next-assignments', { "chore_id": Grocy.EditObjectId },
function(result)
{
Grocy.Components.UserfieldsForm.Save(function() {
Grocy.Api.Post('chores/executions/calculate-next-assignments', {
"chore_id": Grocy.EditObjectId
},
function(result) {
window.location.href = U('/chores');
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr);
}
);
});
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy("chore-form");
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
}
);
}
else
{
} else {
Grocy.Api.Put('objects/chores/' + Grocy.EditObjectId, jsonData,
function(result)
{
Grocy.Components.UserfieldsForm.Save(function()
{
Grocy.Api.Post('chores/executions/calculate-next-assignments', { "chore_id": Grocy.EditObjectId },
function(result)
{
function(result) {
Grocy.Components.UserfieldsForm.Save(function() {
Grocy.Api.Post('chores/executions/calculate-next-assignments', {
"chore_id": Grocy.EditObjectId
},
function(result) {
window.location.href = U('/chores');
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr);
}
);
});
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy("chore-form");
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
}
@ -74,13 +62,11 @@
}
});
$('#chore-form input').keyup(function(event)
{
$('#chore-form input').on('keyup', function(event) {
Grocy.FrontendHelpers.ValidateForm('chore-form');
});
$('#chore-form input').keydown(function(event)
{
$('#chore-form input').on('keydown', function(event) {
if (event.keyCode === 13) //Enter
{
event.preventDefault();
@ -88,58 +74,48 @@ $('#chore-form input').keydown(function(event)
if (document.getElementById('chore-form').checkValidity() === false) //There is at least one validation error
{
return false;
}
else
{
} else {
$('#save-chore-button').click();
}
}
});
var checkboxValues = $("#period_config").val().split(",");
for (var i = 0; i < checkboxValues.length; i++)
{
if (!checkboxValues[i].isEmpty())
{
for (var i = 0; i < checkboxValues.length; i++) {
if (!checkboxValues[i].isEmpty()) {
$("#" + checkboxValues[i]).prop('checked', true);
}
}
Grocy.Components.UserfieldsForm.Load();
$('#name').focus();
$('#name').trigger('focus');
Grocy.FrontendHelpers.ValidateForm('chore-form');
if (Grocy.EditMode == "edit")
{
if (Grocy.EditMode == "edit") {
Grocy.Api.Get('objects/chores_log?limit=1&query[]=chore_id=' + Grocy.EditObjectId,
function(journalEntries)
{
if (journalEntries.length > 0)
{
function(journalEntries) {
if (journalEntries.length > 0) {
$(".datetimepicker-input").attr("disabled", "");
}
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
}
setTimeout(function()
{
setTimeout(function() {
$(".input-group-chore-period-type").trigger("change");
$(".input-group-chore-assignment-type").trigger("change");
// Click twice to trigger on-click but not change the actual checked state
$("#consume_product_on_execution").click();
$("#consume_product_on_execution").click();
$("#consume_product_on_execution").trigger('click');
$("#consume_product_on_execution").trigger('click');
Grocy.Components.ProductPicker.GetPicker().trigger('change');
Grocy.Components.ProductPicker.Validate();
}, 100);
$('.input-group-chore-period-type').on('change keyup', function(e)
{
$('.input-group-chore-period-type').on('change keyup', function(e) {
var periodType = $('#period_type').val();
var periodDays = $('#period_days').val();
var periodInterval = $('#period_interval').val();
@ -148,68 +124,49 @@ $('.input-group-chore-period-type').on('change keyup', function(e)
$(".period-type-" + periodType).removeClass("d-none");
$("#period_config").val("");
if (periodType === 'manually')
{
if (periodType === 'manually') {
$('#chore-schedule-info').text(__t('This means the next execution of this chore is not scheduled'));
}
else if (periodType === 'hourly')
{
} else if (periodType === 'hourly') {
$('#chore-schedule-info').text(__n(periodInterval, "This means the next execution of this chore is scheduled %s hour after the last execution", "This means the next execution of this chore is scheduled %s hours after the last execution"));
}
else if (periodType === 'daily')
{
} else if (periodType === 'daily') {
$('#chore-schedule-info').text(__n(periodInterval, "This means the next execution of this chore is scheduled at the same time (based on the start date) every day", "This means the next execution of this chore is scheduled at the same time (based on the start date) every %s days"));
}
else if (periodType === 'weekly')
{
} else if (periodType === 'weekly') {
$('#chore-schedule-info').text(__n(periodInterval, "This means the next execution of this chore is scheduled every week on the selected weekdays", "This means the next execution of this chore is scheduled every %s weeks on the selected weekdays"));
$("#period_config").val($(".period-type-weekly input:checkbox:checked").map(function() { return this.value; }).get().join(","));
}
else if (periodType === 'monthly')
{
$("#period_config").val($(".period-type-weekly input:checkbox:checked").map(function() {
return this.value;
}).get().join(","));
} else if (periodType === 'monthly') {
$('#chore-schedule-info').text(__n(periodInterval, "This means the next execution of this chore is scheduled on the selected day every month", "This means the next execution of this chore is scheduled on the selected day every %s months"));
$("label[for='period_days']").text(__t("Day of month"));
$("#period_days").attr("min", "1");
$("#period_days").attr("max", "31");
}
else if (periodType === 'yearly')
{
} else if (periodType === 'yearly') {
$('#chore-schedule-info').text(__n(periodInterval, 'This means the next execution of this chore is scheduled every year on the same day (based on the start date)', 'This means the next execution of this chore is scheduled every %s years on the same day (based on the start date)'));
}
else if (periodType === 'adaptive')
{
} else if (periodType === 'adaptive') {
$('#chore-schedule-info').text(__t('This means the next execution of this chore is scheduled dynamically based on the past average execution frequency'));
}
Grocy.FrontendHelpers.ValidateForm('chore-form');
});
$('.input-group-chore-assignment-type').on('change', function(e)
{
$('.input-group-chore-assignment-type').on('change', function(e) {
var assignmentType = $('#assignment_type').val();
$('#chore-period-assignment-info').text("");
$("#assignment_config").removeAttr("required");
$("#assignment_config").attr("disabled", "");
if (assignmentType === 'no-assignment')
{
if (assignmentType === 'no-assignment') {
$('#chore-assignment-type-info').text(__t('This means the next execution of this chore will not be assigned to anyone'));
}
else if (assignmentType === 'who-least-did-first')
{
} else if (assignmentType === 'who-least-did-first') {
$('#chore-assignment-type-info').text(__t('This means the next execution of this chore will be assigned to the one who executed it least'));
$("#assignment_config").attr("required", "");
$("#assignment_config").removeAttr("disabled");
}
else if (assignmentType === 'random')
{
} else if (assignmentType === 'random') {
$('#chore-assignment-type-info').text(__t('This means the next execution of this chore will be assigned randomly'));
$("#assignment_config").attr("required", "");
$("#assignment_config").removeAttr("disabled");
}
else if (assignmentType === 'in-alphabetical-order')
{
} else if (assignmentType === 'in-alphabetical-order') {
$('#chore-assignment-type-info').text(__t('This means the next execution of this chore will be assigned to the next one in alphabetical order'));
$("#assignment_config").attr("required", "");
$("#assignment_config").removeAttr("disabled");
@ -218,15 +175,11 @@ $('.input-group-chore-assignment-type').on('change', function(e)
Grocy.FrontendHelpers.ValidateForm('chore-form');
});
$("#consume_product_on_execution").on("click", function()
{
if (this.checked)
{
$("#consume_product_on_execution").on("click", function() {
if (this.checked) {
Grocy.Components.ProductPicker.Enable();
$("#product_amount").removeAttr("disabled");
}
else
{
} else {
Grocy.Components.ProductPicker.Disable();
$("#product_amount").attr("disabled", "");
}
@ -234,35 +187,28 @@ $("#consume_product_on_execution").on("click", function()
Grocy.FrontendHelpers.ValidateForm("chore-form");
});
Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
{
Grocy.Components.ProductPicker.OnChange(function(e) {
var productId = $(e.target).val();
if (productId)
{
if (productId) {
Grocy.Api.Get('stock/products/' + productId,
function(productDetails)
{
function(productDetails) {
$('#amount_qu_unit').text(productDetails.quantity_unit_stock.name);
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
}
});
$(document).on('click', '.chore-grocycode-label-print', function(e)
{
$(document).on('click', '.chore-grocycode-label-print', function(e) {
e.preventDefault();
document.activeElement.blur();
var choreId = $(e.currentTarget).attr('data-chore-id');
Grocy.Api.Get('chores/' + choreId + '/printlabel', function(labelData)
{
if (Grocy.Webhooks.labelprinter !== undefined)
{
Grocy.Api.Get('chores/' + choreId + '/printlabel', function(labelData) {
if (Grocy.Webhooks.labelprinter !== undefined) {
Grocy.FrontendHelpers.RunWebhook(Grocy.Webhooks.labelprinter, labelData);
}
});

View File

@ -1,20 +1,17 @@
Grocy.Components.BarcodeScanner = {};
Grocy.Components.BarcodeScanner.LiveVideoSizeAdjusted = false;
Grocy.Components.BarcodeScanner.CheckCapabilities = async function()
{
Grocy.Components.BarcodeScanner.CheckCapabilities = async function() {
var track = Quagga.CameraAccess.getActiveTrack();
var capabilities = {};
if (typeof track.getCapabilities === 'function')
{
if (typeof track.getCapabilities === 'function') {
capabilities = track.getCapabilities();
}
// If there is more than 1 camera, show the camera selection
var cameras = await Quagga.CameraAccess.enumerateVideoDevices();
var cameraSelect = document.querySelector('.cameraSelect-wrapper');
if (cameraSelect)
{
if (cameraSelect) {
cameraSelect.style.display = cameras.length > 1 ? 'inline-block' : 'none';
}
@ -22,29 +19,23 @@ Grocy.Components.BarcodeScanner.CheckCapabilities = async function()
var canTorch = typeof capabilities.torch === 'boolean' && capabilities.torch
// Remove the torch button, if either the device can not torch or AutoTorchOn is set.
var node = document.querySelector('.torch');
if (node)
{
if (node) {
node.style.display = canTorch && !Grocy.FeatureFlags.GROCY_FEATURE_FLAG_AUTO_TORCH_ON_WITH_CAMERA ? 'inline-block' : 'none';
}
// If AutoTorchOn is set, turn on the torch.
if (canTorch && Grocy.FeatureFlags.GROCY_FEATURE_FLAG_AUTO_TORCH_ON_WITH_CAMERA)
{
if (canTorch && Grocy.FeatureFlags.GROCY_FEATURE_FLAG_AUTO_TORCH_ON_WITH_CAMERA) {
Grocy.Components.BarcodeScanner.TorchOn(track);
}
// Reduce the height of the video, if it's higher than then the viewport
if (!Grocy.Components.BarcodeScanner.LiveVideoSizeAdjusted)
{
if (!Grocy.Components.BarcodeScanner.LiveVideoSizeAdjusted) {
var bc = document.getElementById('barcodescanner-container');
if (bc)
{
if (bc) {
var bcAspectRatio = bc.offsetWidth / bc.offsetHeight;
var settings = track.getSettings();
if (bcAspectRatio > settings.aspectRatio)
{
if (bcAspectRatio > settings.aspectRatio) {
var v = document.querySelector('#barcodescanner-livestream video')
if (v)
{
if (v) {
var c = document.querySelector('#barcodescanner-livestream canvas')
var newWidth = v.clientWidth / bcAspectRatio * settings.aspectRatio + 'px';
v.style.width = newWidth;
@ -57,8 +48,7 @@ Grocy.Components.BarcodeScanner.CheckCapabilities = async function()
}
}
Grocy.Components.BarcodeScanner.StartScanning = function()
{
Grocy.Components.BarcodeScanner.StartScanning = function() {
Grocy.Components.BarcodeScanner.DecodedCodesCount = 0;
Grocy.Components.BarcodeScanner.DecodedCodesErrorCount = 0;
@ -69,7 +59,9 @@ Grocy.Components.BarcodeScanner.StartScanning = function()
target: document.querySelector("#barcodescanner-livestream"),
constraints: {
facingMode: "environment",
...(window.localStorage.getItem('cameraId') && { deviceId: window.localStorage.getItem('cameraId') }) // If preferred cameraId is set, request to use that specific camera
...(window.localStorage.getItem('cameraId') && {
deviceId: window.localStorage.getItem('cameraId')
}) // If preferred cameraId is set, request to use that specific camera
}
},
locator: {
@ -115,15 +107,12 @@ Grocy.Components.BarcodeScanner.StartScanning = function()
}
},
locate: true
}, function(error)
{
if (error)
{
}, function(error) {
if (error) {
Grocy.FrontendHelpers.ShowGenericError("Error while initializing the barcode scanning library", error.message);
toastr.info(__t("Camera access is only possible when supported and allowed by your browser and when grocy is served via a secure (https://) connection"));
window.localStorage.removeItem("cameraId");
setTimeout(function()
{
setTimeout(function() {
bootbox.hideAll();
}, 500);
return;
@ -135,8 +124,7 @@ Grocy.Components.BarcodeScanner.StartScanning = function()
});
}
Grocy.Components.BarcodeScanner.StopScanning = function()
{
Grocy.Components.BarcodeScanner.StopScanning = function() {
Quagga.stop();
Grocy.Components.BarcodeScanner.DecodedCodesCount = 0;
@ -145,76 +133,77 @@ Grocy.Components.BarcodeScanner.StopScanning = function()
bootbox.hideAll();
}
Grocy.Components.BarcodeScanner.TorchOn = function(track)
{
if (track)
{
Grocy.Components.BarcodeScanner.TorchOn = function(track) {
if (track) {
track.applyConstraints({
advanced: [
{
torch: true
}
]
advanced: [{
torch: true
}]
});
}
}
Quagga.onDetected(function(result)
{
$.each(result.codeResult.decodedCodes, function(id, error)
{
if (error.error != undefined)
{
Quagga.onDetected(function(result) {
$.each(result.codeResult.decodedCodes, function(id, error) {
if (error.error != undefined) {
Grocy.Components.BarcodeScanner.DecodedCodesCount++;
Grocy.Components.BarcodeScanner.DecodedCodesErrorCount += parseFloat(error.error);
}
});
if ((Grocy.Components.BarcodeScanner.DecodedCodesErrorCount / Grocy.Components.BarcodeScanner.DecodedCodesCount < 0.15) ||
(Grocy.Components.BarcodeScanner.DecodedCodesErrorCount == 0 && Grocy.Components.BarcodeScanner.DecodedCodesCount == 0 && result.codeResult.code.length != 0))
{
(Grocy.Components.BarcodeScanner.DecodedCodesErrorCount == 0 && Grocy.Components.BarcodeScanner.DecodedCodesCount == 0 && result.codeResult.code.length != 0)) {
Grocy.Components.BarcodeScanner.StopScanning();
$(document).trigger("Grocy.BarcodeScanned", [result.codeResult.code, Grocy.Components.BarcodeScanner.CurrentTarget]);
}
});
Quagga.onProcessed(function(result)
{
Quagga.onProcessed(function(result) {
var drawingCtx = Quagga.canvas.ctx.overlay;
var drawingCanvas = Quagga.canvas.dom.overlay;
if (result)
{
if (result.boxes)
{
if (result) {
if (result.boxes) {
drawingCtx.clearRect(0, 0, parseInt(drawingCanvas.getAttribute("width")), parseInt(drawingCanvas.getAttribute("height")));
result.boxes.filter(function(box)
{
result.boxes.filter(function(box) {
return box !== result.box;
}).forEach(function(box)
{
Quagga.ImageDebug.drawPath(box, { x: 0, y: 1 }, drawingCtx, { color: "yellow", lineWidth: 4 });
}).forEach(function(box) {
Quagga.ImageDebug.drawPath(box, {
x: 0,
y: 1
}, drawingCtx, {
color: "yellow",
lineWidth: 4
});
});
}
if (result.box)
{
Quagga.ImageDebug.drawPath(result.box, { x: 0, y: 1 }, drawingCtx, { color: "green", lineWidth: 4 });
if (result.box) {
Quagga.ImageDebug.drawPath(result.box, {
x: 0,
y: 1
}, drawingCtx, {
color: "green",
lineWidth: 4
});
}
if (result.codeResult && result.codeResult.code)
{
Quagga.ImageDebug.drawPath(result.line, { x: 'x', y: 'y' }, drawingCtx, { color: "red", lineWidth: 4 });
if (result.codeResult && result.codeResult.code) {
Quagga.ImageDebug.drawPath(result.line, {
x: 'x',
y: 'y'
}, drawingCtx, {
color: "red",
lineWidth: 4
});
}
}
});
$(document).on("click", "#barcodescanner-start-button", async function(e)
{
$(document).on("click", "#barcodescanner-start-button", async function(e) {
e.preventDefault();
var inputElement = $(e.currentTarget).prev();
if (inputElement.hasAttr("disabled"))
{
if (inputElement.hasAttr("disabled")) {
// Do nothing and disable the barcode scanner start button
$(e.currentTarget).addClass("disabled");
return;
@ -225,8 +214,7 @@ $(document).on("click", "#barcodescanner-start-button", async function(e)
var dialog = bootbox.dialog({
message: '<div id="barcodescanner-container" class="col"><div id="barcodescanner-livestream"></div></div>',
title: __t('Scan a barcode'),
onEscape: function()
{
onEscape: function() {
Grocy.Components.BarcodeScanner.StopScanning();
},
size: 'big',
@ -236,8 +224,7 @@ $(document).on("click", "#barcodescanner-start-button", async function(e)
torch: {
label: '<i class="far fa-lightbulb"></i>',
className: 'btn-warning responsive-button torch',
callback: function()
{
callback: function() {
Grocy.Components.BarcodeScanner.TorchOn(Quagga.CameraAccess.getActiveTrack());
return false;
}
@ -245,8 +232,7 @@ $(document).on("click", "#barcodescanner-start-button", async function(e)
cancel: {
label: __t('Cancel'),
className: 'btn-secondary responsive-button',
callback: function()
{
callback: function() {
Grocy.Components.BarcodeScanner.StopScanning();
}
}
@ -258,8 +244,7 @@ $(document).on("click", "#barcodescanner-start-button", async function(e)
var cameraSelect = document.querySelector('.cameraSelect');
var cameras = await Quagga.CameraAccess.enumerateVideoDevices();
cameras.forEach(camera =>
{
cameras.forEach(camera => {
var option = document.createElement("option");
option.text = camera.label ? camera.label : camera.deviceId; // Use camera label if it exists, else show device id
option.value = camera.deviceId;
@ -269,8 +254,7 @@ $(document).on("click", "#barcodescanner-start-button", async function(e)
// Set initial value to preferred camera if one exists - and if not, start out empty
cameraSelect.value = window.localStorage.getItem('cameraId');
cameraSelect.onchange = function()
{
cameraSelect.onchange = function() {
window.localStorage.setItem('cameraId', cameraSelect.value);
Quagga.stop();
Grocy.Components.BarcodeScanner.StartScanning();
@ -280,29 +264,25 @@ $(document).on("click", "#barcodescanner-start-button", async function(e)
});
Grocy.Components.BarcodeScanner.InitDone = false;
Grocy.Components.BarcodeScanner.Init = function()
{
if (Grocy.Components.BarcodeScanner.InitDone)
{
Grocy.Components.BarcodeScanner.Init = function() {
if (Grocy.Components.BarcodeScanner.InitDone) {
return;
}
$(".barcodescanner-input:visible").each(function()
{
if ($(this).hasAttr("disabled"))
{
$(this).after('<a id="barcodescanner-start-button" class="btn btn-sm btn-primary text-white disabled" data-target="' + $(this).attr("data-target") + '"><i class="fas fa-camera"></i></a>');
}
else
{
$(this).after('<a id="barcodescanner-start-button" class="btn btn-sm btn-primary text-white" data-target="' + $(this).attr("data-target") + '"><i class="fas fa-camera"></i></a>');
$(".barcodescanner-input:visible").each(function() {
var isSelect2 = $(this).hasClass('select2') && $(this).parent().hasClass('input-group');
var html = '<a id="barcodescanner-start-button" class="btn' + (isSelect2 ? '' : ' btn-sm') + ' btn-primary text-white' + ($(this).hasAttr("disabled") ? ' disabled' : '') + '" data-target="' + $(this).attr("data-target") + '"><i class="fas fa-camera"></i></a>';
if (isSelect2) {
html = '<div class="input-group-prepend" id="barcodescanner-start-button-container">' + html + '</div>'
$(this).parent().prepend(html);
} else {
$(this).after(html);
}
Grocy.Components.BarcodeScanner.InitDone = true;
});
}
setTimeout(function()
{
setTimeout(function() {
Grocy.Components.BarcodeScanner.Init();
}, 50);

View File

@ -1,377 +1,457 @@
Grocy.Components.ProductPicker = {};
Grocy.Components.ProductPicker.GetPicker = function()
{
Grocy.Components.ProductPicker.GetPicker = function() {
return $('#product_id');
}
Grocy.Components.ProductPicker.GetInputElement = function()
{
return $('#product_id_text_input');
Grocy.Components.ProductPicker.GetValue = function() {
return this.GetPicker().val();
}
Grocy.Components.ProductPicker.GetValue = function()
{
return $('#product_id').val();
Grocy.Components.ProductPicker.GetOption = function(key) {
return this.GetPicker().parents('.form-group').data(key);
}
Grocy.Components.ProductPicker.SetValue = function(value)
{
Grocy.Components.ProductPicker.GetInputElement().val(value);
Grocy.Components.ProductPicker.GetInputElement().trigger('change');
Grocy.Components.ProductPicker.GetState = function(key) {
return this.GetPicker().data(key);
}
Grocy.Components.ProductPicker.SetId = function(value)
{
Grocy.Components.ProductPicker.GetPicker().val(value);
Grocy.Components.ProductPicker.GetPicker().data('combobox').refresh();
Grocy.Components.ProductPicker.GetInputElement().trigger('change');
Grocy.Components.ProductPicker.SetState = function(key, value) {
this.GetPicker().data(key, value);
return this;
}
Grocy.Components.ProductPicker.Clear = function()
{
Grocy.Components.ProductPicker.SetValue('');
Grocy.Components.ProductPicker.SetId(null);
Grocy.Components.ProductPicker.SetId = function(value, callback) {
if (this.GetPicker().find('option[value="' + value + '"]').length) {
this.GetPicker().val(value).trigger('change');
} else {
Grocy.Api.Get('objects/products/' + encodeURIComponent(value),
function(result) {
var option = new Option(result.name, value, true, true);
if (typeof callback === 'function') {
Grocy.Components.ProductPicker.GetPicker().one('change.select2', callback);
}
Grocy.Components.ProductPicker.GetPicker().append(option).trigger('change').select2('close');
},
function(xhr) {
console.error(xhr);
}
);
}
return this;
}
Grocy.Components.ProductPicker.InProductAddWorkflow = function()
{
Grocy.Components.ProductPicker.Clear = function() {
this.GetPicker().val(null).trigger('change');
return this;
}
Grocy.Components.ProductPicker.InProductAddWorkflow = function() {
return GetUriParam('flow') == "InplaceNewProductWithName";
}
Grocy.Components.ProductPicker.InProductModifyWorkflow = function()
{
Grocy.Components.ProductPicker.InProductModifyWorkflow = function() {
return GetUriParam('flow') == "InplaceAddBarcodeToExistingProduct";
}
Grocy.Components.ProductPicker.InAnyFlow = function()
{
return Grocy.Components.ProductPicker.InProductAddWorkflow() || Grocy.Components.ProductPicker.InProductModifyWorkflow();
Grocy.Components.ProductPicker.InAnyFlow = function() {
return GetUriParam('flow') !== undefined;
}
Grocy.Components.ProductPicker.FinishFlow = function()
{
Grocy.Components.ProductPicker.FinishFlow = function() {
RemoveUriParam("flow");
RemoveUriParam("barcode");
RemoveUriParam("product-name");
return this;
}
Grocy.Components.ProductPicker.ShowCustomError = function(text)
{
Grocy.Components.ProductPicker.ShowCustomError = function(text) {
var element = $("#custom-productpicker-error");
element.text(text);
element.removeClass("d-none");
return this;
}
Grocy.Components.ProductPicker.HideCustomError = function()
{
Grocy.Components.ProductPicker.HideCustomError = function() {
$("#custom-productpicker-error").addClass("d-none");
return this;
}
Grocy.Components.ProductPicker.Disable = function()
{
Grocy.Components.ProductPicker.GetInputElement().attr("disabled", "");
Grocy.Components.ProductPicker.Disable = function() {
this.GetPicker().prop("disabled", true);
$("#barcodescanner-start-button").attr("disabled", "");
$("#barcodescanner-start-button").addClass("disabled");
return this;
}
Grocy.Components.ProductPicker.Enable = function()
{
Grocy.Components.ProductPicker.GetInputElement().removeAttr("disabled");
Grocy.Components.ProductPicker.Enable = function() {
this.GetPicker().prop("disabled", false);
$("#barcodescanner-start-button").removeAttr("disabled");
$("#barcodescanner-start-button").removeClass("disabled");
return this;
}
$('.product-combobox').combobox({
appendId: '_text_input',
bsVersion: '4',
clearIfNoMatch: false
Grocy.Components.ProductPicker.Require = function() {
this.GetPicker().prop("required", true);
return this;
}
Grocy.Components.ProductPicker.Optional = function() {
this.GetPicker().prop("required", false);
return this;
}
Grocy.Components.ProductPicker.Focus = function() {
this.GetPicker().select2('open');
return this;
}
Grocy.Components.ProductPicker.Validate = function() {
this.GetPicker().trigger('change');
return this;
}
Grocy.Components.ProductPicker.OnChange = function(eventHandler) {
this.GetPicker().on('change', eventHandler);
return this;
}
Grocy.Components.ProductPicker.Search = function(term, callback) {
var $picker = this.GetPicker();
var doSearch = function() {
var $search = $picker.data('select2').dropdown.$search || $picker.data('select2').selection.$search;
$search.val(term).trigger('input');
// must wait for debounce before listening for 'results:all' event
setTimeout(function() {
$picker.one('Grocy.ResultsUpdated', function() {
if (typeof callback === 'function') {
$picker.one('select2:close', callback);
}
$picker.select2('close');
});
}, 150);
};
if ($picker.data('select2').isOpen()) {
doSearch();
} else {
$picker.one('select2:open', doSearch);
$picker.select2('open');
}
return this;
}
Grocy.Components.ProductPicker.IsOpen = function() {
return this.GetPicker().parent().find('.select2-container').hasClass('select2-container--open');
}
// initialize Select2 product picker
var lastProductSearchTerm = '';
Grocy.Components.ProductPicker.GetPicker().select2({
selectOnClose: true,
ajax: {
delay: 150,
transport: function(params, success, failure) {
var results_per_page = 10;
var page = params.data.page || 1;
var term = params.data.term || "";
var termIsGrocycode = term.startsWith("grcy");
lastProductSearchTerm = term;
// reset grocycode/barcode state
Grocy.Components.ProductPicker.SetState('barcode', 'null');
Grocy.Components.ProductPicker.SetState('grocycode', false);
// build search queries
var baseQuery = Grocy.Components.ProductPicker.GetOption('products-query').split('&');
baseQuery.push('limit=' + encodeURIComponent(results_per_page));
baseQuery.push('offset=' + encodeURIComponent((page - 1) * results_per_page));
var queries = [];
if (term.length > 0) {
queries = [
// search product fields (name, etc.)
baseQuery.concat('search=' + encodeURIComponent(term)).join('&'),
];
// grocycode handling
if (termIsGrocycode) {
var gc = term.split(":");
if (gc[1] == "p") {
queries.push(baseQuery.concat('query%5B%5D=id%3D' + encodeURIComponent(gc[2])).join('&'));
}
}
} else {
queries = [baseQuery.join('&')];
}
// execute all queries in parallel, return first non-empty response
var complete = 0;
var responded = false;
var xhrs = [];
var handleEmptyResponse = function() {
if (responded || complete < xhrs.length) return;
success({
results: [],
pagination: {
more: false
}
});
};
var handleResponse = function(results, meta, cur_xhr) {
// track complete queries
complete++;
// abort if we already responded
if (responded) return;
// abort if no results
if (results.length === 0) {
handleEmptyResponse();
return;
}
// track whether we have responded
responded = true;
// abort all other queries
xhrs.forEach(function(xhr) {
if (xhr !== cur_xhr) xhr.abort();
});
// update grocycode/barcode state
Grocy.Components.ProductPicker.SetState('grocycode', termIsGrocycode);
Grocy.Components.ProductPicker.SetState('barcode', term);
success({
results: results.map(function(result) {
return {
id: result.id,
text: result.name
};
}),
pagination: {
more: page * results_per_page < meta.recordsFiltered
}
});
};
var handleErrors = function(xhr) {
console.error(xhr);
// track complete queries
complete++;
// abort if we already responded
if (responded) return;
handleEmptyResponse();
};
if (term.length > 0) {
xhrs.push(Grocy.Api.Get('objects/product_barcodes?limit=1&query%5B%5D=barcode%3D' + encodeURIComponent(term),
function(results, meta) {
// track complete queries
complete++;
// abort if we already responded
if (responded) return;
// abort if no results
if (results.length === 0) {
handleEmptyResponse();
return;
}
var cur_xhr = Grocy.Api.Get('objects/products?' + baseQuery.concat('query%5B%5D=id%3D' + encodeURIComponent(results[0].product_id)).join('&'),
function(results, meta) {
handleResponse(results, meta, cur_xhr);
},
handleErrors
);
xhrs.push(cur_xhr);
},
handleErrors
));
}
xhrs = xhrs.concat(queries.map(function(query) {
var cur_xhr = Grocy.Api.Get('objects/products' + (query.length > 0 ? '?' + query : ''),
function(results, meta) {
handleResponse(results, meta, cur_xhr);
},
handleErrors
);
return cur_xhr;
}));
}
}
});
var prefillProduct = GetUriParam('product-name');
var prefillProduct2 = Grocy.Components.ProductPicker.GetPicker().parent().data('prefill-by-name').toString();
if (!prefillProduct2.isEmpty())
{
prefillProduct = prefillProduct2;
}
if (typeof prefillProduct !== "undefined")
{
var possibleOptionElement = $("#product_id option[data-additional-searchdata*=\"" + prefillProduct + "\"]").first();
if (possibleOptionElement.length === 0)
{
possibleOptionElement = $("#product_id option:contains(\"" + prefillProduct + "\")").first();
}
// forward 'results:all' event
Grocy.Components.ProductPicker.GetPicker().data('select2').on('results:all', function(data) {
Grocy.Components.ProductPicker.GetPicker().trigger('Grocy.ResultsUpdated', data);
});
if (possibleOptionElement.length > 0)
{
$('#product_id').val(possibleOptionElement.val());
$('#product_id').data('combobox').refresh();
$('#product_id').trigger('change');
// handle barcode scanning
$(document).on("Grocy.BarcodeScanned", function(e, barcode, target) {
// check that the barcode scan is for the product picker
if (!(target == "@productpicker" || target == "undefined" || target == undefined)) return;
var nextInputElement = $(Grocy.Components.ProductPicker.GetPicker().parent().data('next-input-selector').toString());
nextInputElement.focus();
}
}
Grocy.Components.ProductPicker.Search(barcode);
});
var prefillProductId = GetUriParam("product");
var prefillProductId2 = Grocy.Components.ProductPicker.GetPicker().parent().data('prefill-by-id').toString();
if (!prefillProductId2.isEmpty())
{
prefillProductId = prefillProductId2;
}
if (typeof prefillProductId !== "undefined")
{
$('#product_id').val(prefillProductId);
$('#product_id').data('combobox').refresh();
$('#product_id').trigger('change');
// fix placement of bootbox footer buttons
$(document).on("shown.bs.modal", function() {
$(".modal-footer").addClass("d-block").addClass("d-sm-flex");
$(".modal-footer").find("button").addClass("mt-2").addClass("mt-sm-0");
});
var nextInputElement = $(Grocy.Components.ProductPicker.GetPicker().parent().data('next-input-selector').toString());
nextInputElement.focus();
}
if (GetUriParam("flow") === "InplaceAddBarcodeToExistingProduct")
{
if (GetUriParam("flow") === "InplaceAddBarcodeToExistingProduct") {
$('#InplaceAddBarcodeToExistingProduct').text(GetUriParam("barcode"));
$('#flow-info-InplaceAddBarcodeToExistingProduct').removeClass('d-none');
$('#barcode-lookup-disabled-hint').removeClass('d-none');
$('#barcode-lookup-hint').addClass('d-none');
}
// prefill by name
var prefillProduct = Grocy.Components.ProductPicker.GetOption('prefill-by-name') || GetUriParam('product-name');
if (typeof prefillProduct === 'string' && !prefillProduct.isEmpty()) {
Grocy.Components.ProductPicker.Search(prefillProduct, function() {
$(Grocy.Components.ProductPicker.GetOption('next-input-selector')).trigger('focus');
});
}
// prefill by ID
var prefillProduct = Grocy.Components.ProductPicker.GetOption('prefill-by-id') || GetUriParam('product');
if (typeof prefillProduct === 'string' && !prefillProduct.isEmpty()) {
Grocy.Components.ProductPicker.SetId(prefillProduct, function() {
$(Grocy.Components.ProductPicker.GetOption('next-input-selector')).trigger('focus');
});
}
// open create product/barcode dialog if no results
Grocy.Components.ProductPicker.PopupOpen = false;
$('#product_id_text_input').on('blur', function(e)
{
if (Grocy.Components.ProductPicker.GetPicker().hasClass("combobox-menu-visible"))
{
return;
}
$('#product_id').attr("barcode", "null");
Grocy.Components.ProductPicker.GetPicker().on('select2:close', function() {
if (Grocy.Components.ProductPicker.PopupOpen || Grocy.Components.ProductPicker.GetPicker().select2('data').length > 0) return;
var input = $('#product_id_text_input').val().toString();
var possibleOptionElement = [];
// grocycode handling
if (input.startsWith("grcy"))
{
var gc = input.split(":");
if (gc[1] == "p")
{
possibleOptionElement = $("#product_id option[value=\"" + gc[2] + "\"]").first();
$("#product_id").data("grocycode", true);
}
}
else // Normal product barcode handling
{
possibleOptionElement = $("#product_id option[data-additional-searchdata*=\"" + input + ",\"]").first();
var addProductWorkflowsAdditionalCssClasses = "";
if (Grocy.Components.ProductPicker.GetOption('disallow-add-product-workflows')) {
addProductWorkflowsAdditionalCssClasses = "d-none";
}
if (GetUriParam('flow') === undefined && input.length > 0 && possibleOptionElement.length > 0)
{
$('#product_id').val(possibleOptionElement.val());
$('#product_id').attr("barcode", input);
$('#product_id').data('combobox').refresh();
$('#product_id').trigger('change');
var embedded = "";
if (GetUriParam("embedded") !== undefined) {
embedded = "embedded";
}
else
{
if (Grocy.Components.ProductPicker.PopupOpen === true)
{
return;
}
var optionElement = $("#product_id option:contains(\"" + input + "\")").first();
if (input.length > 0 && optionElement.length === 0 && GetUriParam('flow') === undefined && Grocy.Components.ProductPicker.GetPicker().parent().data('disallow-all-product-workflows').toString() === "false")
{
var addProductWorkflowsAdditionalCssClasses = "";
if (Grocy.Components.ProductPicker.GetPicker().parent().data('disallow-add-product-workflows').toString() === "true")
{
addProductWorkflowsAdditionalCssClasses = "d-none";
var buttons = {
cancel: {
label: __t('Cancel'),
className: 'btn-secondary responsive-button',
callback: function() {
Grocy.Components.ProductPicker.PopupOpen = false;
Grocy.Components.ProductPicker.Clear();
}
var embedded = "";
if (GetUriParam("embedded") !== undefined)
{
embedded = "embedded";
}
var buttons = {
cancel: {
label: __t('Cancel'),
className: 'btn-secondary responsive-button',
callback: function()
{
Grocy.Components.ProductPicker.PopupOpen = false;
Grocy.Components.ProductPicker.SetValue('');
}
},
addnewproduct: {
label: '<strong>P</strong> ' + __t('Add as new product'),
className: 'btn-success add-new-product-dialog-button responsive-button ' + addProductWorkflowsAdditionalCssClasses,
callback: function()
{
// Not the best place here - this is only relevant when this flow is started from the shopping list item form
// (to select the correct shopping list on return)
if (GetUriParam("list") !== undefined)
{
embedded += "&list=" + GetUriParam("list");
}
Grocy.Components.ProductPicker.PopupOpen = false;
window.location.href = U('/product/new?flow=InplaceNewProductWithName&name=' + encodeURIComponent(input) + '&returnto=' + encodeURIComponent(Grocy.CurrentUrlRelative + "?flow=InplaceNewProductWithName&" + embedded) + "&" + embedded);
}
},
addbarcode: {
label: '<strong>B</strong> ' + __t('Add as barcode to existing product'),
className: 'btn-info add-new-barcode-dialog-button responsive-button',
callback: function()
{
Grocy.Components.ProductPicker.PopupOpen = false;
window.location.href = U(Grocy.CurrentUrlRelative + '?flow=InplaceAddBarcodeToExistingProduct&barcode=' + encodeURIComponent(input) + "&" + embedded);
}
},
addnewproductwithbarcode: {
label: '<strong>A</strong> ' + __t('Add as new product and prefill barcode'),
className: 'btn-warning add-new-product-with-barcode-dialog-button responsive-button ' + addProductWorkflowsAdditionalCssClasses,
callback: function()
{
Grocy.Components.ProductPicker.PopupOpen = false;
window.location.href = U('/product/new?flow=InplaceNewProductWithBarcode&barcode=' + encodeURIComponent(input) + '&returnto=' + encodeURIComponent(Grocy.CurrentUrlRelative + "?flow=InplaceAddBarcodeToExistingProduct&barcode=" + input + "&" + embedded) + "&" + embedded);
}
},
addnewproduct: {
label: '<strong>P</strong> ' + __t('Add as new product'),
className: 'btn-success add-new-product-dialog-button responsive-button ' + addProductWorkflowsAdditionalCssClasses,
callback: function() {
// Not the best place here - this is only relevant when this flow is started from the shopping list item form
// (to select the correct shopping list on return)
if (GetUriParam("list") !== undefined) {
embedded += "&list=" + GetUriParam("list");
}
};
if (!Grocy.FeatureFlags.GROCY_FEATURE_FLAG_DISABLE_BROWSER_BARCODE_CAMERA_SCANNING)
{
buttons.retrycamerascanning = {
label: '<strong>C</strong> <i class="fas fa-camera"></i>',
className: 'btn-primary responsive-button retry-camera-scanning-button',
callback: function()
{
Grocy.Components.ProductPicker.PopupOpen = false;
Grocy.Components.ProductPicker.SetValue('');
$("#barcodescanner-start-button").click();
}
};
Grocy.Components.ProductPicker.PopupOpen = false;
window.location.href = U('/product/new?flow=InplaceNewProductWithName&name=' + encodeURIComponent(lastProductSearchTerm) + '&returnto=' + encodeURIComponent(Grocy.CurrentUrlRelative + "?flow=InplaceNewProductWithName&" + embedded) + "&" + embedded);
}
},
addbarcode: {
label: '<strong>B</strong> ' + __t('Add as barcode to existing product'),
className: 'btn-info add-new-barcode-dialog-button responsive-button',
callback: function() {
Grocy.Components.ProductPicker.PopupOpen = false;
window.location.href = U(Grocy.CurrentUrlRelative + '?flow=InplaceAddBarcodeToExistingProduct&barcode=' + encodeURIComponent(lastProductSearchTerm) + "&" + embedded);
}
},
addnewproductwithbarcode: {
label: '<strong>A</strong> ' + __t('Add as new product and prefill barcode'),
className: 'btn-warning add-new-product-with-barcode-dialog-button responsive-button ' + addProductWorkflowsAdditionalCssClasses,
callback: function() {
Grocy.Components.ProductPicker.PopupOpen = false;
window.location.href = U('/product/new?flow=InplaceNewProductWithBarcode&barcode=' + encodeURIComponent(lastProductSearchTerm) + '&returnto=' + encodeURIComponent(Grocy.CurrentUrlRelative + "?flow=InplaceAddBarcodeToExistingProduct&barcode=" + lastProductSearchTerm + "&" + embedded) + "&" + embedded);
}
}
};
if (!Grocy.FeatureFlags.GROCY_FEATURE_FLAG_DISABLE_BROWSER_BARCODE_CAMERA_SCANNING) {
buttons.retrycamerascanning = {
label: '<strong>C</strong> <i class="fas fa-camera"></i>',
className: 'btn-primary responsive-button retry-camera-scanning-button',
callback: function() {
Grocy.Components.ProductPicker.PopupOpen = false;
Grocy.Components.ProductPicker.Clear();
$("#barcodescanner-start-button").trigger('click');
}
};
}
// The product picker contains only in-stock products on some pages,
// so only show the workflow dialog when the entered input
// does not match in existing product (name) or barcode,
// otherwise an error validation message that the product is not in stock
var existsAsProduct = false;
var existsAsBarcode = false;
Grocy.Api.Get('objects/product_barcodes?query[]=barcode=' + lastProductSearchTerm,
function(barcodeResult) {
if (barcodeResult.length > 0) {
existsAsProduct = true;
}
// The product picker contains only in-stock products on some pages,
// so only show the workflow dialog when the entered input
// does not match in existing product (name) or barcode,
// otherwise an error validation message that the product is not in stock
var existsAsProduct = false;
var existsAsBarcode = false;
Grocy.Api.Get('objects/product_barcodes?query[]=barcode=' + input,
function(barcodeResult)
{
if (barcodeResult.length > 0)
{
Grocy.Api.Get('objects/products?query[]=name=' + lastProductSearchTerm,
function(productResult) {
if (productResult.length > 0) {
existsAsProduct = true;
}
Grocy.Api.Get('objects/products?query[]=name=' + input,
function(productResult)
{
if (productResult.length > 0)
{
existsAsProduct = true;
}
if (!existsAsBarcode && !existsAsProduct)
{
Grocy.Components.ProductPicker.PopupOpen = true;
bootbox.dialog({
message: __t('"%s" could not be resolved to a product, how do you want to proceed?', input),
title: __t('Create or assign product'),
onEscape: function()
{
Grocy.Components.ProductPicker.PopupOpen = false;
Grocy.Components.ProductPicker.SetValue('');
},
size: 'large',
backdrop: true,
closeButton: false,
buttons: buttons
}).on('keypress', function(e)
{
if (e.key === 'B' || e.key === 'b')
{
$('.add-new-barcode-dialog-button').not(".d-none").click();
}
if (e.key === 'p' || e.key === 'P')
{
$('.add-new-product-dialog-button').not(".d-none").click();
}
if (e.key === 'a' || e.key === 'A')
{
$('.add-new-product-with-barcode-dialog-button').not(".d-none").click();
}
if (e.key === 'c' || e.key === 'C')
{
$('.retry-camera-scanning-button').not(".d-none").click();
}
});
}
else
{
Grocy.Components.ProductAmountPicker.Reset();
if (!existsAsBarcode && !existsAsProduct) {
Grocy.Components.ProductPicker.PopupOpen = true;
bootbox.dialog({
message: __t('"%s" could not be resolved to a product, how do you want to proceed?', lastProductSearchTerm),
title: __t('Create or assign product'),
onEscape: function() {
Grocy.Components.ProductPicker.PopupOpen = false;
Grocy.Components.ProductPicker.Clear();
Grocy.FrontendHelpers.ValidateForm('consume-form');
Grocy.Components.ProductPicker.ShowCustomError(__t('This product is not in stock'));
Grocy.Components.ProductPicker.GetInputElement().focus();
},
size: 'large',
backdrop: true,
closeButton: false,
buttons: buttons
}).on('keypress', function(e) {
if (e.key === 'B' || e.key === 'b') {
$('.add-new-barcode-dialog-button').not(".d-none").trigger('click');
}
},
function(xhr)
{
console.error(xhr);
}
);
if (e.key === 'p' || e.key === 'P') {
$('.add-new-product-dialog-button').not(".d-none").trigger('click');
}
if (e.key === 'a' || e.key === 'A') {
$('.add-new-product-with-barcode-dialog-button').not(".d-none").trigger('click');
}
if (e.key === 'c' || e.key === 'C') {
$('.retry-camera-scanning-button').not(".d-none").trigger('click');
}
});
} else {
Grocy.Components.ProductAmountPicker.Reset();
Grocy.Components.ProductPicker.Clear();
Grocy.FrontendHelpers.ValidateForm('consume-form');
Grocy.Components.ProductPicker.ShowCustomError(__t('This product is not in stock'));
Grocy.Components.ProductPicker.Focus();
}
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
},
function(xhr) {
console.error(xhr);
}
}
});
$(document).on("Grocy.BarcodeScanned", function(e, barcode, target)
{
if (!(target == "@productpicker" || target == "undefined" || target == undefined)) // Default target
{
return;
}
// Don't know why the blur event does not fire immediately ... this works...
Grocy.Components.ProductPicker.GetInputElement().focusout();
Grocy.Components.ProductPicker.GetInputElement().focus();
Grocy.Components.ProductPicker.GetInputElement().blur();
Grocy.Components.ProductPicker.GetInputElement().val(barcode);
setTimeout(function()
{
Grocy.Components.ProductPicker.GetInputElement().focusout();
Grocy.Components.ProductPicker.GetInputElement().focus();
Grocy.Components.ProductPicker.GetInputElement().blur();
}, 200);
});
$(document).on("shown.bs.modal", function(e)
{
$(".modal-footer").addClass("d-block").addClass("d-sm-flex");
$(".modal-footer").find("button").addClass("mt-2").addClass("mt-sm-0");
})
// Make that ENTER behaves the same like TAB (trigger blur to start workflows, but only when the dropdown is not opened)
$('#product_id_text_input').keydown(function(event)
{
if (event.keyCode === 13) // Enter
{
if (Grocy.Components.ProductPicker.GetPicker().hasClass("combobox-menu-visible"))
{
return;
}
$("#product_id_text_input").trigger("blur");
}
);
});

View File

@ -1,9 +1,7 @@
$('#save-consume-button').on('click', function(e)
{
$('#save-consume-button').on('click', function(e) {
e.preventDefault();
if ($(".combobox-menu-visible").length)
{
if ($(".combobox-menu-visible").length) {
return;
}
@ -18,51 +16,42 @@
jsonData.spoiled = $('#spoiled').is(':checked');
jsonData.allow_subproduct_substitution = true;
if ($("#use_specific_stock_entry").is(":checked"))
{
if ($("#use_specific_stock_entry").is(":checked")) {
jsonData.stock_entry_id = jsonForm.specific_stock_entry;
}
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) {
jsonData.location_id = $("#location_id").val();
}
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_RECIPES && Grocy.Components.RecipePicker.GetValue().toString().length > 0)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_RECIPES && Grocy.Components.RecipePicker.GetValue().toString().length > 0) {
jsonData.recipe_id = Grocy.Components.RecipePicker.GetValue();
}
var bookingResponse = null;
Grocy.Api.Get('stock/products/' + jsonForm.product_id,
function(productDetails)
{
function(productDetails) {
Grocy.Api.Post(apiUrl, jsonData,
function(result)
{
if (BoolVal(Grocy.UserSettings.scan_mode_consume_enabled))
{
function(result) {
if (BoolVal(Grocy.UserSettings.scan_mode_consume_enabled)) {
Grocy.UISound.Success();
}
bookingResponse = result;
if (GetUriParam("flow") === "InplaceAddBarcodeToExistingProduct")
{
if (GetUriParam("flow") === "InplaceAddBarcodeToExistingProduct") {
var jsonDataBarcode = {};
jsonDataBarcode.barcode = GetUriParam("barcode");
jsonDataBarcode.product_id = jsonForm.product_id;
Grocy.Api.Post('objects/product_barcodes', jsonDataBarcode,
function(result)
{
function(result) {
$("#flow-info-InplaceAddBarcodeToExistingProduct").addClass("d-none");
$('#barcode-lookup-disabled-hint').addClass('d-none');
$('#barcode-lookup-hint').removeClass('d-none');
window.history.replaceState({}, document.title, U("/consume"));
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy("consume-form");
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
}
@ -70,28 +59,21 @@
}
$("#specific_stock_entry").find("option").remove().end().append("<option></option>");
if ($("#use_specific_stock_entry").is(":checked"))
{
if ($("#use_specific_stock_entry").is(":checked")) {
$("#use_specific_stock_entry").click();
}
if (productDetails.product.enable_tare_weight_handling == 1 && !jsonData.exact_amount)
{
if (productDetails.product.enable_tare_weight_handling == 1 && !jsonData.exact_amount) {
var successMessage = __t('Removed %1$s of %2$s from stock', Math.abs(jsonForm.amount - (parseFloat(productDetails.product.tare_weight) + parseFloat(productDetails.stock_amount))) + " " + __n(jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural, true), productDetails.product.name) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockTransaction(\'' + bookingResponse[0].transaction_id + '\')"><i class="fas fa-undo"></i> ' + __t("Undo") + '</a>';
}
else
{
} else {
var successMessage = __t('Removed %1$s of %2$s from stock', Math.abs(jsonForm.amount) + " " + __n(jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural, true), productDetails.product.name) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockTransaction(\'' + bookingResponse[0].transaction_id + '\')"><i class="fas fa-undo"></i> ' + __t("Undo") + '</a>';
}
if (GetUriParam("embedded") !== undefined)
{
if (GetUriParam("embedded") !== undefined) {
window.parent.postMessage(WindowMessageBag("ProductChanged", jsonForm.product_id), Grocy.BaseUrl);
window.parent.postMessage(WindowMessageBag("ShowSuccessMessage", successMessage), Grocy.BaseUrl);
window.parent.postMessage(WindowMessageBag("CloseAllModals"), Grocy.BaseUrl);
}
else
{
} else {
Grocy.FrontendHelpers.EndUiBusy("consume-form");
toastr.success(successMessage);
Grocy.Components.ProductPicker.FinishFlow();
@ -99,54 +81,45 @@
Grocy.Components.ProductAmountPicker.Reset();
$("#display_amount").attr("min", Grocy.DefaultMinAmount);
$("#display_amount").removeAttr("max");
if (BoolVal(Grocy.UserSettings.stock_default_consume_amount_use_quick_consume_amount))
{
if (BoolVal(Grocy.UserSettings.stock_default_consume_amount_use_quick_consume_amount)) {
$('#display_amount').val(productDetails.product.quick_consume_amount);
}
else
{
} else {
$('#display_amount').val(parseFloat(Grocy.UserSettings.stock_default_consume_amount));
}
RefreshLocaleNumberInput();
$(".input-group-productamountpicker").trigger("change");
$("#tare-weight-handling-info").addClass("d-none");
Grocy.Components.ProductPicker.Clear();
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_RECIPES)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_RECIPES) {
Grocy.Components.RecipePicker.Clear();
}
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) {
$("#location_id").find("option").remove().end().append("<option></option>");
}
Grocy.Components.ProductPicker.GetInputElement().focus();
Grocy.Components.ProductPicker.Focus();
Grocy.Components.ProductCard.Refresh(jsonForm.product_id);
Grocy.FrontendHelpers.ValidateForm('consume-form');
$("#consume-exact-amount-group").addClass("d-none");
}
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
Grocy.FrontendHelpers.EndUiBusy("consume-form");
console.error(xhr);
}
);
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy("consume-form");
console.error(xhr);
}
);
});
$('#save-mark-as-open-button').on('click', function(e)
{
$('#save-mark-as-open-button').on('click', function(e) {
e.preventDefault();
if ($(".combobox-menu-visible").length)
{
if ($(".combobox-menu-visible").length) {
return;
}
@ -159,123 +132,98 @@ $('#save-mark-as-open-button').on('click', function(e)
jsonData.amount = jsonForm.amount;
jsonData.allow_subproduct_substitution = true;
if ($("#use_specific_stock_entry").is(":checked"))
{
if ($("#use_specific_stock_entry").is(":checked")) {
jsonData.stock_entry_id = jsonForm.specific_stock_entry;
}
Grocy.Api.Get('stock/products/' + jsonForm.product_id,
function(productDetails)
{
function(productDetails) {
Grocy.Api.Post(apiUrl, jsonData,
function(result)
{
function(result) {
$("#specific_stock_entry").find("option").remove().end().append("<option></option>");
if ($("#use_specific_stock_entry").is(":checked"))
{
if ($("#use_specific_stock_entry").is(":checked")) {
$("#use_specific_stock_entry").click();
}
Grocy.FrontendHelpers.EndUiBusy("consume-form");
toastr.success(__t('Marked %1$s of %2$s as opened', parseFloat(jsonForm.amount).toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts }) + " " + __n(jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural, true), productDetails.product.name) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockTransaction(\'' + result[0].transaction_id + '\')"><i class="fas fa-undo"></i> ' + __t("Undo") + '</a>');
toastr.success(__t('Marked %1$s of %2$s as opened', parseFloat(jsonForm.amount).toLocaleString({
minimumFractionDigits: 0,
maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts
}) + " " + __n(jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural, true), productDetails.product.name) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockTransaction(\'' + result[0].transaction_id + '\')"><i class="fas fa-undo"></i> ' + __t("Undo") + '</a>');
if (BoolVal(Grocy.UserSettings.stock_default_consume_amount_use_quick_consume_amount))
{
if (BoolVal(Grocy.UserSettings.stock_default_consume_amount_use_quick_consume_amount)) {
$('#display_amount').val(productDetails.product.quick_consume_amount);
}
else
{
} else {
$('#display_amount').val(parseFloat(Grocy.UserSettings.stock_default_consume_amount));
}
RefreshLocaleNumberInput();
$(".input-group-productamountpicker").trigger("change");
Grocy.Components.ProductPicker.Clear();
Grocy.Components.ProductPicker.GetInputElement().focus();
Grocy.Components.ProductPicker.Focus();
Grocy.FrontendHelpers.ValidateForm('consume-form');
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy("consume-form");
console.error(xhr);
}
);
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy("consume-form");
console.error(xhr);
}
);
});
var sumValue = 0;
$("#location_id").on('change', function(e)
{
$("#location_id").on('change', function(e) {
var locationId = $(e.target).val();
$("#specific_stock_entry").find("option").remove().end().append("<option></option>");
if ($("#use_specific_stock_entry").is(":checked"))
{
$("#use_specific_stock_entry").click();
if ($("#use_specific_stock_entry").is(":checked")) {
$("#use_specific_stock_entry").trigger('click');
}
if (GetUriParam("embedded") !== undefined)
{
if (GetUriParam("embedded") !== undefined) {
OnLocationChange(locationId, GetUriParam('stockId'));
}
else
{
} else {
// try to get stock id from grocycode
if ($("#product_id").data("grocycode"))
{
var gc = $("#product_id").attr("barcode").split(":");
if (gc.length == 4)
{
Grocy.Api.Get("stock/products/" + Grocy.Components.ProductPicker.GetValue() + '/entries?query[]=stock_id=' + gc[3],
function(stockEntries)
{
if (Grocy.Components.ProductPicker.GetState('grocycode')) {
var gc = Grocy.Components.ProductPicker.GetState('barcode').split(':');
if (gc.length == 4) {
Grocy.Api.Get("stock/products/" + Grocy.Components.ProductPicker.GetValue() + '/entries?query%5B%5D=stock_id%3D' + gc[3],
function(stockEntries) {
OnLocationChange(stockEntries[0].location_id, gc[3]);
$('#display_amount').val(stockEntries[0].amount);
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
}
}
else
{
} else {
OnLocationChange(locationId, null);
}
}
});
function OnLocationChange(locationId, stockId)
{
function OnLocationChange(locationId, stockId) {
sumValue = 0;
if (locationId)
{
if ($("#location_id").val() != locationId)
{
if (locationId) {
if ($("#location_id").val() != locationId) {
$("#location_id").val(locationId);
}
Grocy.Api.Get("stock/products/" + Grocy.Components.ProductPicker.GetValue() + '/entries?include_sub_products=true',
function(stockEntries)
{
stockEntries.forEach(stockEntry =>
{
function(stockEntries) {
stockEntries.forEach(stockEntry => {
var openTxt = __t("Not opened");
if (stockEntry.open == 1)
{
if (stockEntry.open == 1) {
openTxt = __t("Opened");
}
if (stockEntry.location_id == locationId)
{
if ($("#specific_stock_entry option[value='" + stockEntry.stock_id + "']").length == 0)
{
if (stockEntry.location_id == locationId) {
if ($("#specific_stock_entry option[value='" + stockEntry.stock_id + "']").length == 0) {
$("#specific_stock_entry").append($("<option>", {
value: stockEntry.stock_id,
amount: stockEntry.amount,
@ -285,8 +233,7 @@ function OnLocationChange(locationId, stockId)
sumValue = sumValue + parseFloat(stockEntry.amount || 0);
if (stockEntry.stock_id == stockId)
{
if (stockEntry.stock_id == stockId) {
$("#use_specific_stock_entry").click();
$("#specific_stock_entry").val(stockId);
}
@ -294,63 +241,51 @@ function OnLocationChange(locationId, stockId)
});
Grocy.Api.Get('stock/products/' + Grocy.Components.ProductPicker.GetValue(),
function(productDetails)
{
function(productDetails) {
current_productDetails = productDetails;
RefreshForm();
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
if (document.getElementById("product_id").getAttribute("barcode") == "null")
{
if (document.getElementById("product_id").getAttribute("barcode") == "null") {
ScanModeSubmit();
}
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
}
}
Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
{
if (BoolVal(Grocy.UserSettings.scan_mode_consume_enabled))
{
Grocy.Components.ProductPicker.OnChange(function(e) {
if (BoolVal(Grocy.UserSettings.scan_mode_consume_enabled)) {
Grocy.UISound.BarcodeScannerBeep();
}
$("#specific_stock_entry").find("option").remove().end().append("<option></option>");
if ($("#use_specific_stock_entry").is(":checked"))
{
if ($("#use_specific_stock_entry").is(":checked")) {
$("#use_specific_stock_entry").click();
}
$("#location_id").val("");
var productId = $(e.target).val();
if (productId)
{
if (productId) {
Grocy.Components.ProductCard.Refresh(productId);
Grocy.Api.Get('stock/products/' + productId,
function(productDetails)
{
function(productDetails) {
current_productDetails = productDetails;
Grocy.Components.ProductAmountPicker.Reload(productDetails.product.id, productDetails.quantity_unit_stock.id);
Grocy.Components.ProductAmountPicker.SetQuantityUnit(productDetails.quantity_unit_stock.id);
if (BoolVal(Grocy.UserSettings.stock_default_consume_amount_use_quick_consume_amount))
{
if (BoolVal(Grocy.UserSettings.stock_default_consume_amount_use_quick_consume_amount)) {
$('#display_amount').val(productDetails.product.quick_consume_amount);
}
else
{
} else {
$('#display_amount').val(parseFloat(Grocy.UserSettings.stock_default_consume_amount));
}
RefreshLocaleNumberInput();
@ -358,13 +293,10 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
$("#location_id").find("option").remove().end().append("<option></option>");
Grocy.Api.Get("stock/products/" + productId + '/locations?include_sub_products=true',
function(stockLocations)
{
function(stockLocations) {
var setDefault = 0;
stockLocations.forEach(stockLocation =>
{
if (productDetails.location.id == stockLocation.location_id)
{
stockLocations.forEach(stockLocation => {
if (productDetails.location.id == stockLocation.location_id) {
$("#location_id").append($("<option>", {
value: stockLocation.location_id,
text: stockLocation.location_name + " (" + __t("Default location") + ")"
@ -372,41 +304,32 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
$("#location_id").val(productDetails.location.id);
$("#location_id").trigger('change');
setDefault = 1;
}
else
{
} else {
$("#location_id").append($("<option>", {
value: stockLocation.location_id,
text: stockLocation.location_name
}));
}
if (setDefault == 0)
{
if (setDefault == 0) {
$("#location_id").val(stockLocation.location_id);
$("#location_id").trigger('change');
}
});
if (document.getElementById("product_id").getAttribute("barcode") != "null")
{
if (document.getElementById("product_id").getAttribute("barcode") != "null") {
Grocy.Api.Get('objects/product_barcodes?query[]=barcode=' + document.getElementById("product_id").getAttribute("barcode"),
function(barcodeResult)
{
if (barcodeResult != null)
{
function(barcodeResult) {
if (barcodeResult != null) {
var barcode = barcodeResult[0];
if (barcode != null)
{
if (barcode.amount != null && !barcode.amount.isEmpty())
{
if (barcode != null) {
if (barcode.amount != null && !barcode.amount.isEmpty()) {
$("#display_amount").val(barcode.amount);
$("#display_amount").select();
}
if (barcode.qu_id != null && !barcode.qu_id.isEmpty())
{
if (barcode.qu_id != null && !barcode.qu_id.isEmpty()) {
Grocy.Components.ProductAmountPicker.SetQuantityUnit(barcode.qu_id);
}
@ -417,27 +340,22 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
}
}
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
}
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
if (productDetails.product.enable_tare_weight_handling == 1)
{
if (productDetails.product.enable_tare_weight_handling == 1) {
$("#display_amount").attr("min", productDetails.product.tare_weight);
$('#display_amount').attr('max', parseFloat(productDetails.stock_amount) + parseFloat(productDetails.product.tare_weight));
$("#tare-weight-handling-info").removeClass("d-none");
}
else
{
} else {
$("#display_amount").attr("min", Grocy.DefaultMinAmount);
$("#tare-weight-handling-info").addClass("d-none");
}
@ -446,17 +364,13 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
Grocy.FrontendHelpers.ValidateForm('consume-form');
$('#display_amount').focus();
if (productDetails.stock_amount == productDetails.stock_amount_opened || productDetails.product.enable_tare_weight_handling == 1)
{
if (productDetails.stock_amount == productDetails.stock_amount_opened || productDetails.product.enable_tare_weight_handling == 1) {
$("#save-mark-as-open-button").addClass("disabled");
}
else
{
} else {
$("#save-mark-as-open-button").removeClass("disabled");
}
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
@ -467,29 +381,24 @@ $('#display_amount').val(parseFloat(Grocy.UserSettings.stock_default_consume_amo
$(".input-group-productamountpicker").trigger("change");
Grocy.FrontendHelpers.ValidateForm('consume-form');
$('#display_amount').on('focus', function(e)
{
$('#display_amount').on('focus', function(e) {
$(this).select();
});
$('#price').on('focus', function(e)
{
$('#price').on('focus', function(e) {
$(this).select();
});
$('#consume-form input').keyup(function(event)
{
$('#consume-form input').keyup(function(event) {
Grocy.FrontendHelpers.ValidateForm('consume-form');
});
$('#consume-form select').change(function(event)
{
$('#consume-form select').change(function(event) {
Grocy.FrontendHelpers.ValidateForm('consume-form');
});
$('#consume-form input').keydown(function(event)
{
$('#consume-form input').keydown(function(event) {
if (event.keyCode === 13) //Enter
{
event.preventDefault();
@ -497,58 +406,43 @@ $('#consume-form input').keydown(function(event)
if (document.getElementById('consume-form').checkValidity() === false) //There is at least one validation error
{
return false;
}
else
{
} else {
$('#save-consume-button').click();
}
}
});
$("#specific_stock_entry").on("change", function(e)
{
if ($(e.target).val() == "")
{
$("#specific_stock_entry").on("change", function(e) {
if ($(e.target).val() == "") {
sumValue = 0;
Grocy.Api.Get("stock/products/" + Grocy.Components.ProductPicker.GetValue() + '/entries?include_sub_products=true',
function(stockEntries)
{
stockEntries.forEach(stockEntry =>
{
if (stockEntry.location_id == $("#location_id").val() || stockEntry.location_id == "")
{
function(stockEntries) {
stockEntries.forEach(stockEntry => {
if (stockEntry.location_id == $("#location_id").val() || stockEntry.location_id == "") {
sumValue = sumValue + parseFloat(stockEntry.amount_aggregated);
}
});
$("#display_amount").attr("max", sumValue);
if (sumValue == 0)
{
if (sumValue == 0) {
$("#display_amount").parent().find(".invalid-feedback").text(__t('There are no units available at this location'));
}
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
}
else
{
} else {
$("#display_amount").attr("max", $('option:selected', this).attr('amount'));
}
});
$("#use_specific_stock_entry").on("change", function()
{
$("#use_specific_stock_entry").on("change", function() {
var value = $(this).is(":checked");
if (value)
{
if (value) {
$("#specific_stock_entry").removeAttr("disabled");
$("#specific_stock_entry").attr("required", "");
}
else
{
} else {
$("#specific_stock_entry").attr("disabled", "");
$("#specific_stock_entry").removeAttr("required");
$("#specific_stock_entry").val("");
@ -558,146 +452,114 @@ $("#use_specific_stock_entry").on("change", function()
Grocy.FrontendHelpers.ValidateForm("consume-form");
});
$("#qu_id").on("change", function()
{
$("#qu_id").on("change", function() {
RefreshForm();
});
function UndoStockBooking(bookingId)
{
function UndoStockBooking(bookingId) {
Grocy.Api.Post('stock/bookings/' + bookingId.toString() + '/undo', {},
function(result)
{
function(result) {
toastr.success(__t("Booking successfully undone"));
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
};
function UndoStockTransaction(transactionId)
{
function UndoStockTransaction(transactionId) {
Grocy.Api.Post('stock/transactions/' + transactionId.toString() + '/undo', {},
function(result)
{
function(result) {
toastr.success(__t("Transaction successfully undone"));
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
};
if (GetUriParam("embedded") !== undefined)
{
if (GetUriParam("embedded") !== undefined) {
var locationId = GetUriParam('locationId');
if (typeof locationId === 'undefined')
{
Grocy.Components.ProductPicker.GetPicker().trigger('change');
}
else
{
if (typeof locationId === 'undefined') {
Grocy.Components.ProductPicker.Validate();
} else {
$("#location_id").val(locationId);
$("#location_id").trigger('change');
$("#use_specific_stock_entry").click();
$("#use_specific_stock_entry").trigger('click');
$("#use_specific_stock_entry").trigger('change');
Grocy.Components.ProductPicker.GetPicker().trigger('change');
Grocy.Components.ProductPicker.Validate();
}
}
// Default input field
Grocy.Components.ProductPicker.GetInputElement().focus();
Grocy.Components.ProductPicker.Focus();
$(document).on("change", "#scan-mode", function(e)
{
if ($(this).prop("checked"))
{
$(document).on("change", "#scan-mode", function(e) {
if ($(this).prop("checked")) {
Grocy.UISound.AskForPermission();
}
});
$("#scan-mode-button").on("click", function(e)
{
$("#scan-mode-button").on("click", function(e) {
document.activeElement.blur();
$("#scan-mode").click();
$("#scan-mode").trigger('click');
$("#scan-mode-button").toggleClass("btn-success").toggleClass("btn-danger");
if ($("#scan-mode").prop("checked"))
{
if ($("#scan-mode").prop("checked")) {
$("#scan-mode-status").text(__t("on"));
}
else
{
} else {
$("#scan-mode-status").text(__t("off"));
}
});
$('#consume-exact-amount').on('change', RefreshForm);
var current_productDetails;
function RefreshForm()
{
function RefreshForm() {
var productDetails = current_productDetails;
if (!productDetails)
{
if (!productDetails) {
return;
}
if (productDetails.product.enable_tare_weight_handling == 1)
{
if (productDetails.product.enable_tare_weight_handling == 1) {
$("#consume-exact-amount-group").removeClass("d-none");
}
else
{
} else {
$("#consume-exact-amount-group").addClass("d-none");
}
if (productDetails.product.enable_tare_weight_handling == 1 && !$('#consume-exact-amount').is(':checked'))
{
if (productDetails.product.enable_tare_weight_handling == 1 && !$('#consume-exact-amount').is(':checked')) {
$("#display_amount").attr("min", productDetails.product.tare_weight);
$('#display_amount').attr('max', sumValue + parseFloat(productDetails.product.tare_weight));
$("#tare-weight-handling-info").removeClass("d-none");
}
else
{
} else {
$("#tare-weight-handling-info").addClass("d-none");
$("#display_amount").attr("min", Grocy.DefaultMinAmount);
$('#display_amount').attr('max', sumValue * $("#qu_id option:selected").attr("data-qu-factor"));
if (sumValue == 0)
{
if (sumValue == 0) {
$("#display_amount").parent().find(".invalid-feedback").text(__t('There are no units available at this location'));
}
}
if (productDetails.has_childs)
{
if (productDetails.has_childs) {
$("#display_amount").removeAttr("max");
}
Grocy.FrontendHelpers.ValidateForm("consume-form");
}
function ScanModeSubmit(singleUnit = true)
{
if (BoolVal(Grocy.UserSettings.scan_mode_consume_enabled))
{
if (singleUnit)
{
function ScanModeSubmit(singleUnit = true) {
if (BoolVal(Grocy.UserSettings.scan_mode_consume_enabled)) {
if (singleUnit) {
$("#display_amount").val(1);
$(".input-group-productamountpicker").trigger("change");
}
Grocy.FrontendHelpers.ValidateForm("consume-form");
if (document.getElementById("consume-form").checkValidity() === true)
{
if (document.getElementById("consume-form").checkValidity() === true) {
$('#save-consume-button').click();
}
else
{
} else {
toastr.warning(__t("Scan mode is on but not all required fields could be populated automatically"));
Grocy.UISound.Error();
}

View File

@ -1,9 +1,7 @@
$('#save-inventory-button').on('click', function(e)
{
$('#save-inventory-button').on('click', function(e) {
e.preventDefault();
if ($(".combobox-menu-visible").length)
{
if ($(".combobox-menu-visible").length) {
return;
}
@ -11,11 +9,9 @@
Grocy.FrontendHelpers.BeginUiBusy("inventory-form");
Grocy.Api.Get('stock/products/' + jsonForm.product_id,
function(productDetails)
{
function(productDetails) {
var price = "";
if (!jsonForm.price.toString().isEmpty())
{
if (!jsonForm.price.toString().isEmpty()) {
price = parseFloat(jsonForm.price).toFixed(Grocy.UserSettings.stock_decimal_places_prices);
}
@ -23,16 +19,13 @@
jsonData.new_amount = jsonForm.amount;
jsonData.best_before_date = Grocy.Components.DateTimePicker.GetValue();
jsonData.stock_label_type = jsonForm.stock_label_type;
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) {
jsonData.shopping_location_id = Grocy.Components.ShoppingLocationPicker.GetValue();
}
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) {
jsonData.location_id = Grocy.Components.LocationPicker.GetValue();
}
if (Grocy.UserSettings.show_purchased_date_on_purchase)
{
if (Grocy.UserSettings.show_purchased_date_on_purchase) {
jsonData.purchased_date = Grocy.Components.DateTimePicker2.GetValue();
}
@ -41,69 +34,57 @@
var bookingResponse = null;
Grocy.Api.Post('stock/products/' + jsonForm.product_id + '/inventory', jsonData,
function(result)
{
function(result) {
bookingResponse = result;
if (GetUriParam("flow") === "InplaceAddBarcodeToExistingProduct")
{
if (GetUriParam("flow") === "InplaceAddBarcodeToExistingProduct") {
var jsonDataBarcode = {};
jsonDataBarcode.barcode = GetUriParam("barcode");
jsonDataBarcode.product_id = jsonForm.product_id;
jsonDataBarcode.shopping_location_id = jsonForm.shopping_location_id;
Grocy.Api.Post('objects/product_barcodes', jsonDataBarcode,
function(result)
{
function(result) {
$("#flow-info-InplaceAddBarcodeToExistingProduct").addClass("d-none");
$('#barcode-lookup-disabled-hint').addClass('d-none');
$('#barcode-lookup-hint').removeClass('d-none');
window.history.replaceState({}, document.title, U("/inventory"));
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy("inventory-form");
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
}
);
}
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_LABEL_PRINTER && parseFloat($("#display_amount").attr("data-estimated-booking-amount")) > 0)
{
if (Grocy.Webhooks.labelprinter !== undefined)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_LABEL_PRINTER && parseFloat($("#display_amount").attr("data-estimated-booking-amount")) > 0) {
if (Grocy.Webhooks.labelprinter !== undefined) {
if (jsonForm.stock_label_type == 1) // Single label
{
var webhookData = {};
webhookData.product = productDetails.product.name;
webhookData.grocycode = 'grcy:p:' + jsonForm.product_id + ":" + result[0].stock_id;
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING) {
webhookData.due_date = __t('DD') + ': ' + result[0].best_before_date;
}
Grocy.FrontendHelpers.RunWebhook(Grocy.Webhooks.labelprinter, webhookData);
}
else if (jsonForm.stock_label_type == 2) // Label per unit
} else if (jsonForm.stock_label_type == 2) // Label per unit
{
Grocy.Api.Get('stock/transactions/' + result[0].transaction_id,
function(stockEntries)
{
stockEntries.forEach(stockEntry =>
{
function(stockEntries) {
stockEntries.forEach(stockEntry => {
var webhookData = {};
webhookData.product = productDetails.product.name;
webhookData.grocycode = 'grcy:p:' + jsonForm.product_id + ":" + stockEntry.stock_id;
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING) {
webhookData.due_date = __t('DD') + ': ' + result[0].best_before_date;
}
Grocy.FrontendHelpers.RunWebhook(Grocy.Webhooks.labelprinter, webhookData);
});
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
@ -112,18 +93,14 @@
}
Grocy.Api.Get('stock/products/' + jsonForm.product_id,
function(result)
{
function(result) {
var successMessage = __t('Stock amount of %1$s is now %2$s', result.product.name, result.stock_amount + " " + __n(result.stock_amount, result.quantity_unit_stock.name, result.quantity_unit_stock.name_plural, true)) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockTransaction(\'' + bookingResponse[0].transaction_id + '\')"><i class="fas fa-undo"></i> ' + __t("Undo") + '</a>';
if (GetUriParam("embedded") !== undefined)
{
if (GetUriParam("embedded") !== undefined) {
window.parent.postMessage(WindowMessageBag("ProductChanged", jsonForm.product_id), Grocy.BaseUrl);
window.parent.postMessage(WindowMessageBag("ShowSuccessMessage", successMessage), Grocy.BaseUrl);
window.parent.postMessage(WindowMessageBag("CloseAllModals"), Grocy.BaseUrl);
}
else
{
} else {
Grocy.FrontendHelpers.EndUiBusy("inventory-form");
toastr.success(successMessage);
Grocy.Components.ProductPicker.FinishFlow();
@ -137,125 +114,99 @@
$(".input-group-productamountpicker").trigger("change");
$('#price').val('');
Grocy.Components.DateTimePicker.Clear();
Grocy.Components.ProductPicker.SetValue('');
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
{
Grocy.Components.ProductPicker.Clear();
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) {
Grocy.Components.ShoppingLocationPicker.SetValue('');
}
Grocy.Components.ProductPicker.GetInputElement().focus();
Grocy.Components.ProductPicker.Focus();
Grocy.Components.ProductCard.Refresh(jsonForm.product_id);
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_LABEL_PRINTER)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_LABEL_PRINTER) {
$("#stock_label_type").val(0);
}
Grocy.FrontendHelpers.ValidateForm('inventory-form');
}
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr);
}
);
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy("inventory-form");
console.error(xhr);
}
);
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy("inventory-form");
console.error(xhr);
}
);
});
Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
{
Grocy.Components.ProductPicker.OnChange(function(e) {
var productId = $(e.target).val();
if (productId)
{
if (productId) {
Grocy.Components.ProductCard.Refresh(productId);
Grocy.Api.Get('stock/products/' + productId,
function(productDetails)
{
function(productDetails) {
Grocy.Components.ProductAmountPicker.Reload(productDetails.product.id, productDetails.quantity_unit_stock.id);
Grocy.Components.ProductAmountPicker.SetQuantityUnit(productDetails.quantity_unit_stock.id);
$('#display_amount').attr("data-stock-amount", productDetails.stock_amount)
$('#display_amount').attr('data-not-equal', productDetails.stock_amount * $("#qu_id option:selected").attr("data-qu-factor"));
if (productDetails.product.enable_tare_weight_handling == 1)
{
if (productDetails.product.enable_tare_weight_handling == 1) {
$("#display_amount").attr("min", productDetails.product.tare_weight);
$("#tare-weight-handling-info").removeClass("d-none");
}
else
{
} else {
$("#display_amount").attr("min", "0");
$("#tare-weight-handling-info").addClass("d-none");
}
$('#price').val(parseFloat(productDetails.last_price));
RefreshLocaleNumberInput();
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) {
Grocy.Components.ShoppingLocationPicker.SetId(productDetails.last_shopping_location_id);
}
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) {
Grocy.Components.LocationPicker.SetId(productDetails.location.id);
}
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
{
if (productDetails.product.default_best_before_days.toString() !== '0')
{
if (productDetails.product.default_best_before_days == -1)
{
if (!$("#datetimepicker-shortcut").is(":checked"))
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING) {
if (productDetails.product.default_best_before_days.toString() !== '0') {
if (productDetails.product.default_best_before_days == -1) {
if (!$("#datetimepicker-shortcut").is(":checked")) {
$("#datetimepicker-shortcut").click();
}
}
else
{
} else {
Grocy.Components.DateTimePicker.SetValue(moment().add(productDetails.product.default_best_before_days, 'days').format('YYYY-MM-DD'));
}
}
}
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_LABEL_PRINTER)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_LABEL_PRINTER) {
$("#stock_label_type").val(productDetails.product.default_stock_label_type);
}
if (document.getElementById("product_id").getAttribute("barcode") != "null")
{
if (document.getElementById("product_id").getAttribute("barcode") != "null") {
Grocy.Api.Get('objects/product_barcodes?query[]=barcode=' + document.getElementById("product_id").getAttribute("barcode"),
function(barcodeResult)
{
if (barcodeResult != null)
{
function(barcodeResult) {
if (barcodeResult != null) {
var barcode = barcodeResult[0];
if (barcode != null)
{
if (barcode.amount != null && !barcode.amount.isEmpty())
{
if (barcode != null) {
if (barcode.amount != null && !barcode.amount.isEmpty()) {
$("#display_amount").val(barcode.amount);
$("#display_amount").select();
}
if (barcode.qu_id != null && !barcode.qu_id.isEmpty())
{
if (barcode.qu_id != null && !barcode.qu_id.isEmpty()) {
Grocy.Components.ProductAmountPicker.SetQuantityUnit(barcode.qu_id);
}
@ -265,8 +216,7 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
}
}
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
@ -278,8 +228,7 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
$('#display_amount').focus();
$('#display_amount').trigger('keyup');
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
@ -290,39 +239,29 @@ $('#display_amount').val('');
$(".input-group-productamountpicker").trigger("change");
Grocy.FrontendHelpers.ValidateForm('inventory-form');
if (Grocy.Components.ProductPicker.InAnyFlow() === false && GetUriParam("embedded") === undefined)
{
Grocy.Components.ProductPicker.GetInputElement().focus();
}
else
{
Grocy.Components.ProductPicker.GetPicker().trigger('change');
if (Grocy.Components.ProductPicker.InAnyFlow() === false && GetUriParam("embedded") === undefined) {
Grocy.Components.ProductPicker.Focus();
} else {
Grocy.Components.ProductPicker.Validate();
if (Grocy.Components.ProductPicker.InProductModifyWorkflow())
{
Grocy.Components.ProductPicker.GetInputElement().focus();
if (Grocy.Components.ProductPicker.InProductModifyWorkflow()) {
Grocy.Components.ProductPicker.Focus();
}
}
$('#display_amount').on('focus', function(e)
{
if (Grocy.Components.ProductPicker.GetValue().length === 0)
{
Grocy.Components.ProductPicker.GetInputElement().focus();
}
else
{
$('#display_amount').on('focus', function(e) {
if (Grocy.Components.ProductPicker.GetValue().length === 0) {
Grocy.Components.ProductPicker.Focus();
} else {
$(this).select();
}
});
$('#inventory-form input').keyup(function(event)
{
$('#inventory-form input').keyup(function(event) {
Grocy.FrontendHelpers.ValidateForm('inventory-form');
});
$('#inventory-form input').keydown(function(event)
{
$('#inventory-form input').keydown(function(event) {
if (event.keyCode === 13) //Enter
{
event.preventDefault();
@ -330,46 +269,37 @@ $('#inventory-form input').keydown(function(event)
if (document.getElementById('inventory-form').checkValidity() === false) //There is at least one validation error
{
return false;
}
else
{
} else {
$('#save-inventory-button').click();
}
}
});
$('#qu_id').on('change', function(e)
{
$('#qu_id').on('change', function(e) {
$('#display_amount').attr('data-not-equal', parseFloat($('#display_amount').attr('data-stock-amount')) * parseFloat($("#qu_id option:selected").attr("data-qu-factor")));
Grocy.FrontendHelpers.ValidateForm('inventory-form');
});
Grocy.Components.DateTimePicker.GetInputElement().on('change', function(e)
{
Grocy.Components.DateTimePicker.GetInputElement().on('change', function(e) {
Grocy.FrontendHelpers.ValidateForm('inventory-form');
});
Grocy.Components.DateTimePicker.GetInputElement().on('keypress', function(e)
{
Grocy.Components.DateTimePicker.GetInputElement().on('keypress', function(e) {
Grocy.FrontendHelpers.ValidateForm('inventory-form');
});
$('#display_amount').on('keyup', function(e)
{
$('#display_amount').on('keyup', function(e) {
var productId = Grocy.Components.ProductPicker.GetValue();
var newAmount = parseFloat($('#amount').val());
if (productId)
{
if (productId) {
Grocy.Api.Get('stock/products/' + productId,
function(productDetails)
{
function(productDetails) {
var productStockAmount = parseFloat(productDetails.stock_amount || parseFloat('0'));
var containerWeight = parseFloat("0");
if (productDetails.product.enable_tare_weight_handling == 1)
{
if (productDetails.product.enable_tare_weight_handling == 1) {
containerWeight = parseFloat(productDetails.product.tare_weight);
}
@ -378,71 +308,54 @@ $('#display_amount').on('keyup', function(e)
estimatedBookingAmount = Math.abs(estimatedBookingAmount);
$('#inventory-change-info').removeClass('d-none');
if (productDetails.product.enable_tare_weight_handling == 1 && newAmount < containerWeight)
{
if (productDetails.product.enable_tare_weight_handling == 1 && newAmount < containerWeight) {
$('#inventory-change-info').addClass('d-none');
}
else if (newAmount > productStockAmount + containerWeight)
{
} else if (newAmount > productStockAmount + containerWeight) {
$('#inventory-change-info').text(__t('This means %s will be added to stock', estimatedBookingAmount.toLocaleString() + ' ' + __n(estimatedBookingAmount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural, true)));
Grocy.Components.DateTimePicker.GetInputElement().attr('required', '');
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) {
Grocy.Components.LocationPicker.GetInputElement().attr('required', '');
}
}
else if (newAmount < productStockAmount + containerWeight)
{
} else if (newAmount < productStockAmount + containerWeight) {
$('#inventory-change-info').text(__t('This means %s will be removed from stock', estimatedBookingAmount.toLocaleString() + ' ' + __n(estimatedBookingAmount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural, true)));
Grocy.Components.DateTimePicker.GetInputElement().removeAttr('required');
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) {
Grocy.Components.LocationPicker.GetInputElement().removeAttr('required');
}
}
else if (newAmount == productStockAmount)
{
} else if (newAmount == productStockAmount) {
$('#inventory-change-info').addClass('d-none');
}
if (!Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
{
if (!Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING) {
Grocy.Components.DateTimePicker.GetInputElement().removeAttr('required');
}
Grocy.FrontendHelpers.ValidateForm('inventory-form');
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
}
});
function UndoStockBooking(bookingId)
{
function UndoStockBooking(bookingId) {
Grocy.Api.Post('stock/bookings/' + bookingId.toString() + '/undo', {},
function(result)
{
function(result) {
toastr.success(__t("Booking successfully undone"));
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
};
function UndoStockTransaction(transactionId)
{
function UndoStockTransaction(transactionId) {
Grocy.Api.Post('stock/transactions/' + transactionId.toString() + '/undo', {},
function(result)
{
function(result) {
toastr.success(__t("Transaction successfully undone"));
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);

File diff suppressed because it is too large Load Diff

View File

@ -1,85 +1,59 @@
function saveProductPicture(result, location, jsonData)
{
function saveProductPicture(result, location, jsonData) {
var productId = Grocy.EditObjectId || result.created_object_id;
Grocy.EditObjectId = productId; // Grocy.EditObjectId is not yet set when adding a product
Grocy.Components.UserfieldsForm.Save(() =>
{
if (jsonData.hasOwnProperty("picture_file_name") && !Grocy.DeleteProductPictureOnSave)
{
Grocy.Components.UserfieldsForm.Save(() => {
if (jsonData.hasOwnProperty("picture_file_name") && !Grocy.DeleteProductPictureOnSave) {
Grocy.Api.UploadFile($("#product-picture")[0].files[0], 'productpictures', jsonData.picture_file_name,
(result) =>
{
if (Grocy.ProductEditFormRedirectUri == "reload")
{
(result) => {
if (Grocy.ProductEditFormRedirectUri == "reload") {
window.location.reload();
return
}
var returnTo = GetUriParam('returnto');
if (GetUriParam("closeAfterCreation") !== undefined)
{
if (GetUriParam("closeAfterCreation") !== undefined) {
window.close();
}
else if (returnTo !== undefined)
{
if (GetUriParam("flow") !== undefined)
{
} else if (returnTo !== undefined) {
if (GetUriParam("flow") !== undefined) {
window.location.href = U(returnTo) + '&product-name=' + encodeURIComponent($('#name').val());
}
else
{
} else {
window.location.href = U(returnTo);
}
}
else
{
} else {
window.location.href = U(location + productId);
}
},
(xhr) =>
{
(xhr) => {
Grocy.FrontendHelpers.EndUiBusy("product-form");
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
}
);
}
else
{
if (Grocy.ProductEditFormRedirectUri == "reload")
{
} else {
if (Grocy.ProductEditFormRedirectUri == "reload") {
window.location.reload();
return
}
var returnTo = GetUriParam('returnto');
if (GetUriParam("closeAfterCreation") !== undefined)
{
if (GetUriParam("closeAfterCreation") !== undefined) {
window.close();
}
else if (returnTo !== undefined)
{
if (GetUriParam("flow") !== undefined)
{
} else if (returnTo !== undefined) {
if (GetUriParam("flow") !== undefined) {
window.location.href = U(returnTo) + '&product-name=' + encodeURIComponent($('#name').val());
}
else
{
} else {
window.location.href = U(returnTo);
}
}
else
{
} else {
window.location.href = U(location + productId);
}
}
});
}
$('.save-product-button').on('click', function(e)
{
$('.save-product-button').on('click', function(e) {
e.preventDefault();
var jsonData = $('#product-form').serializeJSON();
@ -88,42 +62,35 @@ $('.save-product-button').on('click', function(e)
jsonData.parent_product_id = parentProductId;
Grocy.FrontendHelpers.BeginUiBusy("product-form");
if (jsonData.parent_product_id.toString().isEmpty())
{
if (jsonData.parent_product_id.toString().isEmpty()) {
jsonData.parent_product_id = null;
}
if ($("#product-picture")[0].files.length > 0)
{
if ($("#product-picture")[0].files.length > 0) {
var someRandomStuff = Math.random().toString(36).substring(2, 100) + Math.random().toString(36).substring(2, 100);
jsonData.picture_file_name = someRandomStuff + CleanFileName($("#product-picture")[0].files[0].name);
}
const location = $(e.currentTarget).attr('data-location') == 'return' ? '/products?product=' : '/product/';
if (Grocy.EditMode == 'create')
{
if (Grocy.EditMode == 'create') {
Grocy.Api.Post('objects/products', jsonData,
(result) => saveProductPicture(result, location, jsonData),
(xhr) =>
{
(xhr) => {
Grocy.FrontendHelpers.EndUiBusy("product-form");
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
});
return;
}
if (Grocy.DeleteProductPictureOnSave)
{
if (Grocy.DeleteProductPictureOnSave) {
jsonData.picture_file_name = null;
Grocy.Api.DeleteFile(Grocy.ProductPictureFileName, 'productpictures', {},
function(result)
{
function(result) {
// Nothing to do
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy("product-form");
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
}
@ -132,55 +99,44 @@ $('.save-product-button').on('click', function(e)
Grocy.Api.Put('objects/products/' + Grocy.EditObjectId, jsonData,
(result) => saveProductPicture(result, location, jsonData),
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy("product-form");
console.error(xhr);
}
);
});
if (Grocy.EditMode == "edit")
{
if (Grocy.EditMode == "edit") {
Grocy.Api.Get('objects/stock_log?limit=1&query[]=product_id=' + Grocy.EditObjectId,
function(productJournalEntries)
{
if (productJournalEntries.length == 0)
{
function(productJournalEntries) {
if (productJournalEntries.length == 0) {
$('#qu_id_stock').removeAttr("disabled");
}
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
}
if (GetUriParam("flow") == "InplaceNewProductWithName")
{
if (GetUriParam("flow") == "InplaceNewProductWithName") {
$('#name').val(GetUriParam("name"));
$('#name').focus();
}
if (GetUriParam("flow") !== undefined || GetUriParam("returnto") !== undefined)
{
if (GetUriParam("flow") !== undefined || GetUriParam("returnto") !== undefined) {
$("#save-hint").addClass("d-none");
$(".save-product-button[data-location='return']").addClass("d-none");
}
$('.input-group-qu').on('change', function(e)
{
$('.input-group-qu').on('change', function(e) {
var quIdPurchase = $("#qu_id_purchase").val();
var quIdStock = $("#qu_id_stock").val();
if (Grocy.EditMode == "create" && !quIdPurchase.toString().isEmpty() && !quIdStock.toString().isEmpty() && quIdPurchase != quIdStock)
{
if (Grocy.EditMode == "create" && !quIdPurchase.toString().isEmpty() && !quIdStock.toString().isEmpty() && quIdPurchase != quIdStock) {
Grocy.Api.Get("objects/quantity_unit_conversions?query[]=product_id=null&query[]=from_qu_id=" + quIdPurchase + "&query[]=to_qu_id=" + quIdStock,
function(response)
{
if (response != null && response.length > 0)
{
function(response) {
if (response != null && response.length > 0) {
var conversion = response[0];
$("#qu_factor_purchase_to_stock").val(conversion.factor);
@ -188,14 +144,11 @@ $('.input-group-qu').on('change', function(e)
RefreshQuConversionInfo();
}
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
}
else
{
} else {
RefreshQuConversionInfo();
}
@ -206,25 +159,20 @@ $('.input-group-qu').on('change', function(e)
Grocy.FrontendHelpers.ValidateForm('product-form');
});
function RefreshQuConversionInfo()
{
function RefreshQuConversionInfo() {
var quIdPurchase = $("#qu_id_purchase").val();
var quIdStock = $("#qu_id_stock").val();
var factor = $('#qu_factor_purchase_to_stock').val();
if (factor > 1 || quIdPurchase != quIdStock)
{
if (factor > 1 || quIdPurchase != quIdStock) {
$('#qu-conversion-info').text(__t('This means 1 %1$s purchased will be converted into %2$s %3$s in stock', $("#qu_id_purchase option:selected").text(), (1 * factor).toString(), __n((1 * factor).toString(), $("#qu_id_stock option:selected").text(), $("#qu_id_stock option:selected").data("plural-form"), true)));
$('#qu-conversion-info').removeClass('d-none');
}
else
{
} else {
$('#qu-conversion-info').addClass('d-none');
}
}
$('#product-form input').keyup(function(event)
{
$('#product-form input').on('keyup', function(event) {
Grocy.FrontendHelpers.ValidateForm('product-form');
$(".input-group-qu").trigger("change");
$("#product-form select").trigger("select");
@ -232,9 +180,7 @@ $('#product-form input').keyup(function(event)
if (document.getElementById('product-form').checkValidity() === false) //There is at least one validation error
{
$("#qu-conversion-add-button").addClass("disabled");
}
else
{
} else {
$("#qu-conversion-add-button").removeClass("disabled");
}
@ -244,13 +190,11 @@ $('#product-form input').keyup(function(event)
}
});
$('#location_id').change(function(event)
{
$('#location_id').on('change', function(event) {
Grocy.FrontendHelpers.ValidateForm('product-form');
});
$('#product-form input').keydown(function(event)
{
$('#product-form input').on('keydown', function(event) {
if (event.keyCode === 13) //Enter
{
event.preventDefault();
@ -258,30 +202,23 @@ $('#product-form input').keydown(function(event)
if (document.getElementById('product-form').checkValidity() === false) //There is at least one validation error
{
return false;
}
else
{
} else {
$('.default-submit-button').click();
}
}
});
$("#enable_tare_weight_handling").on("click", function()
{
if (this.checked)
{
$("#enable_tare_weight_handling").on("click", function() {
if (this.checked) {
$("#tare_weight").removeAttr("disabled");
}
else
{
} else {
$("#tare_weight").attr("disabled", "");
}
Grocy.FrontendHelpers.ValidateForm("product-form");
});
$("#product-picture").on("change", function(e)
{
$("#product-picture").on("change", function(e) {
$("#product-picture-label").removeClass("d-none");
$("#product-picture-label-none").addClass("d-none");
$("#delete-current-product-picture-on-save-hint").addClass("d-none");
@ -290,8 +227,7 @@ $("#product-picture").on("change", function(e)
});
Grocy.DeleteProductPictureOnSave = false;
$("#delete-current-product-picture-button").on("click", function(e)
{
$("#delete-current-product-picture-button").on("click", function(e) {
Grocy.DeleteProductPictureOnSave = true;
$("#current-product-picture").addClass("d-none");
$("#delete-current-product-picture-on-save-hint").removeClass("d-none");
@ -300,12 +236,24 @@ $("#delete-current-product-picture-button").on("click", function(e)
});
var quConversionsTable = $('#qu-conversions-table-products').DataTable({
'order': [[1, 'asc']],
"orderFixed": [[4, 'asc']],
'columnDefs': [
{ 'orderable': false, 'targets': 0 },
{ 'searchable': false, "targets": 0 },
{ 'visible': false, 'targets': 4 }
'order': [
[1, 'asc']
],
"orderFixed": [
[4, 'asc']
],
'columnDefs': [{
'orderable': false,
'targets': 0
},
{
'searchable': false,
"targets": 0
},
{
'visible': false,
'targets': 4
}
].concat($.fn.dataTable.defaults.columnDefs),
'rowGroup': {
enable: true,
@ -316,13 +264,28 @@ $('#qu-conversions-table-products tbody').removeClass("d-none");
quConversionsTable.columns.adjust().draw();
var barcodeTable = $('#barcode-table').DataTable({
'order': [[1, 'asc']],
"orderFixed": [[1, 'asc']],
'columnDefs': [
{ 'orderable': false, 'targets': 0 },
{ 'searchable': false, "targets": 0 },
{ 'visible': false, 'targets': 5 },
{ 'visible': false, 'targets': 6 }
'order': [
[1, 'asc']
],
"orderFixed": [
[1, 'asc']
],
'columnDefs': [{
'orderable': false,
'targets': 0
},
{
'searchable': false,
"targets": 0
},
{
'visible': false,
'targets': 5
},
{
'visible': false,
'targets': 6
}
].concat($.fn.dataTable.defaults.columnDefs)
});
$('#barcode-table tbody').removeClass("d-none");
@ -330,27 +293,23 @@ barcodeTable.columns.adjust().draw();
Grocy.Components.UserfieldsForm.Load();
$("#name").trigger("keyup");
$('#name').focus();
$('#name').trigger('focus');
$('.input-group-qu').trigger('change');
Grocy.FrontendHelpers.ValidateForm('product-form');
$(document).on('click', '.product-grocycode-label-print', function(e)
{
$(document).on('click', '.product-grocycode-label-print', function(e) {
e.preventDefault();
document.activeElement.blur();
var productId = $(e.currentTarget).attr('data-product-id');
Grocy.Api.Get('stock/products/' + productId + '/printlabel', function(labelData)
{
if (Grocy.Webhooks.labelprinter !== undefined)
{
Grocy.Api.Get('stock/products/' + productId + '/printlabel', function(labelData) {
if (Grocy.Webhooks.labelprinter !== undefined) {
Grocy.FrontendHelpers.RunWebhook(Grocy.Webhooks.labelprinter, labelData);
}
});
});
$(document).on('click', '.qu-conversion-delete-button', function(e)
{
$(document).on('click', '.qu-conversion-delete-button', function(e) {
var objectId = $(e.currentTarget).attr('data-qu-conversion-id');
bootbox.confirm({
@ -366,18 +325,14 @@ $(document).on('click', '.qu-conversion-delete-button', function(e)
className: 'btn-danger'
}
},
callback: function(result)
{
if (result === true)
{
callback: function(result) {
if (result === true) {
Grocy.Api.Delete('objects/quantity_unit_conversions/' + objectId, {},
function(result)
{
function(result) {
Grocy.ProductEditFormRedirectUri = "reload";
$('#save-product-button').click();
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
@ -386,8 +341,7 @@ $(document).on('click', '.qu-conversion-delete-button', function(e)
});
});
$(document).on('click', '.barcode-delete-button', function(e)
{
$(document).on('click', '.barcode-delete-button', function(e) {
var objectId = $(e.currentTarget).attr('data-barcode-id');
bootbox.confirm({
@ -403,18 +357,14 @@ $(document).on('click', '.barcode-delete-button', function(e)
className: 'btn-danger'
}
},
callback: function(result)
{
if (result === true)
{
callback: function(result) {
if (result === true) {
Grocy.Api.Delete('objects/product_barcodes/' + objectId, {},
function(result)
{
function(result) {
Grocy.ProductEditFormRedirectUri = "reload";
$('#save-product-button').click();
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
@ -423,14 +373,12 @@ $(document).on('click', '.barcode-delete-button', function(e)
});
});
$('#qu_id_stock').change(function(e)
{
$('#qu_id_stock').on('change', function(e) {
// Preset QU purchase with stock QU if unset
var quIdStock = $('#qu_id_stock');
var quIdPurchase = $('#qu_id_purchase');
if (quIdPurchase[0].selectedIndex === 0 && quIdStock[0].selectedIndex !== 0)
{
if (quIdPurchase[0].selectedIndex === 0 && quIdStock[0].selectedIndex !== 0) {
quIdPurchase[0].selectedIndex = quIdStock[0].selectedIndex;
Grocy.FrontendHelpers.ValidateForm('product-form');
}
@ -438,59 +386,47 @@ $('#qu_id_stock').change(function(e)
RefreshQuConversionInfo();
});
$(window).on("message", function(e)
{
$(window).on("message", function(e) {
var data = e.originalEvent.data;
if (data.Message === "ProductBarcodesChanged" || data.Message === "ProductQUConversionChanged")
{
if (data.Message === "ProductBarcodesChanged" || data.Message === "ProductQUConversionChanged") {
window.location.reload();
}
});
if (Grocy.EditMode == "create" && GetUriParam("copy-of") != undefined)
{
if (Grocy.EditMode == "create" && GetUriParam("copy-of") != undefined) {
Grocy.Api.Get('objects/products/' + GetUriParam("copy-of"),
function(sourceProduct)
{
if (sourceProduct.parent_product_id != null)
{
function(sourceProduct) {
if (sourceProduct.parent_product_id != null) {
Grocy.Components.ProductPicker.SetId(sourceProduct.parent_product_id);
}
if (sourceProduct.description != null)
{
if (sourceProduct.description != null) {
$("#description").summernote("pasteHTML", sourceProduct.description);
}
$("#location_id").val(sourceProduct.location_id);
if (sourceProduct.shopping_location_id != null)
{
if (sourceProduct.shopping_location_id != null) {
Grocy.Components.ShoppingLocationPicker.SetId(sourceProduct.shopping_location_id);
}
$("#min_stock_amount").val(sourceProduct.min_stock_amount);
if (BoolVal(sourceProduct.cumulate_min_stock_amount_of_sub_products))
{
if (BoolVal(sourceProduct.cumulate_min_stock_amount_of_sub_products)) {
$("#cumulate_min_stock_amount_of_sub_products").prop("checked", true);
}
$("#default_best_before_days").val(sourceProduct.default_best_before_days);
$("#default_best_before_days_after_open").val(sourceProduct.default_best_before_days_after_open);
if (sourceProduct.product_group_id != null)
{
if (sourceProduct.product_group_id != null) {
$("#product_group_id").val(sourceProduct.product_group_id);
}
$("#qu_id_stock").val(sourceProduct.qu_id_stock);
$("#qu_id_purchase").val(sourceProduct.qu_id_purchase);
$("#qu_factor_purchase_to_stock").val(sourceProduct.qu_factor_purchase_to_stock);
if (BoolVal(sourceProduct.enable_tare_weight_handling))
{
if (BoolVal(sourceProduct.enable_tare_weight_handling)) {
$("#enable_tare_weight_handling").prop("checked", true);
}
$("#tare_weight").val(sourceProduct.tare_weight);
if (BoolVal(sourceProduct.not_check_stock_fulfillment_for_recipes))
{
if (BoolVal(sourceProduct.not_check_stock_fulfillment_for_recipes)) {
$("#not_check_stock_fulfillment_for_recipes").prop("checked", true);
}
if (sourceProduct.calories != null)
{
if (sourceProduct.calories != null) {
$("#calories").val(sourceProduct.calories);
}
$("#default_best_before_days_after_freezing").val(sourceProduct.default_best_before_days_after_freezing);
@ -499,75 +435,57 @@ if (Grocy.EditMode == "create" && GetUriParam("copy-of") != undefined)
Grocy.FrontendHelpers.ValidateForm('product-form');
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
}
else if (Grocy.EditMode === 'create')
{
if (Grocy.UserSettings.product_presets_location_id.toString() !== '-1')
{
} else if (Grocy.EditMode === 'create') {
if (Grocy.UserSettings.product_presets_location_id.toString() !== '-1') {
$("#location_id").val(Grocy.UserSettings.product_presets_location_id);
}
if (Grocy.UserSettings.product_presets_product_group_id.toString() !== '-1')
{
if (Grocy.UserSettings.product_presets_product_group_id.toString() !== '-1') {
$("#product_group_id").val(Grocy.UserSettings.product_presets_product_group_id);
}
if (Grocy.UserSettings.product_presets_qu_id.toString() !== '-1')
{
if (Grocy.UserSettings.product_presets_qu_id.toString() !== '-1') {
$("select.input-group-qu").val(Grocy.UserSettings.product_presets_qu_id);
}
if (Grocy.UserSettings.product_presets_default_due_days.toString() !== '0')
{
if (Grocy.UserSettings.product_presets_default_due_days.toString() !== '0') {
$("#default_best_before_days").val(Grocy.UserSettings.product_presets_default_due_days);
}
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING) {
$("#treat_opened_as_out_of_stock").prop("checked", BoolVal(Grocy.UserSettings.product_presets_treat_opened_as_out_of_stock));
}
}
Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
{
Grocy.Components.ProductPicker.OnChange(function(e) {
var parentProductId = $(e.target).val();
if (parentProductId)
{
if (parentProductId) {
Grocy.Api.Get('objects/products/' + parentProductId,
function(parentProduct)
{
if (BoolVal(parentProduct.cumulate_min_stock_amount_of_sub_products))
{
function(parentProduct) {
if (BoolVal(parentProduct.cumulate_min_stock_amount_of_sub_products)) {
$("#min_stock_amount").attr("disabled", "");
}
else
{
} else {
$('#min_stock_amount').removeAttr("disabled");
}
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
}
else
{
} else {
$('#min_stock_amount').removeAttr("disabled");
}
});
Grocy.FrontendHelpers.ValidateForm("product-form");
Grocy.Components.ProductPicker.GetPicker().trigger("change");
Grocy.Components.ProductPicker.Validate();
if (Grocy.EditMode == "edit")
{
if (Grocy.EditMode == "edit") {
$(".save-product-button").toggleClass("default-submit-button");
}

View File

@ -1,61 +1,180 @@
var productsTable = $('#products-table').DataTable({
'order': [[1, 'asc']],
'columnDefs': [
{ 'orderable': false, 'targets': 0 },
{ 'searchable': false, "targets": 0 },
{ 'visible': false, 'targets': 7 },
{ "type": "html-num-fmt", "targets": 3 }
].concat($.fn.dataTable.defaults.columnDefs)
var userfieldsColumns = userfields.map(function(userfield) {
if (userfield.show_as_column_in_tables != 1) return null;
return {
data: 'userfields.' + userfield.name,
defaultContent: ''
};
}).filter(function(userfield) {
return userfield !== null;
});
$('#products-table tbody').removeClass("d-none");
productsTable.columns.adjust().draw();
$("#search").on("keyup", Delay(function()
{
var productsTable = $('#products-table').DataTable({
ajax: function(data, callback, settings) {
Grocy.FrontendHelpers.BeginUiBusy();
var query = [];
if (GetUriParam('only_in_stock')) {
query.push('only_in_stock=true');
}
if (!GetUriParam('include_disabled')) {
query.push('query%5B%5D=' + encodeURIComponent('active=1'));
}
data.columns.forEach(function(column) {
var search = column.search.value.trim();
if (search.length > 0) {
query.push('query%5B%5D=' + encodeURIComponent(column.data + '=' + search));
}
});
var search = data.search.value.trim();
if (search.length > 0) {
query.push('search=' + encodeURIComponent(search));
}
query.push('limit=' + encodeURIComponent(data.length));
query.push('offset=' + encodeURIComponent(data.start));
query.push('order=' + encodeURIComponent(data.order.map(function(order) {
return data.columns[order.column].data + ':' + order.dir;
}).join(',')));
Grocy.Api.Get('objects/products' + (query.length > 0 ? '?' + query.join('&') : ''),
function(result, meta) {
callback({
data: result,
recordsTotal: meta.recordsTotal,
recordsFiltered: meta.recordsFiltered,
});
Grocy.FrontendHelpers.EndUiBusy();
},
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy();
Grocy.FrontendHelpers.ShowGenericError('Server error', xhr.response);
}
);
},
paging: true,
serverSide: true,
deferRender: true,
autoWidth: true,
order: [
[1, 'asc']
],
columns: [{
searchable: false,
orderable: false,
render: function(data, type, row, meta) {
return '<a class="btn btn-info btn-sm" href="/product/' + encodeURIComponent(row.id) + '"' +
' data-toggle="tooltip" title="' + __t('Edit this item') + '">' +
' <i class="fas fa-edit"></i>' +
'</a>' +
'<a class="btn btn-danger btn-sm product-delete-button" href="#"' +
' data-product-id="' + row.id + '" data-product-name="' + row.name + '"' +
' data-toggle="tooltip" title="' + __t('Delete this item') + '">' +
' <i class="fas fa-trash"></i>' +
'</a>' +
'<div class="dropdown d-inline-block">' +
' <button class="btn btn-sm btn-light text-secondary" type="button"' +
' data-toggle="dropdown">' +
' <i class="fas fa-ellipsis-v"></i>' +
' </button>' +
' <div class="table-inline-menu dropdown-menu dropdown-menu-right">' +
' <a class="dropdown-item" type="button"' +
' href="/product/new?copy-of=' + encodeURIComponent(row.id) + '">' +
' <span class="dropdown-item-text">' + __t('Copy') + '</span>' +
' </a>' +
' <a class="dropdown-item merge-products-button"' +
' data-product-id="' + row.id + '" data-product-name="' + row.name + '"' +
' type="button" href="#" >' +
' <span class="dropdown-item-text">' + __t('Merge') + '</span>' +
' </a>' +
' </div>' +
'</div>';
}
},
{
data: 'name',
searchable: true,
render: function(data, type, row, meta) {
return data + (row.picture_file_name ? (
' <i class="fas fa-image text-muted" data-toggle="tooltip" ' +
'title="' + __t('This product has a picture') + '"></i>'
) : '');
}
},
{
data: 'location.name',
defaultContent: '',
visible: Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING
},
{
data: 'min_stock_amount',
type: 'html-num-fmt',
render: function(data, type, row, meta) {
return '<span class="locale-number locale-number-quantity-amount">' + data + '</span>';
}
},
{
data: 'qu_purchase.name',
defaultContent: ''
},
{
data: 'qu_stock.name',
defaultContent: ''
},
{
data: 'product_group.name',
defaultContent: ''
},
{
data: 'shopping_location.name',
defaultContent: '',
visible: Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING
}
].concat(userfieldsColumns),
});
productsTable.on('draw', function() {
$('[data-toggle=tooltip]').tooltip();
$('[data-toggle=dropdown]').dropdown();
});
$('#search').on('keyup', Delay(function() {
var value = $(this).val();
if (value === "all")
{
value = "";
if (value === 'all') {
value = '';
}
productsTable.search(value).draw();
}, 200));
}, 500));
$("#product-group-filter").on("change", function()
{
var value = $("#product-group-filter option:selected").text();
if (value === __t("All"))
{
value = "";
$('#product-group-filter').on('change', function() {
var value = $('#product-group-filter option:selected').text();
if (value === __t('All')) {
value = '';
}
productsTable.column(productsTable.colReorder.transpose(6)).search(value).draw();
});
$("#clear-filter-button").on("click", function()
{
$("#search").val("");
$("#product-group-filter").val("all");
productsTable.column(productsTable.colReorder.transpose(6)).search("").draw();
productsTable.search("").draw();
if ($("#show-disabled").is(":checked") || $("#show-only-in-stock").is(":checked"))
{
$("#show-disabled").prop("checked", false);
$("#show-only-in-stock").prop("checked", false);
RemoveUriParam("include_disabled");
RemoveUriParam("only_in_stock");
window.location.reload();
$('#clear-filter-button').on('click', function() {
$('#search').val('');
$('#product-group-filter').val('all');
productsTable.column(productsTable.colReorder.transpose(6)).search('');
productsTable.search('');
if ($('#show-disabled').is(':checked') || $('#show-only-in-stock').is(':checked')) {
$('#show-disabled').prop('checked', false);
$('#show-only-in-stock').prop('checked', false);
RemoveUriParam('include_disabled');
RemoveUriParam('only_in_stock');
}
productsTable.draw();
});
if (typeof GetUriParam("product-group") !== "undefined")
{
$("#product-group-filter").val(GetUriParam("product-group"));
$("#product-group-filter").trigger("change");
if (typeof GetUriParam('product-group') !== 'undefined') {
$('#product-group-filter').val(GetUriParam('product-group'));
$('#product-group-filter').trigger('change');
}
$(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');
@ -72,19 +191,15 @@ $(document).on('click', '.product-delete-button', function(e)
className: 'btn-danger'
}
},
callback: function(result)
{
if (result === true)
{
callback: function(result) {
if (result === true) {
jsonData = {};
jsonData.active = 0;
Grocy.Api.Delete('objects/products/' + objectId, {},
function(result)
{
function(result) {
window.location.href = U('/products');
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
@ -93,61 +208,96 @@ $(document).on('click', '.product-delete-button', function(e)
});
});
$("#show-disabled").change(function()
{
if (this.checked)
{
UpdateUriParam("include_disabled", "true");
$('#show-disabled').on('change', function() {
if (this.checked) {
UpdateUriParam('include_disabled', 'true');
} else {
RemoveUriParam('include_disabled');
}
else
{
RemoveUriParam("include_disabled");
}
window.location.reload();
productsTable.draw();
});
$("#show-only-in-stock").change(function()
{
if (this.checked)
{
UpdateUriParam("only_in_stock", "true");
$('#show-only-in-stock').on('change', function() {
if (this.checked) {
UpdateUriParam('only_in_stock', 'true');
} else {
RemoveUriParam('only_in_stock');
}
else
{
RemoveUriParam("only_in_stock");
}
window.location.reload();
productsTable.draw();
});
if (GetUriParam('include_disabled'))
{
$("#show-disabled").prop('checked', true);
if (GetUriParam('include_disabled')) {
$('#show-disabled').prop('checked', true);
}
$(document).on('click', '.merge-products-button', function(e) {
var $button = $(e.currentTarget);
var $mergeKeep = $('#merge-products-keep');
$(".merge-products-button").on("click", function(e)
{
var productId = $(e.currentTarget).attr("data-product-id");
$("#merge-products-keep").val(productId);
$("#merge-products-remove").val("");
$("#merge-products-modal").modal("show");
var optionId = $button.attr('data-product-id');
var optionText = $button.attr('data-product-name');
if ($mergeKeep.find('option[value="' + optionId + '"]').length) {
$mergeKeep.val(optionId).trigger('change');
} else {
var option = new Option(optionText, optionId, true, true);
$mergeKeep.append(option).trigger('change');
}
$('#merge-products-remove').val(null).trigger('change');
$('#merge-products-modal').modal('show');
});
$("#merge-products-save-button").on("click", function()
{
var productIdToKeep = $("#merge-products-keep").val();
var productIdToRemove = $("#merge-products-remove").val();
$('#merge-products-save-button').on('click', function() {
var productIdToKeep = $('#merge-products-keep').val();
var productIdToRemove = $('#merge-products-remove').val();
Grocy.Api.Post("stock/products/" + productIdToKeep.toString() + "/merge/" + productIdToRemove.toString(), {},
function(result)
{
Grocy.Api.Post('stock/products/' + productIdToKeep.toString() + '/merge/' + productIdToRemove.toString(), {},
function(result) {
window.location.href = U('/products');
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.ShowGenericError('Error while merging', xhr.response);
}
);
});
$('#merge-products-keep, #merge-products-remove').select2({
dropdownParent: $('#merge-products-modal'),
ajax: {
delay: 150,
transport: function(params, success, failure) {
var results_per_page = 10;
var page = params.data.page || 1;
var term = params.data.term || "";
var query = [];
query.push('query%5B%5D=active%3D1');
query.push('limit=' + encodeURIComponent(results_per_page));
query.push('offset=' + encodeURIComponent((page - 1) * results_per_page));
query.push('order=name%3Acollate%20nocase');
if (term.length > 0) {
query.push('search=' + encodeURIComponent(term));
}
Grocy.Api.Get('objects/products' + (query.length > 0 ? '?' + query.join('&') : ''),
function(results, meta) {
success({
results: results.map(function(result) {
return {
id: result.id,
text: result.name
};
}),
pagination: {
more: page * results_per_page < meta.recordsFiltered
}
});
},
function(xhr) {
failure();
}
);
}
}
});

View File

@ -1,16 +1,13 @@
var CurrentProductDetails;
$('#save-purchase-button').on('click', function(e)
{
$('#save-purchase-button').on('click', function(e) {
e.preventDefault();
if ($(".combobox-menu-visible").length)
{
if ($(".combobox-menu-visible").length) {
return;
}
if ($(".combobox-menu-visible").length)
{
if ($(".combobox-menu-visible").length) {
return;
}
@ -19,140 +16,116 @@ $('#save-purchase-button').on('click', function(e)
Grocy.FrontendHelpers.BeginUiBusy("purchase-form");
Grocy.Api.Get('stock/products/' + jsonForm.product_id,
function(productDetails)
{
function(productDetails) {
var jsonData = {};
jsonData.amount = jsonForm.amount;
jsonData.stock_label_type = jsonForm.stock_label_type;
if (!Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
{
if (!Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) {
jsonData.price = 0;
}
else
{
} else {
var amount = jsonForm.display_amount;
if (BoolVal(productDetails.product.enable_tare_weight_handling))
{
if (BoolVal(productDetails.product.enable_tare_weight_handling)) {
amount -= parseFloat(productDetails.product.tare_weight);
}
var price = parseFloat(jsonForm.price * $("#qu_id option:selected").attr("data-qu-factor")).toFixed(Grocy.UserSettings.stock_decimal_places_prices);
if ($("input[name='price-type']:checked").val() == "total-price")
{
if ($("input[name='price-type']:checked").val() == "total-price") {
price = parseFloat(price / amount).toFixed(Grocy.UserSettings.stock_decimal_places_prices);
}
jsonData.price = price;
}
if (BoolVal(Grocy.UserSettings.show_purchased_date_on_purchase))
{
if (BoolVal(Grocy.UserSettings.show_purchased_date_on_purchase)) {
jsonData.purchased_date = Grocy.Components.DateTimePicker2.GetValue();
}
if (Grocy.Components.DateTimePicker)
{
if (Grocy.Components.DateTimePicker) {
jsonData.best_before_date = Grocy.Components.DateTimePicker.GetValue();
}
else
{
} else {
jsonData.best_before_date = null;
}
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) {
jsonData.shopping_location_id = Grocy.Components.ShoppingLocationPicker.GetValue();
}
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) {
jsonData.location_id = Grocy.Components.LocationPicker.GetValue();
}
Grocy.Api.Post('stock/products/' + jsonForm.product_id + '/add', jsonData,
function(result)
{
if ($("#purchase-form").hasAttr("data-used-barcode"))
{
Grocy.Api.Put('objects/product_barcodes/' + $("#purchase-form").attr("data-used-barcode"), { last_price: $("#price").val() },
function(result)
{ },
function(xhr)
{ }
function(result) {
if ($("#purchase-form").hasAttr("data-used-barcode")) {
Grocy.Api.Put('objects/product_barcodes/' + $("#purchase-form").attr("data-used-barcode"), {
last_price: $("#price").val()
},
function(result) {},
function(xhr) {}
);
}
if (BoolVal(Grocy.UserSettings.scan_mode_purchase_enabled))
{
if (BoolVal(Grocy.UserSettings.scan_mode_purchase_enabled)) {
Grocy.UISound.Success();
}
if (GetUriParam("flow") == "InplaceAddBarcodeToExistingProduct")
{
if (GetUriParam("flow") == "InplaceAddBarcodeToExistingProduct") {
var jsonDataBarcode = {};
jsonDataBarcode.barcode = GetUriParam("barcode");
jsonDataBarcode.product_id = jsonForm.product_id;
jsonDataBarcode.shopping_location_id = jsonForm.shopping_location_id;
Grocy.Api.Post('objects/product_barcodes', jsonDataBarcode,
function(result)
{
function(result) {
$("#flow-info-InplaceAddBarcodeToExistingProduct").addClass("d-none");
$('#barcode-lookup-disabled-hint').addClass('d-none');
$('#barcode-lookup-hint').removeClass('d-none');
window.history.replaceState({}, document.title, U("/purchase"));
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy("purchase-form");
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
}
);
}
var amountMessage = parseFloat(jsonForm.amount).toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts });
if (BoolVal(productDetails.product.enable_tare_weight_handling))
{
var amountMessage = parseFloat(jsonForm.amount).toLocaleString({
minimumFractionDigits: 0,
maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts
});
if (BoolVal(productDetails.product.enable_tare_weight_handling)) {
amountMessage = parseFloat(jsonForm.amount) - parseFloat(productDetails.stock_amount) - parseFloat(productDetails.product.tare_weight);
}
var successMessage = __t('Added %1$s of %2$s to stock', amountMessage + " " + __n(amountMessage, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural, true), productDetails.product.name) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockTransaction(\'' + result[0].transaction_id + '\')"><i class="fas fa-undo"></i> ' + __t("Undo") + '</a>';
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_LABEL_PRINTER)
{
if (Grocy.Webhooks.labelprinter !== undefined)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_LABEL_PRINTER) {
if (Grocy.Webhooks.labelprinter !== undefined) {
if (jsonForm.stock_label_type == 1) // Single label
{
var webhookData = {};
webhookData.product = productDetails.product.name;
webhookData.grocycode = 'grcy:p:' + jsonForm.product_id + ":" + result[0].stock_id;
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING) {
webhookData.due_date = __t('DD') + ': ' + result[0].best_before_date;
}
Grocy.FrontendHelpers.RunWebhook(Grocy.Webhooks.labelprinter, webhookData);
}
else if (jsonForm.stock_label_type == 2) // Label per unit
} else if (jsonForm.stock_label_type == 2) // Label per unit
{
Grocy.Api.Get('stock/transactions/' + result[0].transaction_id,
function(stockEntries)
{
stockEntries.forEach(stockEntry =>
{
function(stockEntries) {
stockEntries.forEach(stockEntry => {
var webhookData = {};
webhookData.product = productDetails.product.name;
webhookData.grocycode = 'grcy:p:' + jsonForm.product_id + ":" + stockEntry.stock_id;
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING) {
webhookData.due_date = __t('DD') + ': ' + result[0].best_before_date;
}
Grocy.FrontendHelpers.RunWebhook(Grocy.Webhooks.labelprinter, webhookData);
});
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
@ -160,24 +133,19 @@ $('#save-purchase-button').on('click', function(e)
}
}
if (GetUriParam("embedded") !== undefined)
{
if (GetUriParam("embedded") !== undefined) {
window.parent.postMessage(WindowMessageBag("ProductChanged", jsonForm.product_id), Grocy.BaseUrl);
window.parent.postMessage(WindowMessageBag("AfterItemAdded", GetUriParam("listitemid")), Grocy.BaseUrl);
window.parent.postMessage(WindowMessageBag("ShowSuccessMessage", successMessage), Grocy.BaseUrl);
window.parent.postMessage(WindowMessageBag("Ready"), Grocy.BaseUrl);
window.parent.postMessage(WindowMessageBag("CloseAllModals"), Grocy.BaseUrl);
}
else
{
} else {
Grocy.FrontendHelpers.EndUiBusy("purchase-form");
toastr.success(successMessage);
Grocy.Components.ProductPicker.FinishFlow();
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && BoolVal(Grocy.UserSettings.show_warning_on_purchase_when_due_date_is_earlier_than_next))
{
if (moment(jsonData.best_before_date).isBefore(CurrentProductDetails.next_due_date))
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && BoolVal(Grocy.UserSettings.show_warning_on_purchase_when_due_date_is_earlier_than_next)) {
if (moment(jsonData.best_before_date).isBefore(CurrentProductDetails.next_due_date)) {
toastr.warning(__t("This is due earlier than already in-stock items"));
}
}
@ -189,23 +157,19 @@ $('#save-purchase-button').on('click', function(e)
$(".input-group-productamountpicker").trigger("change");
$('#price').val('');
$("#tare-weight-handling-info").addClass("d-none");
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) {
Grocy.Components.LocationPicker.Clear();
}
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING) {
Grocy.Components.DateTimePicker.Clear();
}
Grocy.Components.ProductPicker.SetValue('');
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
{
Grocy.Components.ProductPicker.Clear();
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) {
Grocy.Components.ShoppingLocationPicker.SetValue('');
}
Grocy.Components.ProductPicker.GetInputElement().focus();
Grocy.Components.ProductPicker.Focus();
Grocy.Components.ProductCard.Refresh(jsonForm.product_id);
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_LABEL_PRINTER)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_LABEL_PRINTER) {
$("#stock_label_type").val(0);
}
@ -217,85 +181,66 @@ $('#save-purchase-button').on('click', function(e)
Grocy.FrontendHelpers.ValidateForm('purchase-form');
}
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy("purchase-form");
console.error(xhr);
}
);
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy("purchase-form");
console.error(xhr);
}
);
});
if (Grocy.Components.ProductPicker !== undefined)
{
Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
{
if (BoolVal(Grocy.UserSettings.scan_mode_purchase_enabled))
{
if (Grocy.Components.ProductPicker !== undefined) {
Grocy.Components.ProductPicker.OnChange(function(e) {
if (BoolVal(Grocy.UserSettings.scan_mode_purchase_enabled)) {
Grocy.UISound.BarcodeScannerBeep();
}
var productId = $(e.target).val();
if (productId)
{
if (productId) {
Grocy.Components.ProductCard.Refresh(productId);
Grocy.Api.Get('stock/products/' + productId,
function(productDetails)
{
function(productDetails) {
CurrentProductDetails = productDetails;
Grocy.Components.ProductAmountPicker.Reload(productDetails.product.id, productDetails.quantity_unit_stock.id);
if (productDetails.product.enable_tare_weight_handling == 1)
{
if (productDetails.product.enable_tare_weight_handling == 1) {
Grocy.Components.ProductAmountPicker.SetQuantityUnit(productDetails.quantity_unit_stock.id);
$("#qu_id").attr("disabled", "");
}
else
{
} else {
Grocy.Components.ProductAmountPicker.SetQuantityUnit(productDetails.default_quantity_unit_purchase.id);
}
$('#display_amount').val(parseFloat(Grocy.UserSettings.stock_default_purchase_amount));
$(".input-group-productamountpicker").trigger("change");
if (GetUriParam("flow") === "shoppinglistitemtostock")
{
if (GetUriParam("flow") === "shoppinglistitemtostock") {
Grocy.Components.ProductAmountPicker.SetQuantityUnit(GetUriParam("quId"));
$('#display_amount').val(parseFloat(GetUriParam("amount") * $("#qu_id option:selected").attr("data-qu-factor")));
}
$(".input-group-productamountpicker").trigger("change");
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
{
if (productDetails.last_shopping_location_id != null)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) {
if (productDetails.last_shopping_location_id != null) {
Grocy.Components.ShoppingLocationPicker.SetId(productDetails.last_shopping_location_id);
}
else
{
} else {
Grocy.Components.ShoppingLocationPicker.SetId(productDetails.default_shopping_location_id);
}
}
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) {
Grocy.Components.LocationPicker.SetId(productDetails.location.id);
}
if (productDetails.last_price == null || productDetails.last_price == 0)
{
if (productDetails.last_price == null || productDetails.last_price == 0) {
$("#price").val("")
}
else
{
} else {
$('#price').val(parseFloat(productDetails.last_price / $("#qu_id option:selected").attr("data-qu-factor")));
}
@ -305,65 +250,52 @@ if (Grocy.Components.ProductPicker !== undefined)
refreshPriceHint();
if (productDetails.product.enable_tare_weight_handling == 1)
{
if (productDetails.product.enable_tare_weight_handling == 1) {
var minAmount = parseFloat(productDetails.product.tare_weight) / $("#qu_id option:selected").attr("data-qu-factor") + parseFloat(productDetails.stock_amount);
$("#display_amount").attr("min", minAmount);
$("#tare-weight-handling-info").removeClass("d-none");
}
else
{
} else {
$("#display_amount").attr("min", Grocy.DefaultMinAmount);
$("#tare-weight-handling-info").addClass("d-none");
}
PrefillBestBeforeDate(productDetails.product, productDetails.location);
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_LABEL_PRINTER)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_LABEL_PRINTER) {
$("#stock_label_type").val(productDetails.product.default_stock_label_type);
}
$("#display_amount").focus();
Grocy.FrontendHelpers.ValidateForm('purchase-form');
if (GetUriParam("flow") === "shoppinglistitemtostock" && BoolVal(Grocy.UserSettings.shopping_list_to_stock_workflow_auto_submit_when_prefilled) && document.getElementById("purchase-form").checkValidity() === true)
{
if (GetUriParam("flow") === "shoppinglistitemtostock" && BoolVal(Grocy.UserSettings.shopping_list_to_stock_workflow_auto_submit_when_prefilled) && document.getElementById("purchase-form").checkValidity() === true) {
$("#save-purchase-button").click();
}
RefreshLocaleNumberInput();
if (document.getElementById("product_id").getAttribute("barcode") != "null")
{
if (document.getElementById("product_id").getAttribute("barcode") != "null") {
Grocy.Api.Get('objects/product_barcodes?query[]=barcode=' + document.getElementById("product_id").getAttribute("barcode"),
function(barcodeResult)
{
if (barcodeResult != null)
{
function(barcodeResult) {
if (barcodeResult != null) {
var barcode = barcodeResult[0];
$("#purchase-form").attr("data-used-barcode", barcode.id);
if (barcode != null)
{
if (barcode.amount != null && !barcode.amount.isEmpty())
{
if (barcode != null) {
if (barcode.amount != null && !barcode.amount.isEmpty()) {
$("#display_amount").val(barcode.amount);
$("#display_amount").select();
}
if (barcode.qu_id != null && !barcode.qu_id.isEmpty())
{
if (barcode.qu_id != null && !barcode.qu_id.isEmpty()) {
Grocy.Components.ProductAmountPicker.SetQuantityUnit(barcode.qu_id);
}
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING && barcode.shopping_location_id != null)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING && barcode.shopping_location_id != null) {
Grocy.Components.ShoppingLocationPicker.SetId(barcode.shopping_location_id);
}
if (barcode.last_price != null && !barcode.last_price.isEmpty())
{
if (barcode.last_price != null && !barcode.last_price.isEmpty()) {
$("#price").val(barcode.last_price);
$("#price-type-total-price").click();
}
@ -376,22 +308,18 @@ if (Grocy.Components.ProductPicker !== undefined)
ScanModeSubmit(false);
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
}
else
{
} else {
$("#purchase-form").removeAttr("data-used-barcode");
ScanModeSubmit();
}
$('#display_amount').trigger("keyup");
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
@ -399,51 +327,36 @@ if (Grocy.Components.ProductPicker !== undefined)
});
}
function PrefillBestBeforeDate(product, location)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
{
function PrefillBestBeforeDate(product, location) {
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING) {
var dueDays;
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRODUCT_FREEZING && BoolVal(location.is_freezer))
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRODUCT_FREEZING && BoolVal(location.is_freezer)) {
dueDays = product.default_best_before_days_after_freezing;
}
else
{
} else {
dueDays = product.default_best_before_days;
}
dueDays = parseFloat(dueDays);
if (dueDays != 0)
{
if (dueDays == -1)
{
if (!$("#datetimepicker-shortcut").is(":checked"))
{
if (dueDays != 0) {
if (dueDays == -1) {
if (!$("#datetimepicker-shortcut").is(":checked")) {
$("#datetimepicker-shortcut").click();
}
}
else
{
} else {
Grocy.Components.DateTimePicker.SetValue(moment().add(dueDays, 'days').format('YYYY-MM-DD'));
}
}
}
}
if (Grocy.Components.LocationPicker !== undefined)
{
Grocy.Components.LocationPicker.GetPicker().on('change', function(e)
{
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRODUCT_FREEZING)
{
if (Grocy.Components.LocationPicker !== undefined) {
Grocy.Components.LocationPicker.GetPicker().on('change', function(e) {
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRODUCT_FREEZING) {
Grocy.Api.Get('objects/locations/' + Grocy.Components.LocationPicker.GetValue(),
function(location)
{
function(location) {
PrefillBestBeforeDate(CurrentProductDetails.product, location);
},
function(xhr)
{ }
function(xhr) {}
);
}
});
@ -454,47 +367,35 @@ RefreshLocaleNumberInput();
$(".input-group-productamountpicker").trigger("change");
Grocy.FrontendHelpers.ValidateForm('purchase-form');
if (Grocy.Components.ProductPicker)
{
if (Grocy.Components.ProductPicker.InAnyFlow() === false && GetUriParam("embedded") === undefined)
{
Grocy.Components.ProductPicker.GetInputElement().focus();
}
else
{
Grocy.Components.ProductPicker.GetPicker().trigger('change');
if (Grocy.Components.ProductPicker) {
if (Grocy.Components.ProductPicker.InAnyFlow() === false && GetUriParam("embedded") === undefined) {
Grocy.Components.ProductPicker.Focus();
} else {
Grocy.Components.ProductPicker.Validate();
if (Grocy.Components.ProductPicker.InProductModifyWorkflow())
{
Grocy.Components.ProductPicker.GetInputElement().focus();
if (Grocy.Components.ProductPicker.InProductModifyWorkflow()) {
Grocy.Components.ProductPicker.Focus();
}
}
}
$('#display_amount').on('focus', function(e)
{
if (Grocy.Components.ProductPicker.GetValue().length === 0)
{
Grocy.Components.ProductPicker.GetInputElement().focus();
}
else
{
$('#display_amount').on('focus', function(e) {
if (Grocy.Components.ProductPicker.GetValue().length === 0) {
Grocy.Components.ProductPicker.Focus();
} else {
$(this).select();
}
});
$('#price').on('focus', function(e)
{
$('#price').on('focus', function(e) {
$(this).select();
});
$('#purchase-form input').keyup(function(event)
{
$('#purchase-form input').keyup(function(event) {
Grocy.FrontendHelpers.ValidateForm('purchase-form');
});
$('#purchase-form input').keydown(function(event)
{
$('#purchase-form input').keydown(function(event) {
if (event.keyCode === 13) //Enter
{
event.preventDefault();
@ -502,191 +403,154 @@ $('#purchase-form input').keydown(function(event)
if (document.getElementById('purchase-form').checkValidity() === false) //There is at least one validation error
{
return false;
}
else
{
} else {
$('#save-purchase-button').click();
}
}
});
if (Grocy.Components.DateTimePicker)
{
Grocy.Components.DateTimePicker.GetInputElement().on('change', function(e)
{
if (Grocy.Components.DateTimePicker) {
Grocy.Components.DateTimePicker.GetInputElement().on('change', function(e) {
Grocy.FrontendHelpers.ValidateForm('purchase-form');
});
Grocy.Components.DateTimePicker.GetInputElement().on('keypress', function(e)
{
Grocy.Components.DateTimePicker.GetInputElement().on('keypress', function(e) {
Grocy.FrontendHelpers.ValidateForm('purchase-form');
});
}
if (Grocy.Components.DateTimePicker2)
{
Grocy.Components.DateTimePicker2.GetInputElement().on('change', function(e)
{
if (Grocy.Components.DateTimePicker2) {
Grocy.Components.DateTimePicker2.GetInputElement().on('change', function(e) {
Grocy.FrontendHelpers.ValidateForm('purchase-form');
});
Grocy.Components.DateTimePicker2.GetInputElement().on('keypress', function(e)
{
Grocy.Components.DateTimePicker2.GetInputElement().on('keypress', function(e) {
Grocy.FrontendHelpers.ValidateForm('purchase-form');
});
Grocy.Components.DateTimePicker2.GetInputElement().trigger("input");
}
$('#price').on('keyup', function(e)
{
$('#price').on('keyup', function(e) {
refreshPriceHint();
});
$('#price-type-unit-price').on('change', function(e)
{
$('#price-type-unit-price').on('change', function(e) {
refreshPriceHint();
});
$('#price-type-total-price').on('change', function(e)
{
$('#price-type-total-price').on('change', function(e) {
refreshPriceHint();
});
$('#display_amount').on('change', function(e)
{
$('#display_amount').on('change', function(e) {
refreshPriceHint();
Grocy.FrontendHelpers.ValidateForm('purchase-form');
});
function refreshPriceHint()
{
if ($('#amount').val() == 0 || $('#price').val() == 0)
{
function refreshPriceHint() {
if ($('#amount').val() == 0 || $('#price').val() == 0) {
$('#price-hint').text("");
return;
}
if ($("input[name='price-type']:checked").val() == "total-price" || $("#qu_id").attr("data-destination-qu-name") != $("#qu_id option:selected").text())
{
if ($("input[name='price-type']:checked").val() == "total-price" || $("#qu_id").attr("data-destination-qu-name") != $("#qu_id option:selected").text()) {
var amount = $('#display_amount').val();
if (BoolVal(CurrentProductDetails.product.enable_tare_weight_handling))
{
if (BoolVal(CurrentProductDetails.product.enable_tare_weight_handling)) {
amount -= parseFloat(CurrentProductDetails.product.tare_weight);
}
var price = parseFloat($('#price').val() * $("#qu_id option:selected").attr("data-qu-factor")).toFixed(Grocy.UserSettings.stock_decimal_places_prices);
if ($("input[name='price-type']:checked").val() == "total-price")
{
if ($("input[name='price-type']:checked").val() == "total-price") {
price = parseFloat(price / amount).toFixed(Grocy.UserSettings.stock_decimal_places_prices);
}
$('#price-hint').text(__t('means %1$s per %2$s', price.toLocaleString(undefined, { style: "currency", currency: Grocy.Currency, minimumFractionDigits: Grocy.UserSettings.stock_decimal_places_prices, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_prices }), $("#qu_id").attr("data-destination-qu-name")));
}
else
{
$('#price-hint').text(__t('means %1$s per %2$s', price.toLocaleString(undefined, {
style: "currency",
currency: Grocy.Currency,
minimumFractionDigits: Grocy.UserSettings.stock_decimal_places_prices,
maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_prices
}), $("#qu_id").attr("data-destination-qu-name")));
} else {
$('#price-hint').text("");
}
};
function UndoStockBooking(bookingId)
{
function UndoStockBooking(bookingId) {
Grocy.Api.Post('stock/bookings/' + bookingId.toString() + '/undo', {},
function(result)
{
function(result) {
toastr.success(__t("Booking successfully undone"));
Grocy.Api.Get('stock/bookings/' + bookingId.toString(),
function(result)
{
function(result) {
window.postMessage(WindowMessageBag("ProductChanged", result.product_id), Grocy.BaseUrl);
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
};
function UndoStockTransaction(transactionId)
{
function UndoStockTransaction(transactionId) {
Grocy.Api.Post('stock/transactions/' + transactionId.toString() + '/undo', {},
function(result)
{
function(result) {
toastr.success(__t("Transaction successfully undone"));
Grocy.Api.Get('stock/transactions/' + transactionId.toString(),
function(result)
{
function(result) {
window.postMessage(WindowMessageBag("ProductChanged", result[0].product_id), Grocy.BaseUrl);
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
};
$("#scan-mode").on("change", function(e)
{
if ($(this).prop("checked"))
{
$("#scan-mode").on("change", function(e) {
if ($(this).prop("checked")) {
Grocy.UISound.AskForPermission();
}
});
$("#scan-mode-button").on("click", function(e)
{
$("#scan-mode-button").on("click", function(e) {
document.activeElement.blur();
$("#scan-mode").click();
$("#scan-mode-button").toggleClass("btn-success").toggleClass("btn-danger");
if ($("#scan-mode").prop("checked"))
{
if ($("#scan-mode").prop("checked")) {
$("#scan-mode-status").text(__t("on"));
}
else
{
} else {
$("#scan-mode-status").text(__t("off"));
}
});
$('#qu_id').on('change', function(e)
{
$('#qu_id').on('change', function(e) {
var priceTypeUnitPrice = $("#price-type-unit-price");
var priceTypeUnitPriceLabel = $("[for=" + priceTypeUnitPrice.attr("id") + "]");
priceTypeUnitPriceLabel.text($("#qu_id option:selected").text() + " " + __t("price"));
refreshPriceHint();
});
function ScanModeSubmit(singleUnit = true)
{
if (BoolVal(Grocy.UserSettings.scan_mode_purchase_enabled))
{
if (singleUnit)
{
function ScanModeSubmit(singleUnit = true) {
if (BoolVal(Grocy.UserSettings.scan_mode_purchase_enabled)) {
if (singleUnit) {
$("#display_amount").val(1);
$(".input-group-productamountpicker").trigger("change");
}
Grocy.FrontendHelpers.ValidateForm("purchase-form");
if (document.getElementById("purchase-form").checkValidity() === true)
{
if (document.getElementById("purchase-form").checkValidity() === true) {
$('#save-purchase-button').click();
}
else
{
} else {
toastr.warning(__t("Scan mode is on but not all required fields could be populated automatically"));
Grocy.UISound.Error();
}

View File

@ -1,11 +1,9 @@
Grocy.RecipePosFormInitialLoadDone = false;
$('#save-recipe-pos-button').on('click', function(e)
{
$('#save-recipe-pos-button').on('click', function(e) {
e.preventDefault();
if ($(".combobox-menu-visible").length)
{
if ($(".combobox-menu-visible").length) {
return;
}
@ -15,31 +13,24 @@ $('#save-recipe-pos-button').on('click', function(e)
Grocy.FrontendHelpers.BeginUiBusy("recipe-pos-form");
if (Grocy.EditMode === 'create')
{
if (Grocy.EditMode === 'create') {
Grocy.Api.Post('objects/recipes_pos', jsonData,
function(result)
{
function(result) {
window.parent.postMessage(WindowMessageBag("IngredientsChanged"), Grocy.BaseUrl);
window.parent.postMessage(WindowMessageBag("CloseAllModals"), Grocy.BaseUrl);
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy("recipe-pos-form");
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
}
);
}
else
{
} else {
Grocy.Api.Put('objects/recipes_pos/' + Grocy.EditObjectId, jsonData,
function(result)
{
function(result) {
window.parent.postMessage(WindowMessageBag("IngredientsChanged"), Grocy.BaseUrl);
window.parent.postMessage(WindowMessageBag("CloseAllModals"), Grocy.BaseUrl);
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy("recipe-pos-form");
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
}
@ -47,33 +38,25 @@ $('#save-recipe-pos-button').on('click', function(e)
}
});
Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
{
Grocy.Components.ProductPicker.OnChange(function(e) {
var productId = $(e.target).val();
if (productId)
{
if (productId) {
Grocy.Components.ProductCard.Refresh(productId);
Grocy.Api.Get('stock/products/' + productId,
function(productDetails)
{
if (!Grocy.RecipePosFormInitialLoadDone)
{
function(productDetails) {
if (!Grocy.RecipePosFormInitialLoadDone) {
Grocy.Components.ProductAmountPicker.Reload(productDetails.product.id, productDetails.quantity_unit_stock.id, true);
}
else
{
} else {
Grocy.Components.ProductAmountPicker.Reload(productDetails.product.id, productDetails.quantity_unit_stock.id);
}
if (Grocy.Mode == "create")
{
if (Grocy.Mode == "create") {
$("#not_check_stock_fulfillment").prop("checked", productDetails.product.not_check_stock_fulfillment_for_recipes == 1);
}
if (!$("#only_check_single_unit_in_stock").prop("checked") && Grocy.RecipePosFormInitialLoadDone)
{
if (!$("#only_check_single_unit_in_stock").prop("checked") && Grocy.RecipePosFormInitialLoadDone) {
Grocy.Components.ProductAmountPicker.SetQuantityUnit(productDetails.quantity_unit_stock.id);
}
@ -81,8 +64,7 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
Grocy.FrontendHelpers.ValidateForm('recipe-pos-form');
Grocy.RecipePosFormInitialLoadDone = true;
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
@ -91,41 +73,32 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
Grocy.FrontendHelpers.ValidateForm('recipe-pos-form');
if (Grocy.Components.ProductPicker.InProductAddWorkflow() === false)
{
Grocy.Components.ProductPicker.GetInputElement().focus();
if (Grocy.Components.ProductPicker.InProductAddWorkflow() === false) {
Grocy.Components.ProductPicker.Focus();
}
Grocy.Components.ProductPicker.GetPicker().trigger('change');
Grocy.Components.ProductPicker.Validate();
if (Grocy.EditMode == "create")
{
if (Grocy.EditMode == "create") {
Grocy.RecipePosFormInitialLoadDone = true;
}
$('#display_amount').on('focus', function(e)
{
if (Grocy.Components.ProductPicker.GetValue().length === 0)
{
Grocy.Components.ProductPicker.GetInputElement().focus();
}
else
{
$('#display_amount').on('focus', function(e) {
if (Grocy.Components.ProductPicker.GetValue().length === 0) {
Grocy.Components.ProductPicker.Focus();
} else {
$(this).select();
}
});
$('#recipe-pos-form input').keyup(function(event)
{
$('#recipe-pos-form input').keyup(function(event) {
Grocy.FrontendHelpers.ValidateForm('recipe-pos-form');
});
$('#qu_id').change(function(event)
{
$('#qu_id').change(function(event) {
Grocy.FrontendHelpers.ValidateForm('recipe-pos-form');
});
$('#recipe-pos-form input').keydown(function(event)
{
$('#recipe-pos-form input').keydown(function(event) {
if (event.keyCode === 13) //Enter
{
event.preventDefault();
@ -133,32 +106,25 @@ $('#recipe-pos-form input').keydown(function(event)
if (document.getElementById('recipe-pos-form').checkValidity() === false) //There is at least one validation error
{
return false;
}
else
{
} else {
$('#save-recipe-pos-button').click();
}
}
});
$("#only_check_single_unit_in_stock").on("change", function()
{
if (this.checked)
{
$("#only_check_single_unit_in_stock").on("change", function() {
if (this.checked) {
$("#display_amount").attr("min", Grocy.DefaultMinAmount);
Grocy.Components.ProductAmountPicker.AllowAnyQu(true);
Grocy.FrontendHelpers.ValidateForm("recipe-pos-form");
}
else
{
} else {
$("#display_amount").attr("min", "0");
Grocy.Components.ProductPicker.GetPicker().trigger("change"); // Selects the default quantity unit of the selected product
Grocy.Components.ProductPicker.Validate(); // Selects the default quantity unit of the selected product
Grocy.Components.ProductAmountPicker.AllowAnyQuEnabled = false;
Grocy.FrontendHelpers.ValidateForm("recipe-pos-form");
}
});
if ($("#only_check_single_unit_in_stock").prop("checked"))
{
if ($("#only_check_single_unit_in_stock").prop("checked")) {
Grocy.Components.ProductAmountPicker.AllowAnyQu(true);
}

View File

@ -1,47 +1,40 @@
Grocy.ShoppingListItemFormInitialLoadDone = false;
$('#save-shoppinglist-button').on('click', function(e)
{
$('#save-shoppinglist-button').on('click', function(e) {
e.preventDefault();
if ($(".combobox-menu-visible").length)
{
if ($(".combobox-menu-visible").length) {
return;
}
var jsonData = $('#shoppinglist-form').serializeJSON();
var displayAmount = parseFloat(jsonData.display_amount);
if (!jsonData.product_id)
{
if (!jsonData.product_id) {
jsonData.amount = jsonData.display_amount;
}
delete jsonData.display_amount;
Grocy.FrontendHelpers.BeginUiBusy("shoppinglist-form");
if (GetUriParam("flow") === "InplaceAddBarcodeToExistingProduct")
{
if (GetUriParam("flow") === "InplaceAddBarcodeToExistingProduct") {
var jsonDataBarcode = {};
jsonDataBarcode.barcode = GetUriParam("barcode");
jsonDataBarcode.product_id = jsonData.product_id;
Grocy.Api.Post('objects/product_barcodes', jsonDataBarcode,
function(result)
{
function(result) {
$("#flow-info-InplaceAddBarcodeToExistingProduct").addClass("d-none");
$('#barcode-lookup-disabled-hint').addClass('d-none');
$('#barcode-lookup-hint').removeClass('d-none');
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy("shoppinglist-form");
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
}
);
}
if (GetUriParam("updateexistingproduct") !== undefined)
{
if (GetUriParam("updateexistingproduct") !== undefined) {
jsonData.product_amount = jsonData.amount;
delete jsonData.amount;
@ -49,118 +42,96 @@ $('#save-shoppinglist-button').on('click', function(e)
delete jsonData.shopping_list_id;
Grocy.Api.Post('stock/shoppinglist/add-product', jsonData,
function(result)
{
function(result) {
Grocy.EditObjectId = result.created_object_id;
Grocy.Components.UserfieldsForm.Save();
if (GetUriParam("embedded") !== undefined)
{
if (GetUriParam("embedded") !== undefined) {
Grocy.Api.Get('stock/products/' + jsonData.product_id,
function(productDetails)
{
window.parent.postMessage(WindowMessageBag("ShowSuccessMessage", __t("Added %1$s of %2$s to the shopping list \"%3$s\"", displayAmount.toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts }) + " " + __n(displayAmount, $("#qu_id option:selected").text(), $("#qu_id option:selected").attr("data-qu-name-plural"), true), productDetails.product.name, $("#shopping_list_id option:selected").text())), Grocy.BaseUrl);
function(productDetails) {
window.parent.postMessage(WindowMessageBag("ShowSuccessMessage", __t("Added %1$s of %2$s to the shopping list \"%3$s\"", displayAmount.toLocaleString({
minimumFractionDigits: 0,
maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts
}) + " " + __n(displayAmount, $("#qu_id option:selected").text(), $("#qu_id option:selected").attr("data-qu-name-plural"), true), productDetails.product.name, $("#shopping_list_id option:selected").text())), Grocy.BaseUrl);
window.parent.postMessage(WindowMessageBag("ShoppingListChanged", $("#shopping_list_id").val().toString()), Grocy.BaseUrl);
window.parent.postMessage(WindowMessageBag("CloseAllModals"), Grocy.BaseUrl);
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
}
else
{
} else {
window.location.href = U('/shoppinglist?list=' + $("#shopping_list_id").val().toString());
}
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy("shoppinglist-form");
console.error(xhr);
}
);
}
else if (Grocy.EditMode === 'create')
{
} else if (Grocy.EditMode === 'create') {
Grocy.Api.Post('objects/shopping_list', jsonData,
function(result)
{
function(result) {
Grocy.EditObjectId = result.created_object_id;
Grocy.Components.UserfieldsForm.Save();
if (GetUriParam("embedded") !== undefined)
{
if (jsonData.product_id)
{
if (GetUriParam("embedded") !== undefined) {
if (jsonData.product_id) {
Grocy.Api.Get('stock/products/' + jsonData.product_id,
function(productDetails)
{
window.parent.postMessage(WindowMessageBag("ShowSuccessMessage", __t("Added %1$s of %2$s to the shopping list \"%3$s\"", displayAmount.toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts }) + " " + __n(displayAmount, $("#qu_id option:selected").text(), $("#qu_id option:selected").attr("data-qu-name-plural"), true), productDetails.product.name, $("#shopping_list_id option:selected").text())), Grocy.BaseUrl);
function(productDetails) {
window.parent.postMessage(WindowMessageBag("ShowSuccessMessage", __t("Added %1$s of %2$s to the shopping list \"%3$s\"", displayAmount.toLocaleString({
minimumFractionDigits: 0,
maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts
}) + " " + __n(displayAmount, $("#qu_id option:selected").text(), $("#qu_id option:selected").attr("data-qu-name-plural"), true), productDetails.product.name, $("#shopping_list_id option:selected").text())), Grocy.BaseUrl);
window.parent.postMessage(WindowMessageBag("ShoppingListChanged", $("#shopping_list_id").val().toString()), Grocy.BaseUrl);
window.parent.postMessage(WindowMessageBag("CloseAllModals"), Grocy.BaseUrl);
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
}
else
{
} else {
window.parent.postMessage(WindowMessageBag("ShoppingListChanged", $("#shopping_list_id").val().toString()), Grocy.BaseUrl);
window.parent.postMessage(WindowMessageBag("CloseAllModals"), Grocy.BaseUrl);
}
}
else
{
} else {
window.location.href = U('/shoppinglist?list=' + $("#shopping_list_id").val().toString());
}
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy("shoppinglist-form");
console.error(xhr);
}
);
}
else
{
} else {
Grocy.Api.Put('objects/shopping_list/' + Grocy.EditObjectId, jsonData,
function(result)
{
function(result) {
Grocy.Components.UserfieldsForm.Save();
if (GetUriParam("embedded") !== undefined)
{
if (jsonData.product_id)
{
if (GetUriParam("embedded") !== undefined) {
if (jsonData.product_id) {
Grocy.Api.Get('stock/products/' + jsonData.product_id,
function(productDetails)
{
window.parent.postMessage(WindowMessageBag("ShowSuccessMessage", __t("Added %1$s of %2$s to the shopping list \"%3$s\"", displayAmount.toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts }) + " " + __n(displayAmount, $("#qu_id option:selected").text(), $("#qu_id option:selected").attr("data-qu-name-plural"), true), productDetails.product.name, $("#shopping_list_id option:selected").text())), Grocy.BaseUrl);
function(productDetails) {
window.parent.postMessage(WindowMessageBag("ShowSuccessMessage", __t("Added %1$s of %2$s to the shopping list \"%3$s\"", displayAmount.toLocaleString({
minimumFractionDigits: 0,
maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts
}) + " " + __n(displayAmount, $("#qu_id option:selected").text(), $("#qu_id option:selected").attr("data-qu-name-plural"), true), productDetails.product.name, $("#shopping_list_id option:selected").text())), Grocy.BaseUrl);
window.parent.postMessage(WindowMessageBag("ShoppingListChanged", $("#shopping_list_id").val().toString()), Grocy.BaseUrl);
window.parent.postMessage(WindowMessageBag("CloseAllModals"), Grocy.BaseUrl);
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
}
else
{
} else {
window.parent.postMessage(WindowMessageBag("ShoppingListChanged", $("#shopping_list_id").val().toString()), Grocy.BaseUrl);
window.parent.postMessage(WindowMessageBag("CloseAllModals"), Grocy.BaseUrl);
}
}
else
{
} else {
window.location.href = U('/shoppinglist?list=' + $("#shopping_list_id").val().toString());
}
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy("shoppinglist-form");
console.error(xhr);
}
@ -168,74 +139,57 @@ $('#save-shoppinglist-button').on('click', function(e)
}
});
Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
{
Grocy.Components.ProductPicker.OnChange(function(e) {
var productId = $(e.target).val();
if (productId)
{
if (productId) {
Grocy.Api.Get('stock/products/' + productId,
function(productDetails)
{
if (!Grocy.ShoppingListItemFormInitialLoadDone)
{
function(productDetails) {
if (!Grocy.ShoppingListItemFormInitialLoadDone) {
Grocy.Components.ProductAmountPicker.Reload(productDetails.product.id, productDetails.quantity_unit_stock.id, true);
}
else
{
} else {
Grocy.Components.ProductAmountPicker.Reload(productDetails.product.id, productDetails.quantity_unit_stock.id);
Grocy.Components.ProductAmountPicker.SetQuantityUnit(productDetails.default_quantity_unit_purchase.id);
}
if ($("#display_amount").val().toString().isEmpty())
{
if ($("#display_amount").val().toString().isEmpty()) {
$("#display_amount").val(1);
$("#display_amount").trigger("change");
}
$('#display_amount').focus();
$('#display_amount').trigger('focus');
Grocy.FrontendHelpers.ValidateForm('shoppinglist-form');
Grocy.ShoppingListItemFormInitialLoadDone = true;
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
}
// TODO: what is the point of this?
$("#note").trigger("input");
$("#product_id").trigger("input");
// Grocy.Components.ProductPicker.GetPicker().trigger("input");
});
Grocy.FrontendHelpers.ValidateForm('shoppinglist-form');
setTimeout(function()
{
Grocy.Components.ProductPicker.GetInputElement().focus();
setTimeout(function() {
Grocy.Components.ProductPicker.Focus();
}, 250);
if (Grocy.EditMode === "edit")
{
Grocy.Components.ProductPicker.GetPicker().trigger('change');
}
if (Grocy.EditMode == "create")
{
if (Grocy.EditMode == "create") {
Grocy.ShoppingListItemFormInitialLoadDone = true;
}
$('#display_amount').on('focus', function(e)
{
$(this).select();
$('#display_amount').on('focus', function(e) {
$(this).trigger('select');
});
$('#shoppinglist-form input').keyup(function(event)
{
$('#shoppinglist-form input').on('keyup', function(event) {
Grocy.FrontendHelpers.ValidateForm('shoppinglist-form');
});
$('#shoppinglist-form input').keydown(function(event)
{
$('#shoppinglist-form input').on('keydown', function(event) {
if (event.keyCode === 13) //Enter
{
event.preventDefault();
@ -243,52 +197,48 @@ $('#shoppinglist-form input').keydown(function(event)
if (document.getElementById('shoppinglist-form').checkValidity() === false) //There is at least one validation error
{
return false;
}
else
{
} else {
$('#save-shoppinglist-button').click();
}
}
});
if (GetUriParam("list") !== undefined)
{
if (GetUriParam("list") !== undefined) {
$("#shopping_list_id").val(GetUriParam("list"));
}
if (GetUriParam("amount") !== undefined)
{
if (GetUriParam("amount") !== undefined) {
$("#display_amount").val(parseFloat(GetUriParam("amount")));
RefreshLocaleNumberInput();
$(".input-group-productamountpicker").trigger("change");
Grocy.FrontendHelpers.ValidateForm('shoppinglist-form');
}
if (GetUriParam("embedded") !== undefined)
{
if (GetUriParam("product") !== undefined)
{
Grocy.Components.ProductPicker.GetPicker().trigger('change');
$("#display_amount").focus();
}
else
{
Grocy.Components.ProductPicker.GetInputElement().focus();
if (GetUriParam("embedded") !== undefined) {
if (GetUriParam("product") !== undefined) {
$("#display_amount").trigger('focus');
} else {
Grocy.Components.ProductPicker.Focus();
}
}
var eitherRequiredFields = $("#product_id,#product_id_text_input,#note");
eitherRequiredFields.prop('required', "");
eitherRequiredFields.on('input', function()
{
eitherRequiredFields.not(this).prop('required', !$(this).val().length);
$("#note").prop('required', "");
$("#note").on('input', function() {
if (!$(this).val().length) {
Grocy.Components.ProductPicker.Require();
} else {
Grocy.Components.ProductPicker.Optional();
}
Grocy.FrontendHelpers.ValidateForm('shoppinglist-form');
});
Grocy.Components.ProductPicker.OnChange(function() {
$("#note").prop('required', !$(this).val().length);
Grocy.FrontendHelpers.ValidateForm('shoppinglist-form');
});
if (GetUriParam("product-name") != null)
{
Grocy.Components.ProductPicker.GetPicker().trigger('change');
if (GetUriParam("product-name") != null) {
Grocy.Components.ProductPicker.Validate();
}
Grocy.Components.UserfieldsForm.Load();

View File

@ -1,50 +1,68 @@
var stockEntriesTable = $('#stockentries-table').DataTable({
'order': [[2, 'asc']],
'columnDefs': [
{ 'orderable': false, 'targets': 0 },
{ 'searchable': false, "targets": 0 },
{ 'visible': false, 'targets': 10 },
{ "type": "num", "targets": 1 },
{ "type": "num", "targets": 3 },
{ "type": "html", "targets": 4 },
{ "type": "html-num-fmt", "targets": 7 },
{ "type": "html", "targets": 8 },
{ "type": "html", "targets": 9 }
'order': [
[2, 'asc']
],
'columnDefs': [{
'orderable': false,
'targets': 0
},
{
'searchable': false,
"targets": 0
},
{
'visible': false,
'targets': 10
},
{
"type": "num",
"targets": 1
},
{
"type": "num",
"targets": 3
},
{
"type": "html",
"targets": 4
},
{
"type": "html-num-fmt",
"targets": 7
},
{
"type": "html",
"targets": 8
},
{
"type": "html",
"targets": 9
}
].concat($.fn.dataTable.defaults.columnDefs)
});
$('#stockentries-table tbody').removeClass("d-none");
stockEntriesTable.columns.adjust().draw();
$.fn.dataTable.ext.search.push(function(settings, data, dataIndex)
{
$.fn.dataTable.ext.search.push(function(settings, data, dataIndex) {
var productId = Grocy.Components.ProductPicker.GetValue();
if ((isNaN(productId) || productId == "" || productId == data[1]))
{
if ((isNaN(productId) || productId == "" || productId == data[1])) {
return true;
}
return false;
});
$("#clear-filter-button").on("click", function()
{
$("#clear-filter-button").on("click", function() {
Grocy.Components.ProductPicker.Clear();
stockEntriesTable.draw();
});
Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
{
Grocy.Components.ProductPicker.OnChange(function(e) {
stockEntriesTable.draw();
});
Grocy.Components.ProductPicker.GetInputElement().on('keyup', function(e)
{
stockEntriesTable.draw();
});
$(document).on('click', '.stock-consume-button', function(e)
{
$(document).on('click', '.stock-consume-button', function(e) {
e.preventDefault();
// Remove the focus from the current button
@ -61,15 +79,21 @@ $(document).on('click', '.stock-consume-button', function(e)
var wasSpoiled = $(e.currentTarget).hasClass("stock-consume-button-spoiled");
Grocy.Api.Post('stock/products/' + productId + '/consume', { 'amount': consumeAmount, 'spoiled': wasSpoiled, 'location_id': locationId, 'stock_entry_id': specificStockEntryId, 'exact_amount': true },
function(bookingResponse)
{
Grocy.Api.Post('stock/products/' + productId + '/consume', {
'amount': consumeAmount,
'spoiled': wasSpoiled,
'location_id': locationId,
'stock_entry_id': specificStockEntryId,
'exact_amount': true
},
function(bookingResponse) {
Grocy.Api.Get('stock/products/' + productId,
function(result)
{
var toastMessage = __t('Removed %1$s of %2$s from stock', parseFloat(consumeAmount).toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts }) + " " + __n(consumeAmount, result.quantity_unit_stock.name, result.quantity_unit_stock.name_plural, true), result.product.name) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockBookingEntry(' + bookingResponse[0].id + ',' + stockRowId + ')"><i class="fas fa-undo"></i> ' + __t("Undo") + '</a>';
if (wasSpoiled)
{
function(result) {
var toastMessage = __t('Removed %1$s of %2$s from stock', parseFloat(consumeAmount).toLocaleString({
minimumFractionDigits: 0,
maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts
}) + " " + __n(consumeAmount, result.quantity_unit_stock.name, result.quantity_unit_stock.name_plural, true), result.product.name) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockBookingEntry(' + bookingResponse[0].id + ',' + stockRowId + ')"><i class="fas fa-undo"></i> ' + __t("Undo") + '</a>';
if (wasSpoiled) {
toastMessage += " (" + __t("Spoiled") + ")";
}
@ -77,23 +101,20 @@ $(document).on('click', '.stock-consume-button', function(e)
RefreshStockEntryRow(stockRowId);
toastr.success(toastMessage);
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr);
}
);
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr);
}
);
});
$(document).on('click', '.product-open-button', function(e)
{
$(document).on('click', '.product-open-button', function(e) {
e.preventDefault();
// Remove the focus from the current button
@ -109,65 +130,55 @@ $(document).on('click', '.product-open-button', function(e)
var stockRowId = $(e.currentTarget).attr('data-stockrow-id');
var button = $(e.currentTarget);
Grocy.Api.Post('stock/products/' + productId + '/open', { 'amount': 1, 'stock_entry_id': specificStockEntryId },
function(bookingResponse)
{
Grocy.Api.Post('stock/products/' + productId + '/open', {
'amount': 1,
'stock_entry_id': specificStockEntryId
},
function(bookingResponse) {
button.addClass("disabled");
Grocy.FrontendHelpers.EndUiBusy();
toastr.success(__t('Marked %1$s of %2$s as opened', 1 + " " + productQuName, productName) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockBookingEntry(' + bookingResponse[0].id + ',' + stockRowId + ')"><i class="fas fa-undo"></i> ' + __t("Undo") + '</a>');
RefreshStockEntryRow(stockRowId);
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr);
}
);
});
$(document).on("click", ".stock-name-cell", function(e)
{
$(document).on("click", ".stock-name-cell", function(e) {
Grocy.Components.ProductCard.Refresh($(e.currentTarget).attr("data-stock-id"));
$("#stockentry-productcard-modal").modal("show");
});
$(document).on('click', '.stockentry-grocycode-label-print', function(e)
{
$(document).on('click', '.stockentry-grocycode-label-print', function(e) {
e.preventDefault();
document.activeElement.blur();
var stockId = $(e.currentTarget).attr('data-stock-id');
Grocy.Api.Get('stock/entry/' + stockId + '/printlabel', function(labelData)
{
if (Grocy.Webhooks.labelprinter !== undefined)
{
Grocy.Api.Get('stock/entry/' + stockId + '/printlabel', function(labelData) {
if (Grocy.Webhooks.labelprinter !== undefined) {
Grocy.FrontendHelpers.RunWebhook(Grocy.Webhooks.labelprinter, labelData);
}
});
});
function RefreshStockEntryRow(stockRowId)
{
function RefreshStockEntryRow(stockRowId) {
Grocy.Api.Get("stock/entry/" + stockRowId,
function(result)
{
function(result) {
var stockRow = $('#stock-' + stockRowId + '-row');
// If the stock row not exists / is invisible (happens after consume/undo because the undone new stock row has different id), just reload the page for now
if (!stockRow.length || stockRow.hasClass("d-none"))
{
if (!stockRow.length || stockRow.hasClass("d-none")) {
window.location.reload();
}
if (result == null || result.amount == 0)
{
animateCSS("#stock-" + stockRowId + "-row", "fadeOut", function()
{
if (result == null || result.amount == 0) {
animateCSS("#stock-" + stockRowId + "-row", "fadeOut", function() {
$("#stock-" + stockRowId + "-row").addClass("d-none");
});
}
else
{
} else {
var dueThreshold = moment().add(Grocy.UserSettings.stock_due_soon_days, "days");
var now = moment();
var bestBeforeDate = moment(result.best_before_date);
@ -177,19 +188,13 @@ function RefreshStockEntryRow(stockRowId)
stockRow.removeClass("table-info");
stockRow.removeClass("d-none");
stockRow.removeAttr("style");
if (now.isAfter(bestBeforeDate))
{
if (stockRow.attr("data-due-type") == 1)
{
if (now.isAfter(bestBeforeDate)) {
if (stockRow.attr("data-due-type") == 1) {
stockRow.addClass("table-secondary");
}
else
{
} else {
stockRow.addClass("table-danger");
}
}
else if (bestBeforeDate.isBefore(dueThreshold))
{
} else if (bestBeforeDate.isBefore(dueThreshold)) {
stockRow.addClass("table-warning");
}
@ -203,15 +208,13 @@ function RefreshStockEntryRow(stockRowId)
var locationName = "";
Grocy.Api.Get("objects/locations/" + result.location_id,
function(locationResult)
{
function(locationResult) {
locationName = locationResult.name;
$('#stock-' + stockRowId + '-location').attr('data-location-id', result.location_id);
$('#stock-' + stockRowId + '-location').text(locationName);
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
@ -222,74 +225,61 @@ function RefreshStockEntryRow(stockRowId)
var shoppingLocationName = "";
Grocy.Api.Get("objects/shopping_locations/" + result.shopping_location_id,
function(shoppingLocationResult)
{
function(shoppingLocationResult) {
shoppingLocationName = shoppingLocationResult.name;
$('#stock-' + stockRowId + '-shopping-location').attr('data-shopping-location-id', result.location_id);
$('#stock-' + stockRowId + '-shopping-location').text(shoppingLocationName);
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
if (result.open == 1)
{
if (result.open == 1) {
$('#stock-' + stockRowId + '-opened-amount').text(__t('Opened'));
}
else
{
} else {
$('#stock-' + stockRowId + '-opened-amount').text("");
$(".product-open-button[data-stockrow-id='" + stockRowId + "']").removeClass("disabled");
}
}
// Needs to be delayed because of the animation above the date-text would be wrong if fired immediately...
setTimeout(function()
{
setTimeout(function() {
RefreshContextualTimeago("#stock-" + stockRowId + "-row");
RefreshLocaleNumberDisplay("#stock-" + stockRowId + "-row");
}, 600);
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy();
console.error(xhr);
}
);
}
$(window).on("message", function(e)
{
$(window).on("message", function(e) {
var data = e.originalEvent.data;
if (data.Message === "StockEntryChanged")
{
if (data.Message === "StockEntryChanged") {
RefreshStockEntryRow(data.Payload);
}
});
Grocy.Components.ProductPicker.GetPicker().trigger('change');
Grocy.Components.ProductPicker.Validate();
function UndoStockBookingEntry(bookingId, stockRowId)
{
function UndoStockBookingEntry(bookingId, stockRowId) {
Grocy.Api.Post('stock/bookings/' + bookingId.toString() + '/undo', {},
function(result)
{
function(result) {
window.postMessage(WindowMessageBag("StockEntryChanged", stockRowId), Grocy.BaseUrl);
toastr.success(__t("Booking successfully undone"));
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
};
$(document).on("click", ".product-name-cell", function(e)
{
$(document).on("click", ".product-name-cell", function(e) {
Grocy.Components.ProductCard.Refresh($(e.currentTarget).attr("data-product-id"));
$("#productcard-modal").modal("show");
});

View File

@ -1,83 +1,118 @@
var stockJournalTable = $('#stock-journal-table').DataTable({
'order': [[3, 'desc']],
'columnDefs': [
{ 'orderable': false, 'targets': 0 },
{ 'searchable': false, "targets": 0 }
'order': [
[3, 'desc']
],
'columnDefs': [{
'orderable': false,
'targets': 0
},
{
'searchable': false,
"targets": 0
}
].concat($.fn.dataTable.defaults.columnDefs)
});
$('#stock-journal-table tbody').removeClass("d-none");
stockJournalTable.columns.adjust().draw();
$("#product-filter").on("change", function()
{
var value = $(this).val();
if (value === "all")
{
RemoveUriParam("product");
}
else
{
UpdateUriParam("product", value);
}
$("#product-filter").select2({
ajax: {
delay: 150,
transport: function(params, success, failure) {
var results_per_page = 10;
var page = params.data.page || 1;
var term = params.data.term || "";
window.location.reload();
var query = [];
query.push('query%5B%5D=active%3D1');
query.push('limit=' + encodeURIComponent(results_per_page));
query.push('offset=' + encodeURIComponent((page - 1) * results_per_page));
query.push('order=name%3Acollate%20nocase');
if (term.length > 0) {
query.push('search=' + encodeURIComponent(term));
}
Grocy.Api.Get('objects/products' + (query.length > 0 ? '?' + query.join('&') : ''),
function(results, meta) {
success({
results: [{
id: 'all',
text: __t('All')
}].concat(results.map(function(result) {
return {
id: result.id,
text: result.name
};
})),
pagination: {
more: page * results_per_page < meta.recordsFiltered
}
});
},
function(xhr) {
failure();
}
);
}
}
});
$("#transaction-type-filter").on("change", function()
{
$("#product-filter").on("change", function() {
var value = $(this).val();
if (value === "all" && GetUriParam("product") !== undefined) {
RemoveUriParam("product");
window.location.reload();
} else if (GetUriParam("product") !== value) {
UpdateUriParam("product", value);
window.location.reload();
}
});
$("#transaction-type-filter").on("change", function() {
var value = $(this).val();
var text = $("#transaction-type-filter option:selected").text();
if (value === "all")
{
if (value === "all") {
text = "";
}
stockJournalTable.column(stockJournalTable.colReorder.transpose(4)).search(text).draw();
});
$("#location-filter").on("change", function()
{
$("#location-filter").on("change", function() {
var value = $(this).val();
var text = $("#location-filter option:selected").text();
if (value === "all")
{
if (value === "all") {
text = "";
}
stockJournalTable.column(stockJournalTable.colReorder.transpose(5)).search(text).draw();
});
$("#user-filter").on("change", function()
{
$("#user-filter").on("change", function() {
var value = $(this).val();
var text = $("#user-filter option:selected").text();
if (value === "all")
{
if (value === "all") {
text = "";
}
stockJournalTable.column(stockJournalTable.colReorder.transpose(6)).search(text).draw();
});
$("#daterange-filter").on("change", function()
{
$("#daterange-filter").on("change", function() {
UpdateUriParam("months", $(this).val());
window.location.reload();
});
$("#search").on("keyup", Delay(function()
{
$("#search").on("keyup", Delay(function() {
var value = $(this).val();
if (value === "all")
{
if (value === "all") {
value = "";
}
stockJournalTable.search(value).draw();
}, 200));
$("#clear-filter-button").on("click", function()
{
$("#clear-filter-button").on("click", function() {
$("#search").val("");
$("#transaction-type-filter").val("all");
$("#location-filter").val("all");
@ -90,40 +125,47 @@ $("#clear-filter-button").on("click", function()
window.location.reload();
});
if (typeof GetUriParam("product") !== "undefined")
{
$("#product-filter").val(GetUriParam("product"));
var prefillProductId = GetUriParam("product");
if (typeof prefillProductId !== "undefined") {
if ($("#product-filter").find('option[value="' + prefillProductId + '"]').length) {
$("#product-filter").val(prefillProductId).trigger('change');
} else {
Grocy.Api.Get('objects/products/' + encodeURIComponent(prefillProductId),
function(result) {
var option = new Option(result.name, prefillProductId, true, true);
$("#product-filter").append(option).trigger('change');
},
function(xhr) {
console.error(xhr);
}
);
}
}
if (typeof GetUriParam("months") !== "undefined")
{
if (typeof GetUriParam("months") !== "undefined") {
$("#daterange-filter").val(GetUriParam("months"));
}
$(document).on('click', '.undo-stock-booking-button', function(e)
{
$(document).on('click', '.undo-stock-booking-button', function(e) {
e.preventDefault();
var bookingId = $(e.currentTarget).attr('data-booking-id');
var correlationId = $("#stock-booking-" + bookingId + "-row").attr("data-correlation-id");
var correspondingBookingsRoot = $("#stock-booking-" + bookingId + "-row");
if (!correlationId.isEmpty())
{
if (!correlationId.isEmpty()) {
correspondingBookingsRoot = $(".stock-booking-correlation-" + correlationId);
}
Grocy.Api.Post('stock/bookings/' + bookingId.toString() + '/undo', {},
function(result)
{
function(result) {
correspondingBookingsRoot.addClass("text-muted");
correspondingBookingsRoot.find("span.name-anchor").addClass("text-strike-through").after("<br>" + __t("Undone on") + " " + moment().format("YYYY-MM-DD HH:mm:ss") + " <time class='timeago timeago-contextual' datetime='" + moment().format("YYYY-MM-DD HH:mm:ss") + "'></time>");
correspondingBookingsRoot.find(".undo-stock-booking-button").addClass("disabled");
RefreshContextualTimeago("#stock-booking-" + bookingId + "-row");
toastr.success(__t("Booking successfully undone"));
},
function(xhr)
{
function(xhr) {
console.error(xhr);
toastr.error(__t(JSON.parse(xhr.response).error_message));
}

View File

@ -1,64 +1,102 @@
var journalSummaryTable = $('#stock-journal-summary-table').DataTable({
'order': [[1, 'asc']],
'columnDefs': [
{ 'orderable': false, 'targets': 0 },
{ 'searchable': false, "targets": 0 }
'order': [
[1, 'asc']
],
'columnDefs': [{
'orderable': false,
'targets': 0
},
{
'searchable': false,
"targets": 0
}
].concat($.fn.dataTable.defaults.columnDefs)
});
$('#stock-journal-summary-table tbody').removeClass("d-none");
journalSummaryTable.columns.adjust().draw();
$("#product-filter").on("change", function()
{
var value = $(this).val();
var text = $("#product-filter option:selected").text();
if (value === "all")
{
journalSummaryTable.column(journalSummaryTable.colReorder.transpose(1)).search("").draw();
$("#product-filter").select2({
ajax: {
delay: 150,
transport: function(params, success, failure) {
var results_per_page = 10;
var page = params.data.page || 1;
var term = params.data.term || "";
var query = [];
query.push('query%5B%5D=active%3D1');
query.push('limit=' + encodeURIComponent(results_per_page));
query.push('offset=' + encodeURIComponent((page - 1) * results_per_page));
query.push('order=name%3Acollate%20nocase');
if (term.length > 0) {
query.push('search=' + encodeURIComponent(term));
}
Grocy.Api.Get('objects/products' + (query.length > 0 ? '?' + query.join('&') : ''),
function(results, meta) {
success({
results: [{
id: 'all',
text: __t('All')
}].concat(results.map(function(result) {
return {
id: result.id,
text: result.name
};
})),
pagination: {
more: page * results_per_page < meta.recordsFiltered
}
});
},
function(xhr) {
failure();
}
);
}
}
else
{
});
$("#product-filter").on("change", function() {
var value = $(this).val();
var text = $("#product-filter option:selected").text().trim();
if (value === "all") {
journalSummaryTable.column(journalSummaryTable.colReorder.transpose(1)).search("").draw();
} else {
journalSummaryTable.column(journalSummaryTable.colReorder.transpose(1)).search("^" + text + "$", true, false).draw();
}
});
$("#transaction-type-filter").on("change", function()
{
$("#transaction-type-filter").on("change", function() {
var value = $(this).val();
var text = $("#transaction-type-filter option:selected").text();
if (value === "all")
{
if (value === "all") {
text = "";
}
journalSummaryTable.column(journalSummaryTable.colReorder.transpose(2)).search(text).draw();
});
$("#user-filter").on("change", function()
{
$("#user-filter").on("change", function() {
var value = $(this).val();
var text = $("#user-filter option:selected").text();
if (value === "all")
{
if (value === "all") {
text = "";
}
journalSummaryTable.column(journalSummaryTable.colReorder.transpose(3)).search(text).draw();
});
$("#search").on("keyup", Delay(function()
{
$("#search").on("keyup", Delay(function() {
var value = $(this).val();
if (value === "all")
{
if (value === "all") {
value = "";
}
journalSummaryTable.search(value).draw();
}, 200));
$("#clear-filter-button").on("click", function()
{
$("#clear-filter-button").on("click", function() {
$("#search").val("");
$("#transaction-type-filter").val("all");
$("#location-filter").val("all");

View File

@ -1,9 +1,7 @@
$('#save-transfer-button').on('click', function(e)
{
$('#save-transfer-button').on('click', function(e) {
e.preventDefault();
if ($(".combobox-menu-visible").length)
{
if ($(".combobox-menu-visible").length) {
return;
}
@ -17,60 +15,48 @@
jsonData.location_id_to = $("#location_id_to").val();
jsonData.location_id_from = $("#location_id_from").val();
if ($("#use_specific_stock_entry").is(":checked"))
{
if ($("#use_specific_stock_entry").is(":checked")) {
jsonData.stock_entry_id = jsonForm.specific_stock_entry;
}
var bookingResponse = null;
Grocy.Api.Get('stock/products/' + jsonForm.product_id,
function(productDetails)
{
function(productDetails) {
Grocy.Api.Post(apiUrl, jsonData,
function(result)
{
function(result) {
bookingResponse = result;
if (GetUriParam("flow") === "InplaceAddBarcodeToExistingProduct")
{
if (GetUriParam("flow") === "InplaceAddBarcodeToExistingProduct") {
var jsonDataBarcode = {};
jsonDataBarcode.barcode = GetUriParam("barcode");
jsonDataBarcode.product_id = jsonForm.product_id;
Grocy.Api.Post('objects/product_barcodes', jsonDataBarcode,
function(result)
{
function(result) {
$("#flow-info-InplaceAddBarcodeToExistingProduct").addClass("d-none");
$('#barcode-lookup-disabled-hint').addClass('d-none');
$('#barcode-lookup-hint').removeClass('d-none');
window.history.replaceState({}, document.title, U("/transfer"));
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy("transfer-form");
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
}
);
}
if (productDetails.product.enable_tare_weight_handling == 1)
{
if (productDetails.product.enable_tare_weight_handling == 1) {
var successMessage = __t('Transfered %1$s of %2$s from %3$s to %4$s', Math.abs(jsonForm.amount - parseFloat(productDetails.product.tare_weight)) + " " + __n(jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural, true), productDetails.product.name, $('option:selected', "#location_id_from").text(), $('option:selected', "#location_id_to").text()) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockTransaction(\'' + bookingResponse[0].transaction_id + '\')"><i class="fas fa-undo"></i> ' + __t("Undo") + '</a>';
}
else
{
} else {
var successMessage = __t('Transfered %1$s of %2$s from %3$s to %4$s', Math.abs(jsonForm.amount) + " " + __n(jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural, true), productDetails.product.name, $('option:selected', "#location_id_from").text(), $('option:selected', "#location_id_to").text()) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockTransaction(\'' + bookingResponse[0].transaction_id + '\')"><i class="fas fa-undo"></i> ' + __t("Undo") + '</a>';
}
if (GetUriParam("embedded") !== undefined)
{
if (GetUriParam("embedded") !== undefined) {
window.parent.postMessage(WindowMessageBag("ProductChanged", jsonForm.product_id), Grocy.BaseUrl);
window.parent.postMessage(WindowMessageBag("ShowSuccessMessage", successMessage), Grocy.BaseUrl);
window.parent.postMessage(WindowMessageBag("CloseAllModals"), Grocy.BaseUrl);
}
else
{
} else {
Grocy.FrontendHelpers.EndUiBusy("transfer-form");
toastr.success(successMessage);
Grocy.Components.ProductPicker.FinishFlow();
@ -79,8 +65,7 @@
{
toastr.info('<span>' + __t("Frozen") + "</span> <i class='fas fa-snowflake'></i>");
if (BoolVal(productDetails.product.should_not_be_frozen))
{
if (BoolVal(productDetails.product.should_not_be_frozen)) {
toastr.warning(__t("This product shouldn't be frozen"));
}
}
@ -92,8 +77,7 @@
$("#specific_stock_entry").find("option").remove().end().append("<option></option>");
$("#specific_stock_entry").attr("disabled", "");
$("#specific_stock_entry").removeAttr("required");
if ($("#use_specific_stock_entry").is(":checked"))
{
if ($("#use_specific_stock_entry").is(":checked")) {
$("#use_specific_stock_entry").click();
}
@ -108,67 +92,56 @@
Grocy.Components.ProductPicker.Clear();
$("#location_id_to").val("");
$("#location_id_from").val("");
Grocy.Components.ProductPicker.GetInputElement().focus();
Grocy.Components.ProductPicker.Focus();
Grocy.Components.ProductCard.Refresh(jsonForm.product_id);
Grocy.FrontendHelpers.ValidateForm('transfer-form');
}
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy("transfer-form");
console.error(xhr);
}
);
},
function(xhr)
{
function(xhr) {
Grocy.FrontendHelpers.EndUiBusy("transfer-form");
console.error(xhr);
}
);
});
Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
{
Grocy.Components.ProductPicker.OnChange(function(e) {
$("#specific_stock_entry").find("option").remove().end().append("<option></option>");
if ($("#use_specific_stock_entry").is(":checked") && GetUriParam("stockId") == null)
{
$("#use_specific_stock_entry").click();
if ($("#use_specific_stock_entry").is(":checked") && GetUriParam("stockId") == null) {
$("#use_specific_stock_entry").trigger('click');
}
$("#location_id_to").val("");
if (GetUriParam("stockId") == null)
{
if (GetUriParam("stockId") == null) {
$("#location_id_from").val("");
}
var productId = $(e.target).val();
if (productId)
{
if (productId) {
Grocy.Components.ProductCard.Refresh(productId);
Grocy.Api.Get('stock/products/' + productId,
function(productDetails)
{
function(productDetails) {
Grocy.Components.ProductAmountPicker.Reload(productDetails.product.id, productDetails.quantity_unit_stock.id);
Grocy.Components.ProductAmountPicker.SetQuantityUnit(productDetails.quantity_unit_stock.id);
if (productDetails.product.enable_tare_weight_handling == 1)
{
Grocy.Components.ProductPicker.GetPicker().parent().find(".invalid-feedback").text(__t('Products with tare weight enabled are currently not supported for transfer'));
if (productDetails.product.enable_tare_weight_handling == 1) {
Grocy.Components.ProductPicker.ShowCustomError(__t('Products with tare weight enabled are currently not supported for transfer'));
Grocy.Components.ProductPicker.Clear();
return;
}
$("#location_id_from").find("option").remove().end().append("<option></option>");
Grocy.Api.Get("stock/products/" + productId + '/locations',
function(stockLocations)
{
function(stockLocations) {
var setDefault = 0;
stockLocations.forEach(stockLocation =>
{
if (productDetails.location.id == stockLocation.location_id)
{
stockLocations.forEach(stockLocation => {
if (productDetails.location.id == stockLocation.location_id) {
$("#location_id_from").append($("<option>", {
value: stockLocation.location_id,
text: stockLocation.location_name + " (" + __t("Default location") + ")",
@ -177,9 +150,7 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
$("#location_id_from").val(productDetails.location.id);
$("#location_id_from").trigger('change');
setDefault = 1;
}
else
{
} else {
$("#location_id_from").append($("<option>", {
value: stockLocation.location_id,
text: stockLocation.location_name,
@ -187,44 +158,35 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
}));
}
if (setDefault == 0)
{
if (setDefault == 0) {
$("#location_id_from").val(stockLocation.location_id);
$("#location_id_from").trigger('change');
}
});
if (GetUriParam("locationId") != null)
{
if (GetUriParam("locationId") != null) {
$("#location_id_from").val(GetUriParam("locationId"));
$("#location_id_from").trigger("change");
}
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
if (document.getElementById("product_id").getAttribute("barcode") != "null")
{
if (document.getElementById("product_id").getAttribute("barcode") != "null") {
Grocy.Api.Get('objects/product_barcodes?query[]=barcode=' + document.getElementById("product_id").getAttribute("barcode"),
function(barcodeResult)
{
if (barcodeResult != null)
{
function(barcodeResult) {
if (barcodeResult != null) {
var barcode = barcodeResult[0];
if (barcode != null)
{
if (barcode.amount != null && !barcode.amount.isEmpty())
{
if (barcode != null) {
if (barcode.amount != null && !barcode.amount.isEmpty()) {
$("#display_amount").val(barcode.amount);
$("#display_amount").select();
$("#display_amount").trigger('select');
}
if (barcode.qu_id != null && !barcode.qu_id.isEmpty())
{
if (barcode.qu_id != null && !barcode.qu_id.isEmpty()) {
Grocy.Components.ProductAmountPicker.SetQuantityUnit(barcode.qu_id);
}
@ -234,20 +196,16 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
}
}
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
}
if (productDetails.product.enable_tare_weight_handling == 1)
{
if (productDetails.product.enable_tare_weight_handling == 1) {
$("#display_amount").attr("min", productDetails.product.tare_weight);
$("#tare-weight-handling-info").removeClass("d-none");
}
else
{
} else {
$("#display_amount").attr("min", Grocy.DefaultMinAmount);
$("#tare-weight-handling-info").addClass("d-none");
}
@ -256,11 +214,11 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
Grocy.Components.ProductPicker.HideCustomError();
Grocy.FrontendHelpers.ValidateForm('transfer-form');
$('#display_amount').focus();
$('#display_amount').trigger('focus');
},
function(xhr)
{
function(xhr) {
console.error(xhr);
Grocy.Components.ProductPicker.HideCustomError();
}
);
}
@ -271,45 +229,35 @@ $(".input-group-productamountpicker").trigger("change");
Grocy.FrontendHelpers.ValidateForm('transfer-form');
RefreshLocaleNumberInput();
$("#location_id_from").on('change', function(e)
{
$("#location_id_from").on('change', function(e) {
var locationId = $(e.target).val();
var sumValue = 0;
var stockId = null;
if (locationId == $("#location_id_to").val())
{
if (locationId == $("#location_id_to").val()) {
$("#location_id_to").val("");
}
if (GetUriParam("embedded") !== undefined)
{
if (GetUriParam("embedded") !== undefined) {
stockId = GetUriParam('stockId');
}
$("#specific_stock_entry").find("option").remove().end().append("<option></option>");
if ($("#use_specific_stock_entry").is(":checked") && GetUriParam("stockId") == null)
{
if ($("#use_specific_stock_entry").is(":checked") && GetUriParam("stockId") == null) {
$("#use_specific_stock_entry").click();
}
if (locationId)
{
if (locationId) {
Grocy.Api.Get("stock/products/" + Grocy.Components.ProductPicker.GetValue() + '/entries',
function(stockEntries)
{
stockEntries.forEach(stockEntry =>
{
function(stockEntries) {
stockEntries.forEach(stockEntry => {
var openTxt = __t("Not opened");
if (stockEntry.open == 1)
{
if (stockEntry.open == 1) {
openTxt = __t("Opened");
}
if (stockEntry.location_id == locationId)
{
if ($("#specific_stock_entry option[value='" + stockEntry.stock_id + "']").length == 0)
{
if (stockEntry.location_id == locationId) {
if ($("#specific_stock_entry option[value='" + stockEntry.stock_id + "']").length == 0) {
$("#specific_stock_entry").append($("<option>", {
value: stockEntry.stock_id,
amount: stockEntry.amount,
@ -317,8 +265,7 @@ $("#location_id_from").on('change', function(e)
}));
}
if (stockEntry.stock_id == stockId)
{
if (stockEntry.stock_id == stockId) {
$("#specific_stock_entry").val(stockId);
}
@ -326,52 +273,43 @@ $("#location_id_from").on('change', function(e)
}
});
$("#display_amount").attr("max", sumValue * $("#qu_id option:selected").attr("data-qu-factor"));
if (sumValue == 0)
{
if (sumValue == 0) {
$("#display_amount").parent().find(".invalid-feedback").text(__t('There are no units available at this location'));
}
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
}
});
$("#location_id_to").on('change', function(e)
{
$("#location_id_to").on('change', function(e) {
var locationId = $(e.target).val();
if (locationId == $("#location_id_from").val())
{
if (locationId == $("#location_id_from").val()) {
$("#location_id_to").parent().find(".invalid-feedback").text(__t('This cannot be the same as the "From" location'));
$("#location_id_to").val("");
}
});
$("#qu_id").on('change', function(e)
{
$("#qu_id").on('change', function(e) {
$("#display_amount").attr("max", parseFloat($('#display_amount').attr("data-stock-amount")) * $("#qu_id option:selected").attr("data-qu-factor"));
});
$('#display_amount').on('focus', function(e)
{
$('#display_amount').on('focus', function(e) {
$(this).select();
});
$('#transfer-form input').keyup(function(event)
{
$('#transfer-form input').keyup(function(event) {
Grocy.FrontendHelpers.ValidateForm('transfer-form');
});
$('#transfer-form select').change(function(event)
{
$('#transfer-form select').change(function(event) {
Grocy.FrontendHelpers.ValidateForm('transfer-form');
});
$('#transfer-form input').keydown(function(event)
{
$('#transfer-form input').keydown(function(event) {
if (event.keyCode === 13) //Enter
{
event.preventDefault();
@ -379,58 +317,43 @@ $('#transfer-form input').keydown(function(event)
if (document.getElementById('transfer-form').checkValidity() === false) //There is at least one validation error
{
return false;
}
else
{
} else {
$('#save-transfer-button').click();
}
}
});
$("#specific_stock_entry").on("change", function(e)
{
if ($(e.target).val() == "")
{
$("#specific_stock_entry").on("change", function(e) {
if ($(e.target).val() == "") {
var sumValue = 0;
Grocy.Api.Get("stock/products/" + Grocy.Components.ProductPicker.GetValue() + '/entries',
function(stockEntries)
{
stockEntries.forEach(stockEntry =>
{
if (stockEntry.location_id == $("#location_id_from").val() || stockEntry.location_id == "")
{
function(stockEntries) {
stockEntries.forEach(stockEntry => {
if (stockEntry.location_id == $("#location_id_from").val() || stockEntry.location_id == "") {
sumValue = sumValue + parseFloat(stockEntry.amount);
}
});
$("#display_amount").attr("max", sumValue * $("#qu_id option:selected").attr("data-qu-factor"));
if (sumValue == 0)
{
if (sumValue == 0) {
$("#display_amount").parent().find(".invalid-feedback").text(__t('There are no units available at this location'));
}
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
}
else
{
} else {
$("#display_amount").attr("max", $('option:selected', this).attr('amount'));
}
});
$("#use_specific_stock_entry").on("change", function()
{
$("#use_specific_stock_entry").on("change", function() {
var value = $(this).is(":checked");
if (value)
{
if (value) {
$("#specific_stock_entry").removeAttr("disabled");
$("#specific_stock_entry").attr("required", "");
}
else
{
} else {
$("#specific_stock_entry").attr("disabled", "");
$("#specific_stock_entry").removeAttr("required");
$("#specific_stock_entry").val("");
@ -440,53 +363,43 @@ $("#use_specific_stock_entry").on("change", function()
Grocy.FrontendHelpers.ValidateForm("transfer-form");
});
function UndoStockBooking(bookingId)
{
function UndoStockBooking(bookingId) {
Grocy.Api.Post('stock/bookings/' + bookingId.toString() + '/undo', {},
function(result)
{
function(result) {
toastr.success(__t("Booking successfully undone"));
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
};
function UndoStockTransaction(transactionId)
{
function UndoStockTransaction(transactionId) {
Grocy.Api.Post('stock/transactions/' + transactionId.toString() + '/undo', {},
function(result)
{
function(result) {
toastr.success(__t("Transaction successfully undone"));
},
function(xhr)
{
function(xhr) {
console.error(xhr);
}
);
};
if (GetUriParam("embedded") !== undefined)
{
if (GetUriParam("embedded") !== undefined) {
var locationId = GetUriParam('locationId');
if (typeof locationId === 'undefined')
{
Grocy.Components.ProductPicker.GetPicker().trigger('change');
Grocy.Components.ProductPicker.GetInputElement().focus();
}
else
{
if (typeof locationId === 'undefined') {
Grocy.Components.ProductPicker.Validate();
Grocy.Components.ProductPicker.Focus();
} else {
$("#location_id_from").val(locationId);
$("#location_id_from").trigger('change');
$("#use_specific_stock_entry").click();
$("#use_specific_stock_entry").trigger('click');
$("#use_specific_stock_entry").trigger('change');
Grocy.Components.ProductPicker.GetPicker().trigger('change');
Grocy.Components.ProductPicker.Validate();
}
}
// Default input field
Grocy.Components.ProductPicker.GetInputElement().focus();
Grocy.Components.ProductPicker.Focus();

View File

@ -5,140 +5,124 @@
@section('viewJsName', 'batteries')
@section('content')
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
id="related-links">
<a class="btn btn-primary responsive-button permission-MASTER_DATA_EDIT m-1 mt-md-0 mb-md-0 float-right show-as-dialog-link"
href="{{ $U('/battery/new?embedded') }}">
{{ $__t('Add') }}
</a>
<a class="btn btn-outline-secondary m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/userfields?entity=batteries') }}">
{{ $__t('Configure userfields') }}
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100" id="related-links">
<a class="btn btn-primary responsive-button permission-MASTER_DATA_EDIT m-1 mt-md-0 mb-md-0 float-right show-as-dialog-link"
href="{{ $U('/battery/new?embedded') }}">
{{ $__t('Add') }}
</a>
<a class="btn btn-outline-secondary m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/userfields?entity=batteries') }}">
{{ $__t('Configure userfields') }}
</a>
</div>
</div>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row collapse d-md-flex"
id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text"
id="search"
class="form-control"
placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="form-check custom-control custom-checkbox">
<input class="form-check-input custom-control-input"
type="checkbox"
id="show-disabled">
<label class="form-check-label custom-control-label"
for="show-disabled">
{{ $__t('Show disabled') }}
</label>
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row collapse d-md-flex" id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text" id="search" class="form-control" placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="form-check custom-control custom-checkbox">
<input class="form-check-input custom-control-input" type="checkbox" id="show-disabled">
<label class="form-check-label custom-control-label" for="show-disabled">
{{ $__t('Show disabled') }}
</label>
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button" class="btn btn-sm btn-outline-info" href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row">
<div class="col">
<table id="batteries-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#batteries-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Name') }}</th>
<th>{{ $__t('Description') }}</th>
<th class="allow-grouping">{{ $__t('Used in') }}</th>
<th class="allow-grouping">{{ $__t('Charge cycle interval (days)') }}</th>
<div class="row">
<div class="col">
{{-- TODO: DataTables: dynamic data: batteries --}}
<table id="batteries-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#batteries-table" href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Name') }}</th>
<th>{{ $__t('Description') }}</th>
<th class="allow-grouping">{{ $__t('Used in') }}</th>
<th class="allow-grouping">{{ $__t('Charge cycle interval (days)') }}</th>
@include('components.userfields_thead', array(
'userfields' => $userfields
))
@include('components.userfields_thead', [
'userfields' => $userfields,
])
</tr>
</thead>
<tbody class="d-none">
@foreach($batteries as $battery)
<tr class="@if($battery->active == 0) text-muted @endif">
<td class="fit-content border-right">
<a class="btn btn-info btn-sm permission-MASTER_DATA_EDIT show-as-dialog-link"
href="{{ $U('/battery/') }}{{ $battery->id }}?embedded"
data-toggle="tooltip"
title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger btn-sm battery-delete-button permission-MASTER_DATA_EDIT"
href="#"
data-battery-id="{{ $battery->id }}"
data-battery-name="{{ $battery->name }}"
data-toggle="tooltip"
title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
{{ $battery->name }}
</td>
<td>
{{ $battery->description }}
</td>
<td>
{{ $battery->used_in }}
</td>
<td>
{{ $battery->charge_interval_days }}
</td>
</tr>
</thead>
<tbody class="d-none">
@foreach ($batteries as $battery)
<tr class="@if ($battery->active == 0) text-muted @endif">
<td class="fit-content border-right">
<a class="btn btn-info btn-sm permission-MASTER_DATA_EDIT show-as-dialog-link"
href="{{ $U('/battery/') }}{{ $battery->id }}?embedded" data-toggle="tooltip"
title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger btn-sm battery-delete-button permission-MASTER_DATA_EDIT" href="#"
data-battery-id="{{ $battery->id }}" data-battery-name="{{ $battery->name }}"
data-toggle="tooltip" title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
{{ $battery->name }}
</td>
<td>
{{ $battery->description }}
</td>
<td>
{{ $battery->used_in }}
</td>
<td>
{{ $battery->charge_interval_days }}
</td>
@include('components.userfields_tbody', array(
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $battery->id)
))
@include('components.userfields_tbody', [
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue(
$userfieldValues,
'object_id',
$battery->id
),
])
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@stop

View File

@ -5,125 +5,110 @@
@section('viewJsName', 'batteriesjournal')
@section('content')
<div class="row">
<div class="col">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
</div>
</div>
</div>
<div class="row">
<div class="col">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button" data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
</div>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row collapse d-md-flex"
id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text"
id="search"
class="form-control"
placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Battery') }}</span>
</div>
<select class="custom-control custom-select"
id="battery-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach($batteries as $battery)
<option value="{{ $battery->id }}">{{ $battery->name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-clock"></i>&nbsp;{{ $__t('Date range') }}</span>
</div>
<select class="custom-control custom-select"
id="daterange-filter">
<option value="1">{{ $__n(1, '%s month', '%s months') }}</option>
<option value="6">{{ $__n(6, '%s month', '%s months') }}</option>
<option value="12">{{ $__n(1, '%s year', '%s years') }}</option>
<option value="24"
selected>{{ $__n(2, '%s month', '%s years') }}</option>
<option value="9999">{{ $__t('All') }}</option>
</select>
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row collapse d-md-flex" id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text" id="search" class="form-control" placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Battery') }}</span>
</div>
{{-- TODO: Select2: dynamic data: batteries --}}
<select class="custom-control custom-select" id="battery-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach ($batteries as $battery)
<option value="{{ $battery->id }}">{{ $battery->name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-clock"></i>&nbsp;{{ $__t('Date range') }}</span>
</div>
<select class="custom-control custom-select" id="daterange-filter">
<option value="1">{{ $__n(1, '%s month', '%s months') }}</option>
<option value="6">{{ $__n(6, '%s month', '%s months') }}</option>
<option value="12">{{ $__n(1, '%s year', '%s years') }}</option>
<option value="24" selected>{{ $__n(2, '%s month', '%s years') }}</option>
<option value="9999">{{ $__t('All') }}</option>
</select>
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button" class="btn btn-sm btn-outline-info" href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col">
<table id="batteries-journal-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#batteries-journal-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th class="allow-grouping">{{ $__t('Battery') }}</th>
<th>{{ $__t('Tracked time') }}</th>
</tr>
</thead>
<tbody class="d-none">
@foreach($chargeCycles as $chargeCycleEntry)
<tr id="charge-cycle-{{ $chargeCycleEntry->id }}-row"
class="@if($chargeCycleEntry->undone == 1) text-muted @endif">
<td class="fit-content border-right">
<a class="btn btn-secondary btn-xs undo-battery-execution-button @if($chargeCycleEntry->undone == 1) disabled @endif permission-BATTERIES_UNDO_CHARGE_CYCLE"
href="#"
data-charge-cycle-id="{{ $chargeCycleEntry->id }}"
data-toggle="tooltip"
data-placement="left"
title="{{ $__t('Undo charge cycle') }}">
<i class="fas fa-undo"></i>
</a>
</td>
<td>
<span class="name-anchor @if($chargeCycleEntry->undone == 1) text-strike-through @endif">{{ FindObjectInArrayByPropertyValue($batteries, 'id', $chargeCycleEntry->battery_id)->name }}</span>
@if($chargeCycleEntry->undone == 1)
<br>
{{ $__t('Undone on') . ' ' . $chargeCycleEntry->undone_timestamp }}
<time class="timeago timeago-contextual"
datetime="{{ $chargeCycleEntry->undone_timestamp }}"></time>
@endif
</td>
<td>
{{ $chargeCycleEntry->tracked_time }}
<time class="timeago timeago-contextual"
datetime="{{ $chargeCycleEntry->tracked_time }}"></time>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<div class="row mt-2">
<div class="col">
{{-- TODO: DataTables: dynamic data: battery_charge_cycles --}}
<table id="batteries-journal-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#batteries-journal-table" href="#"><i class="fas fa-eye"></i></a>
</th>
<th class="allow-grouping">{{ $__t('Battery') }}</th>
<th>{{ $__t('Tracked time') }}</th>
</tr>
</thead>
<tbody class="d-none">
@foreach ($chargeCycles as $chargeCycleEntry)
<tr id="charge-cycle-{{ $chargeCycleEntry->id }}-row"
class="@if ($chargeCycleEntry->undone == 1) text-muted @endif">
<td class="fit-content border-right">
<a class="btn btn-secondary btn-xs undo-battery-execution-button @if ($chargeCycleEntry->undone == 1) disabled @endif permission-BATTERIES_UNDO_CHARGE_CYCLE"
href="#" data-charge-cycle-id="{{ $chargeCycleEntry->id }}" data-toggle="tooltip"
data-placement="left" title="{{ $__t('Undo charge cycle') }}">
<i class="fas fa-undo"></i>
</a>
</td>
<td>
<span
class="name-anchor @if ($chargeCycleEntry->undone == 1) text-strike-through @endif">{{ FindObjectInArrayByPropertyValue($batteries, 'id', $chargeCycleEntry->battery_id)->name }}</span>
@if ($chargeCycleEntry->undone == 1)
<br>
{{ $__t('Undone on') . ' ' . $chargeCycleEntry->undone_timestamp }}
<time class="timeago timeago-contextual"
datetime="{{ $chargeCycleEntry->undone_timestamp }}"></time>
@endif
</td>
<td>
{{ $chargeCycleEntry->tracked_time }}
<time class="timeago timeago-contextual"
datetime="{{ $chargeCycleEntry->tracked_time }}"></time>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@stop

View File

@ -5,223 +5,199 @@
@section('viewJsName', 'batteriesoverview')
@push('pageStyles')
<link href="{{ $U('/node_modules/animate.css/animate.min.css?v=', true) }}{{ $version }}"
rel="stylesheet">
<link href="{{ $U('/node_modules/animate.css/animate.min.css?v=', true) }}{{ $version }}" rel="stylesheet">
@endpush
@section('content')
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<button class="btn btn-outline-dark d-md-none mt-2 float-right order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
id="related-links">
<a class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/batteriesjournal') }}">
{{ $__t('Journal') }}
</a>
</div>
</div>
<div class="border-top border-bottom my-2 py-1">
<div id="info-overdue-batteries"
data-status-filter="overdue"
class="error-message status-filter-message responsive-button mr-2"></div>
<div id="info-due-today-batteries"
data-status-filter="duetoday"
class="normal-message status-filter-message responsive-button mr-2"></div>
<div id="info-due-soon-batteries"
data-status-filter="duesoon"
data-next-x-days="{{ $nextXDays }}"
class="warning-message status-filter-message responsive-button @if($nextXDays == 0) d-none @endif"></div>
<div class="float-right">
<a class="btn btn-sm btn-outline-info d-md-none mt-1"
data-toggle="collapse"
href="#table-filter-row"
role="button">
<i class="fas fa-filter"></i>
</a>
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info mt-1"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<button class="btn btn-outline-dark d-md-none mt-2 float-right order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100" id="related-links">
<a class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/batteriesjournal') }}">
{{ $__t('Journal') }}
</a>
</div>
</div>
<div class="border-top border-bottom my-2 py-1">
<div id="info-overdue-batteries" data-status-filter="overdue"
class="error-message status-filter-message responsive-button mr-2"></div>
<div id="info-due-today-batteries" data-status-filter="duetoday"
class="normal-message status-filter-message responsive-button mr-2"></div>
<div id="info-due-soon-batteries" data-status-filter="duesoon" data-next-x-days="{{ $nextXDays }}"
class="warning-message status-filter-message responsive-button @if ($nextXDays == 0) d-none @endif">
</div>
<div class="float-right">
<a class="btn btn-sm btn-outline-info d-md-none mt-1" data-toggle="collapse" href="#table-filter-row"
role="button">
<i class="fas fa-filter"></i>
</a>
<a id="clear-filter-button" class="btn btn-sm btn-outline-info mt-1" href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
</div>
<div class="row collapse d-md-flex"
id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text"
id="search"
class="form-control"
placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Status') }}</span>
</div>
<select class="custom-control custom-select"
id="status-filter">
<option value="all">{{ $__t('All') }}</option>
<option value="overdue">{{ $__t('Overdue') }}</option>
<option value="duetoday">{{ $__t('Due today') }}</option>
@if($nextXDays > 0)
<option value="duesoon">{{ $__t('Due soon') }}</option>
@endif
</select>
</div>
</div>
</div>
<div class="row collapse d-md-flex" id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text" id="search" class="form-control" placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Status') }}</span>
</div>
<select class="custom-control custom-select" id="status-filter">
<option value="all">{{ $__t('All') }}</option>
<option value="overdue">{{ $__t('Overdue') }}</option>
<option value="duetoday">{{ $__t('Due today') }}</option>
@if ($nextXDays > 0)
<option value="duesoon">{{ $__t('Due soon') }}</option>
@endif
</select>
</div>
</div>
</div>
<div class="row">
<div class="col">
<table id="batteries-overview-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#batteries-overview-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Battery') }}</th>
<th class="allow-grouping">{{ $__t('Used in') }}</th>
<th>{{ $__t('Last charged') }}</th>
<th>{{ $__t('Next planned charge cycle') }}</th>
<th class="d-none">Hidden status</th>
<div class="row">
<div class="col">
{{-- TODO: DataTables: dynamic data: batteries --}}
<table id="batteries-overview-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#batteries-overview-table" href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Battery') }}</th>
<th class="allow-grouping">{{ $__t('Used in') }}</th>
<th>{{ $__t('Last charged') }}</th>
<th>{{ $__t('Next planned charge cycle') }}</th>
<th class="d-none">Hidden status</th>
@include('components.userfields_thead', array(
'userfields' => $userfields
))
@include('components.userfields_thead', [
'userfields' => $userfields,
])
</tr>
</thead>
<tbody class="d-none">
@foreach($current as $currentBatteryEntry)
<tr id="battery-{{ $currentBatteryEntry->battery_id }}-row"
class="@if($currentBatteryEntry->due_type == 'overdue') table-danger @elseif($currentBatteryEntry->due_type == 'duetoday') table-info @elseif($currentBatteryEntry->due_type == 'duesoon') table-warning @endif">
<td class="fit-content border-right">
<a class="btn btn-success btn-sm track-charge-cycle-button permission-BATTERIES_TRACK_CHARGE_CYCLE"
href="#"
data-toggle="tooltip"
data-placement="left"
title="{{ $__t('Track charge cycle') }}"
data-battery-id="{{ $currentBatteryEntry->battery_id }}"
data-battery-name="{{ FindObjectInArrayByPropertyValue($batteries, 'id', $currentBatteryEntry->battery_id)->name }}">
<i class="fas fa-car-battery"></i>
</a>
<div class="dropdown d-inline-block">
<button class="btn btn-sm btn-light text-secondary"
type="button"
data-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="table-inline-menu dropdown-menu dropdown-menu-right">
<a class="dropdown-item battery-name-cell"
data-battery-id="{{ $currentBatteryEntry->battery_id }}"
type="button"
href="#">
<span class="dropdown-item-text">{{ $__t('Battery overview') }}</span>
</a>
<a class="dropdown-item show-as-dialog-link"
type="button"
href="{{ $U('/batteriesjournal?embedded&battery=') }}{{ $currentBatteryEntry->battery_id }}">
<span class="dropdown-item-text">{{ $__t('Battery journal') }}</span>
</a>
<a class="dropdown-item permission-MASTER_DATA_EDIT show-as-dialog-link"
type="button"
href="{{ $U('/battery/') }}{{ $currentBatteryEntry->battery_id }}?embedded">
<span class="dropdown-item-text">{{ $__t('Edit battery') }}</span>
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item"
type="button"
href="{{ $U('/battery/' . $currentBatteryEntry->battery_id . '/grocycode?download=true') }}">
{!! str_replace('grocycode', '<span class="ls-n1">grocycode</span>', $__t('Download %s grocycode', $__t('Battery'))) !!}
</a>
@if(GROCY_FEATURE_FLAG_LABEL_PRINTER)
<a class="dropdown-item battery-grocycode-label-print"
data-battery-id="{{ $currentBatteryEntry->battery_id }}"
type="button"
href="#">
{!! str_replace('grocycode', '<span class="ls-n1">grocycode</span>', $__t('Print %s grocycode on label printer', $__t('Battery'))) !!}
</a>
@endif
</div>
</div>
</td>
<td class="battery-name-cell cursor-link"
data-battery-id="{{ $currentBatteryEntry->battery_id }}">
{{ FindObjectInArrayByPropertyValue($batteries, 'id', $currentBatteryEntry->battery_id)->name }}
</td>
<td class="fit-content">
{{ FindObjectInArrayByPropertyValue($batteries, 'id', $currentBatteryEntry->battery_id)->used_in }}
</td>
<td>
<span id="battery-{{ $currentBatteryEntry->battery_id }}-last-tracked-time">{{ $currentBatteryEntry->last_tracked_time }}</span>
<time id="battery-{{ $currentBatteryEntry->battery_id }}-last-tracked-time-timeago"
class="timeago timeago-contextual"
datetime="{{ $currentBatteryEntry->last_tracked_time }}"></time>
</td>
<td>
@if(FindObjectInArrayByPropertyValue($batteries, 'id', $currentBatteryEntry->battery_id)->charge_interval_days > 0)
<span id="battery-{{ $currentBatteryEntry->battery_id }}-next-charge-time">{{ $currentBatteryEntry->next_estimated_charge_time }}</span>
<time id="battery-{{ $currentBatteryEntry->battery_id }}-next-charge-time-timeago"
class="timeago timeago-contextual"
datetime="{{ $currentBatteryEntry->next_estimated_charge_time }}"></time>
@else
...
@endif
</td>
<td class="d-none">
{{ $currentBatteryEntry->due_type }}
@if($currentBatteryEntry->due_type == 'duetoday')
duesoon
@endif
</td>
</tr>
</thead>
<tbody class="d-none">
@foreach ($current as $currentBatteryEntry)
<tr id="battery-{{ $currentBatteryEntry->battery_id }}-row"
class="@if ($currentBatteryEntry->due_type == 'overdue') table-danger @elseif($currentBatteryEntry->due_type == 'duetoday') table-info @elseif($currentBatteryEntry->due_type == 'duesoon') table-warning @endif">
<td class="fit-content border-right">
<a class="btn btn-success btn-sm track-charge-cycle-button permission-BATTERIES_TRACK_CHARGE_CYCLE"
href="#" data-toggle="tooltip" data-placement="left"
title="{{ $__t('Track charge cycle') }}"
data-battery-id="{{ $currentBatteryEntry->battery_id }}"
data-battery-name="{{ FindObjectInArrayByPropertyValue($batteries, 'id', $currentBatteryEntry->battery_id)->name }}">
<i class="fas fa-car-battery"></i>
</a>
<div class="dropdown d-inline-block">
<button class="btn btn-sm btn-light text-secondary" type="button"
data-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="table-inline-menu dropdown-menu dropdown-menu-right">
<a class="dropdown-item battery-name-cell"
data-battery-id="{{ $currentBatteryEntry->battery_id }}" type="button"
href="#">
<span class="dropdown-item-text">{{ $__t('Battery overview') }}</span>
</a>
<a class="dropdown-item show-as-dialog-link" type="button"
href="{{ $U('/batteriesjournal?embedded&battery=') }}{{ $currentBatteryEntry->battery_id }}">
<span class="dropdown-item-text">{{ $__t('Battery journal') }}</span>
</a>
<a class="dropdown-item permission-MASTER_DATA_EDIT show-as-dialog-link"
type="button"
href="{{ $U('/battery/') }}{{ $currentBatteryEntry->battery_id }}?embedded">
<span class="dropdown-item-text">{{ $__t('Edit battery') }}</span>
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" type="button"
href="{{ $U('/battery/' . $currentBatteryEntry->battery_id . '/grocycode?download=true') }}">
{!! str_replace('grocycode', '<span class="ls-n1">grocycode</span>', $__t('Download %s grocycode', $__t('Battery'))) !!}
</a>
@if (GROCY_FEATURE_FLAG_LABEL_PRINTER)
<a class="dropdown-item battery-grocycode-label-print"
data-battery-id="{{ $currentBatteryEntry->battery_id }}" type="button"
href="#">
{!! str_replace('grocycode', '<span class="ls-n1">grocycode</span>', $__t('Print %s grocycode on label printer', $__t('Battery'))) !!}
</a>
@endif
</div>
</div>
</td>
<td class="battery-name-cell cursor-link"
data-battery-id="{{ $currentBatteryEntry->battery_id }}">
{{ FindObjectInArrayByPropertyValue($batteries, 'id', $currentBatteryEntry->battery_id)->name }}
</td>
<td class="fit-content">
{{ FindObjectInArrayByPropertyValue($batteries, 'id', $currentBatteryEntry->battery_id)->used_in }}
</td>
<td>
<span
id="battery-{{ $currentBatteryEntry->battery_id }}-last-tracked-time">{{ $currentBatteryEntry->last_tracked_time }}</span>
<time id="battery-{{ $currentBatteryEntry->battery_id }}-last-tracked-time-timeago"
class="timeago timeago-contextual"
datetime="{{ $currentBatteryEntry->last_tracked_time }}"></time>
</td>
<td>
@if (FindObjectInArrayByPropertyValue($batteries, 'id', $currentBatteryEntry->battery_id)->charge_interval_days > 0)
<span
id="battery-{{ $currentBatteryEntry->battery_id }}-next-charge-time">{{ $currentBatteryEntry->next_estimated_charge_time }}</span>
<time id="battery-{{ $currentBatteryEntry->battery_id }}-next-charge-time-timeago"
class="timeago timeago-contextual"
datetime="{{ $currentBatteryEntry->next_estimated_charge_time }}"></time>
@else
...
@endif
</td>
<td class="d-none">
{{ $currentBatteryEntry->due_type }}
@if ($currentBatteryEntry->due_type == 'duetoday')
duesoon
@endif
</td>
@include('components.userfields_tbody',
array( 'userfields'=> $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $currentBatteryEntry->battery_id)
))
@include('components.userfields_tbody', [
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue(
$userfieldValues,
'object_id',
$currentBatteryEntry->battery_id
),
])
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<div class="modal fade"
id="batteriesoverview-batterycard-modal"
tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-center">
<div class="modal-body">
@include('components.batterycard')
</div>
<div class="modal-footer">
<button type="button"
class="btn btn-secondary"
data-dismiss="modal">{{ $__t('Close') }}</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="batteriesoverview-batterycard-modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-center">
<div class="modal-body">
@include('components.batterycard')
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ $__t('Close') }}</button>
</div>
</div>
</div>
</div>
@stop

View File

@ -11,21 +11,15 @@
<hr class="my-2">
<form id="batterytracking-form"
novalidate>
<form id="batterytracking-form" novalidate>
<div class="form-group">
<label class="w-100"
for="battery_id">
<label class="w-100" for="battery_id">
{{ $__t('Battery') }}
<i id="barcode-lookup-hint"
class="fas fa-barcode float-right mt-1"></i>
<i id="barcode-lookup-hint" class="fas fa-barcode float-right mt-1"></i>
</label>
<select class="form-control combobox barcodescanner-input"
id="battery_id"
name="battery_id"
required
data-target="@batterypicker">
{{-- TODO: Select2: dynamic data: batteries --}}
<select class="form-control combobox barcodescanner-input" id="battery_id" name="battery_id" required data-target="@batterypicker">
<option value=""></option>
@foreach($batteries as $battery)
<option value="{{ $battery->id }}">{{ $battery->name }}</option>
@ -44,8 +38,7 @@
'invalidFeedback' => $__t('This can only be before now')
))
<button id="save-batterytracking-button"
class="btn btn-success">{{ $__t('OK') }}</button>
<button id="save-batterytracking-button" class="btn btn-success">{{ $__t('OK') }}</button>
</form>
</div>
@ -56,4 +49,4 @@
</div>
@include('components.barcodescanner')
@stop
@stop

View File

@ -82,12 +82,12 @@
@php if($mode == 'edit') { $value = $chore->period_days; } else { $value = 0; } @endphp
@include('components.numberpicker', array(
'id' => 'period_days',
'label' => 'Period days',
'value' => $value,
'min' => '0',
'additionalCssClasses' => 'input-group-chore-period-type',
'additionalGroupCssClasses' => 'period-type-input period-type-monthly'
'id' => 'period_days',
'label' => 'Period days',
'value' => $value,
'min' => '0',
'additionalCssClasses' => 'input-group-chore-period-type',
'additionalGroupCssClasses' => 'period-type-input period-type-monthly'
))
<div class="form-group period-type-input period-type-weekly">
@ -156,12 +156,12 @@
@php if($mode == 'edit') { $value = $chore->period_interval; } else { $value = 1; } @endphp
@include('components.numberpicker', array(
'id' => 'period_interval',
'label' => 'Period interval',
'value' => $value,
'min' => '1',
'additionalCssClasses' => 'input-group-chore-period-type',
'additionalGroupCssClasses' => 'period-type-input period-type-hourly period-type-daily period-type-weekly period-type-monthly period-type-yearly'
'id' => 'period_interval',
'label' => 'Period interval',
'value' => $value,
'min' => '1',
'additionalCssClasses' => 'input-group-chore-period-type',
'additionalGroupCssClasses' => 'period-type-input period-type-hourly period-type-daily period-type-weekly period-type-monthly period-type-yearly'
))
<p id="chore-schedule-info"
@ -175,15 +175,15 @@
}
@endphp
@include('components.datetimepicker', array(
'id' => 'start',
'label' => 'Start date',
'initialValue' => $value,
'format' => 'YYYY-MM-DD HH:mm:ss',
'initWithNow' => true,
'limitEndToNow' => false,
'limitStartToNow' => false,
'invalidFeedback' => $__t('A start date is required'),
'hint' => $__t('The start date cannot be changed when the chore was once tracked')
'id' => 'start',
'label' => 'Start date',
'initialValue' => $value,
'format' => 'YYYY-MM-DD HH:mm:ss',
'initWithNow' => true,
'limitEndToNow' => false,
'limitStartToNow' => false,
'invalidFeedback' => $__t('A start date is required'),
'hint' => $__t('The start date cannot be changed when the chore was once tracked')
))
@if(GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
@ -276,29 +276,29 @@
@php $prefillById = ''; if($mode=='edit' && !empty($chore->product_id)) { $prefillById = $chore->product_id; } @endphp
@include('components.productpicker', array(
'products' => $products,
'nextInputSelector' => '#product_amount',
'isRequired' => false,
'disallowAllProductWorkflows' => true,
'prefillById' => $prefillById
'productsQuery' => 'order=name%3Acollate%20nocase',
'nextInputSelector' => '#product_amount',
'isRequired' => false,
'disallowAllProductWorkflows' => true,
'prefillById' => $prefillById
))
@php if($mode == 'edit') { $value = $chore->product_amount; } else { $value = ''; } @endphp
@include('components.numberpicker', array(
'id' => 'product_amount',
'label' => 'Amount',
'contextInfoId' => 'amount_qu_unit',
'min' => $DEFAULT_MIN_AMOUNT,
'decimals' => $userSettings['stock_decimal_places_amounts'],
'isRequired' => false,
'value' => $value,
'additionalCssClasses' => 'locale-number-input locale-number-quantity-amount'
'id' => 'product_amount',
'label' => 'Amount',
'contextInfoId' => 'amount_qu_unit',
'min' => $DEFAULT_MIN_AMOUNT,
'decimals' => $userSettings['stock_decimal_places_amounts'],
'isRequired' => false,
'value' => $value,
'additionalCssClasses' => 'locale-number-input locale-number-quantity-amount'
))
@endif
@include('components.userfieldsform', array(
'userfields' => $userfields,
'entity' => 'chores'
'userfields' => $userfields,
'entity' => 'chores'
))
<div class="sticky-form-footer pt-1">

View File

@ -5,203 +5,174 @@
@section('viewJsName', 'chores')
@section('content')
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
id="related-links">
<a class="btn btn-primary responsive-button m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/chore/new') }}">
{{ $__t('Add') }}
</a>
<a class="btn btn-outline-secondary m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/userfields?entity=chores') }}">
{{ $__t('Configure userfields') }}
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100" id="related-links">
<a class="btn btn-primary responsive-button m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/chore/new') }}">
{{ $__t('Add') }}
</a>
<a class="btn btn-outline-secondary m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/userfields?entity=chores') }}">
{{ $__t('Configure userfields') }}
</a>
</div>
</div>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row collapse d-md-flex"
id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text"
id="search"
class="form-control"
placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="form-check custom-control custom-checkbox">
<input class="form-check-input custom-control-input"
type="checkbox"
id="show-disabled">
<label class="form-check-label custom-control-label"
for="show-disabled">
{{ $__t('Show disabled') }}
</label>
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row collapse d-md-flex" id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text" id="search" class="form-control" placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="form-check custom-control custom-checkbox">
<input class="form-check-input custom-control-input" type="checkbox" id="show-disabled">
<label class="form-check-label custom-control-label" for="show-disabled">
{{ $__t('Show disabled') }}
</label>
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button" class="btn btn-sm btn-outline-info" href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row">
<div class="col">
<table id="chores-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#chores-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Name') }}</th>
<th class="allow-grouping">{{ $__t('Period type') }}</th>
<th>{{ $__t('Description') }}</th>
<div class="row">
<div class="col">
{{-- TODO: DataTables: dynamic data: chores --}}
<table id="chores-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#chores-table" href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Name') }}</th>
<th class="allow-grouping">{{ $__t('Period type') }}</th>
<th>{{ $__t('Description') }}</th>
@include('components.userfields_thead', array(
'userfields' => $userfields
))
@include('components.userfields_thead', [
'userfields' => $userfields,
])
</tr>
</thead>
<tbody class="d-none">
@foreach($chores as $chore)
<tr class="@if($chore->active == 0) text-muted @endif">
<td class="fit-content border-right">
<a class="btn btn-info btn-sm"
href="{{ $U('/chore/') }}{{ $chore->id }}"
data-toggle="tooltip"
title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger btn-sm chore-delete-button"
href="#"
data-chore-id="{{ $chore->id }}"
data-chore-name="{{ $chore->name }}"
data-toggle="tooltip"
title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
<div class="dropdown d-inline-block">
<button class="btn btn-sm btn-light text-secondary"
type="button"
data-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="table-inline-menu dropdown-menu dropdown-menu-right">
<a class="dropdown-item merge-chores-button"
data-chore-id="{{ $chore->id }}"
type="button"
href="#">
<span class="dropdown-item-text">{{ $__t('Merge') }}</span>
</a>
</div>
</div>
</td>
<td>
{{ $chore->name }}
</td>
<td>
{{ $__t($chore->period_type) }}
</td>
<td>
{{ $chore->description }}
</td>
</tr>
</thead>
<tbody class="d-none">
@foreach ($chores as $chore)
<tr class="@if ($chore->active == 0) text-muted @endif">
<td class="fit-content border-right">
<a class="btn btn-info btn-sm" href="{{ $U('/chore/') }}{{ $chore->id }}"
data-toggle="tooltip" title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger btn-sm chore-delete-button" href="#"
data-chore-id="{{ $chore->id }}" data-chore-name="{{ $chore->name }}"
data-toggle="tooltip" title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
<div class="dropdown d-inline-block">
<button class="btn btn-sm btn-light text-secondary" type="button"
data-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="table-inline-menu dropdown-menu dropdown-menu-right">
<a class="dropdown-item merge-chores-button" data-chore-id="{{ $chore->id }}"
type="button" href="#">
<span class="dropdown-item-text">{{ $__t('Merge') }}</span>
</a>
</div>
</div>
</td>
<td>
{{ $chore->name }}
</td>
<td>
{{ $__t($chore->period_type) }}
</td>
<td>
{{ $chore->description }}
</td>
@include('components.userfields_tbody', array(
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $chore->id)
))
@include('components.userfields_tbody', [
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue(
$userfieldValues,
'object_id',
$chore->id
),
])
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<div class="modal fade"
id="merge-chores-modal"
tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-center">
<div class="modal-header">
<h4 class="modal-title w-100">{{ $__t('Merge chores') }}</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label for="merge-chores-keep">{{ $__t('Chore to keep') }}&nbsp;<i class="fas fa-question-circle text-muted"
data-toggle="tooltip"
data-trigger="hover click"
title="{{ $__t('After merging, this chore will be kept') }}"></i>
</label>
<select class="custom-control custom-select"
id="merge-chores-keep">
<option></option>
@foreach($chores as $chore)
<option value="{{ $chore->id }}">{{ $chore->name }}</option>
@endforeach
</select>
</div>
<div class="form-group">
<label for="merge-chores-remove">{{ $__t('Chore to remove') }}&nbsp;<i class="fas fa-question-circle text-muted"
data-toggle="tooltip"
data-trigger="hover click"
title="{{ $__t('After merging, all occurences of this chore will be replaced by the kept chore (means this chore will not exist anymore)') }}"></i>
</label>
<select class="custom-control custom-select"
id="merge-chores-remove">
<option></option>
@foreach($chores as $chore)
<option value="{{ $chore->id }}">{{ $chore->name }}</option>
@endforeach
</select>
</div>
</div>
<div class="modal-footer">
<button type="button"
class="btn btn-secondary"
data-dismiss="modal">{{ $__t('Cancel') }}</button>
<button id="merge-chores-save-button"
type="button"
class="btn btn-primary"
data-dismiss="modal">{{ $__t('OK') }}</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="merge-chores-modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-center">
<div class="modal-header">
<h4 class="modal-title w-100">{{ $__t('Merge chores') }}</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label for="merge-chores-keep">{{ $__t('Chore to keep') }}&nbsp;<i
class="fas fa-question-circle text-muted" data-toggle="tooltip" data-trigger="hover click"
title="{{ $__t('After merging, this chore will be kept') }}"></i>
</label>
{{-- TODO: Select2: dynamic data: chores --}}
<select class="custom-control custom-select" id="merge-chores-keep">
<option></option>
@foreach ($chores as $chore)
<option value="{{ $chore->id }}">{{ $chore->name }}</option>
@endforeach
</select>
</div>
<div class="form-group">
<label for="merge-chores-remove">{{ $__t('Chore to remove') }}&nbsp;<i
class="fas fa-question-circle text-muted" data-toggle="tooltip" data-trigger="hover click"
title="{{ $__t('After merging, all occurences of this chore will be replaced by the kept chore (means this chore will not exist anymore)') }}"></i>
</label>
{{-- TODO: Select2: dynamic data: chores --}}
<select class="custom-control custom-select" id="merge-chores-remove">
<option></option>
@foreach ($chores as $chore)
<option value="{{ $chore->id }}">{{ $chore->name }}</option>
@endforeach
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ $__t('Cancel') }}</button>
<button id="merge-chores-save-button" type="button" class="btn btn-primary"
data-dismiss="modal">{{ $__t('OK') }}</button>
</div>
</div>
</div>
</div>
@stop

View File

@ -5,149 +5,139 @@
@section('viewJsName', 'choresjournal')
@section('content')
<div class="row">
<div class="col">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
</div>
</div>
</div>
<div class="row">
<div class="col">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button" data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
</div>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row collapse d-md-flex"
id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text"
id="search"
class="form-control"
placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Chore') }}</span>
</div>
<select class="custom-control custom-select"
id="chore-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach($chores as $chore)
<option value="{{ $chore->id }}">{{ $chore->name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-clock"></i>&nbsp;{{ $__t('Date range') }}</span>
</div>
<select class="custom-control custom-select"
id="daterange-filter">
<option value="1">{{ $__n(1, '%s month', '%s months') }}</option>
<option value="6">{{ $__n(6, '%s month', '%s months') }}</option>
<option value="12"
selected>{{ $__n(1, '%s year', '%s years') }}</option>
<option value="24">{{ $__n(2, '%s month', '%s years') }}</option>
<option value="9999">{{ $__t('All') }}</option>
</select>
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row collapse d-md-flex" id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text" id="search" class="form-control" placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Chore') }}</span>
</div>
{{-- TODO: Select2: dynamic data: chores --}}
<select class="custom-control custom-select" id="chore-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach ($chores as $chore)
<option value="{{ $chore->id }}">{{ $chore->name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-clock"></i>&nbsp;{{ $__t('Date range') }}</span>
</div>
<select class="custom-control custom-select" id="daterange-filter">
<option value="1">{{ $__n(1, '%s month', '%s months') }}</option>
<option value="6">{{ $__n(6, '%s month', '%s months') }}</option>
<option value="12" selected>{{ $__n(1, '%s year', '%s years') }}</option>
<option value="24">{{ $__n(2, '%s month', '%s years') }}</option>
<option value="9999">{{ $__t('All') }}</option>
</select>
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button" class="btn btn-sm btn-outline-info" href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col">
<table id="chores-journal-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#chores-journal-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th class="allow-grouping">{{ $__t('Chore') }}</th>
<th>{{ $__t('Tracked time') }}</th>
@if(GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
<th class="allow-grouping">{{ $__t('Done by') }}</th>
@endif
<div class="row mt-2">
<div class="col">
{{-- TODO: DataTables: dynamic data: chores_log --}}
<table id="chores-journal-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#chores-journal-table" href="#"><i class="fas fa-eye"></i></a>
</th>
<th class="allow-grouping">{{ $__t('Chore') }}</th>
<th>{{ $__t('Tracked time') }}</th>
@if (GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
<th class="allow-grouping">{{ $__t('Done by') }}</th>
@endif
@include('components.userfields_thead', array(
'userfields' => $userfields
))
</tr>
</thead>
<tbody class="d-none">
@foreach($choresLog as $choreLogEntry)
<tr id="chore-execution-{{ $choreLogEntry->id }}-row"
class="@if($choreLogEntry->undone == 1) text-muted @endif @if($choreLogEntry->skipped == 1) font-italic @endif">
<td class="fit-content border-right">
<a class="btn btn-secondary btn-xs undo-chore-execution-button permission-CHORE_UNDO_EXECUTION @if($choreLogEntry->undone == 1) disabled @endif"
href="#"
data-execution-id="{{ $choreLogEntry->id }}"
data-toggle="tooltip"
data-placement="left"
title="{{ $__t('Undo chore execution') }}">
<i class="fas fa-undo"></i>
</a>
</td>
<td>
<span class="name-anchor @if($choreLogEntry->undone == 1) text-strike-through @endif">{{ FindObjectInArrayByPropertyValue($chores, 'id', $choreLogEntry->chore_id)->name }}</span>
@if($choreLogEntry->undone == 1)
<br>
{{ $__t('Undone on') . ' ' . $choreLogEntry->undone_timestamp }}
<time class="timeago timeago-contextual"
datetime="{{ $choreLogEntry->undone_timestamp }}"></time>
@endif
</td>
<td>
<span>{{ $choreLogEntry->tracked_time }}</span>
<time class="timeago timeago-contextual @if(FindObjectInArrayByPropertyValue($chores, 'id', $choreLogEntry->chore_id)->track_date_only == 1) timeago-date-only @endif"
datetime="{{ $choreLogEntry->tracked_time }}"></time>
@if($choreLogEntry->skipped == 1)
<span class="text-muted">{{ $__t('Skipped') }}</span>
@endif
</td>
@if(GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
<td>
@if ($choreLogEntry->done_by_user_id !== null && !empty($choreLogEntry->done_by_user_id))
{{ GetUserDisplayName(FindObjectInArrayByPropertyValue($users, 'id', $choreLogEntry->done_by_user_id)) }}
@else
{{ $__t('Unknown') }}
@endif
</td>
@endif
@include('components.userfields_thead', [
'userfields' => $userfields,
])
</tr>
</thead>
<tbody class="d-none">
@foreach ($choresLog as $choreLogEntry)
<tr id="chore-execution-{{ $choreLogEntry->id }}-row"
class="@if ($choreLogEntry->undone == 1) text-muted @endif @if ($choreLogEntry->skipped == 1) font-italic @endif">
<td class="fit-content border-right">
<a class="btn btn-secondary btn-xs undo-chore-execution-button permission-CHORE_UNDO_EXECUTION @if ($choreLogEntry->undone == 1) disabled @endif"
href="#" data-execution-id="{{ $choreLogEntry->id }}" data-toggle="tooltip"
data-placement="left" title="{{ $__t('Undo chore execution') }}">
<i class="fas fa-undo"></i>
</a>
</td>
<td>
<span
class="name-anchor @if ($choreLogEntry->undone == 1) text-strike-through @endif">{{ FindObjectInArrayByPropertyValue($chores, 'id', $choreLogEntry->chore_id)->name }}</span>
@if ($choreLogEntry->undone == 1)
<br>
{{ $__t('Undone on') . ' ' . $choreLogEntry->undone_timestamp }}
<time class="timeago timeago-contextual"
datetime="{{ $choreLogEntry->undone_timestamp }}"></time>
@endif
</td>
<td>
<span>{{ $choreLogEntry->tracked_time }}</span>
<time
class="timeago timeago-contextual @if (FindObjectInArrayByPropertyValue($chores, 'id', $choreLogEntry->chore_id)->track_date_only == 1) timeago-date-only @endif"
datetime="{{ $choreLogEntry->tracked_time }}"></time>
@if ($choreLogEntry->skipped == 1)
<span class="text-muted">{{ $__t('Skipped') }}</span>
@endif
</td>
@if (GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
<td>
@if ($choreLogEntry->done_by_user_id !== null && !empty($choreLogEntry->done_by_user_id))
{{ GetUserDisplayName(FindObjectInArrayByPropertyValue($users, 'id', $choreLogEntry->done_by_user_id)) }}
@else
{{ $__t('Unknown') }}
@endif
</td>
@endif
@include('components.userfields_tbody', array(
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $choreLogEntry->id)
))
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@include('components.userfields_tbody', [
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue(
$userfieldValues,
'object_id',
$choreLogEntry->id
),
])
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@stop

View File

@ -5,268 +5,238 @@
@section('viewJsName', 'choresoverview')
@push('pageStyles')
<link href="{{ $U('/node_modules/animate.css/animate.min.css?v=', true) }}{{ $version }}"
rel="stylesheet">
<link href="{{ $U('/node_modules/animate.css/animate.min.css?v=', true) }}{{ $version }}" rel="stylesheet">
@endpush
@section('content')
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<button class="btn btn-outline-dark d-md-none mt-2 float-right order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
id="related-links">
<a class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/choresjournal') }}">
{{ $__t('Journal') }}
</a>
</div>
</div>
<div class="border-top border-bottom my-2 py-1">
<div id="info-overdue-chores"
data-status-filter="overdue"
class="error-message status-filter-message responsive-button mr-2"></div>
<div id="info-due-today-chores"
data-status-filter="duetoday"
class="normal-message status-filter-message responsive-button mr-2"></div>
<div id="info-due-soon-chores"
data-status-filter="duesoon"
data-next-x-days="{{ $nextXDays }}"
class="warning-message status-filter-message responsive-message mr-2 @if($nextXDays == 0) d-none @endif"></div>
@if(GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
<div id="info-assigned-to-me-chores"
data-user-filter="xx{{ GROCY_USER_ID }}xx"
class="secondary-message user-filter-message responsive-button"></div>
@endif
<div class="float-right">
<a class="btn btn-sm btn-outline-info d-md-none mt-1"
data-toggle="collapse"
href="#table-filter-row"
role="button">
<i class="fas fa-filter"></i>
</a>
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info mt-1"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<button class="btn btn-outline-dark d-md-none mt-2 float-right order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100" id="related-links">
<a class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/choresjournal') }}">
{{ $__t('Journal') }}
</a>
</div>
</div>
<div class="border-top border-bottom my-2 py-1">
<div id="info-overdue-chores" data-status-filter="overdue"
class="error-message status-filter-message responsive-button mr-2"></div>
<div id="info-due-today-chores" data-status-filter="duetoday"
class="normal-message status-filter-message responsive-button mr-2"></div>
<div id="info-due-soon-chores" data-status-filter="duesoon" data-next-x-days="{{ $nextXDays }}"
class="warning-message status-filter-message responsive-message mr-2 @if ($nextXDays == 0) d-none @endif">
</div>
@if (GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
<div id="info-assigned-to-me-chores" data-user-filter="xx{{ GROCY_USER_ID }}xx"
class="secondary-message user-filter-message responsive-button"></div>
@endif
<div class="float-right">
<a class="btn btn-sm btn-outline-info d-md-none mt-1" data-toggle="collapse" href="#table-filter-row"
role="button">
<i class="fas fa-filter"></i>
</a>
<a id="clear-filter-button" class="btn btn-sm btn-outline-info mt-1" href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
</div>
<div class="row collapse d-md-flex"
id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text"
id="search"
class="form-control"
placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Status') }}</span>
</div>
<select class="custom-control custom-select"
id="status-filter">
<option value="all">{{ $__t('All') }}</option>
<option value="overdue">{{ $__t('Overdue') }}</option>
<option value="duetoday">{{ $__t('Due today') }}</option>
@if($nextXDays > 0)
<option value="duesoon">{{ $__t('Due soon') }}</option>
@endif
</select>
</div>
</div>
@if(GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Assignment') }}</span>
</div>
<select class="custom-control custom-select"
id="user-filter">
<option></option>
@foreach($users as $user)
<option data-user-id="{{ $user->id }}"
value="xx{{ $user->id }}xx">{{ $user->display_name }}</option>
@endforeach
</select>
</div>
</div>
@endif
</div>
<div class="row collapse d-md-flex" id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text" id="search" class="form-control" placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Status') }}</span>
</div>
<select class="custom-control custom-select" id="status-filter">
<option value="all">{{ $__t('All') }}</option>
<option value="overdue">{{ $__t('Overdue') }}</option>
<option value="duetoday">{{ $__t('Due today') }}</option>
@if ($nextXDays > 0)
<option value="duesoon">{{ $__t('Due soon') }}</option>
@endif
</select>
</div>
</div>
@if (GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS)
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i
class="fas fa-filter"></i>&nbsp;{{ $__t('Assignment') }}</span>
</div>
{{-- TODO: Select2: dynamic data: users --}}
<select class="custom-control custom-select" id="user-filter">
<option></option>
@foreach ($users as $user)
<option data-user-id="{{ $user->id }}" value="xx{{ $user->id }}xx">
{{ $user->display_name }}</option>
@endforeach
</select>
</div>
</div>
@endif
</div>
<div class="row">
<div class="col">
<table id="chores-overview-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#chores-overview-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Chore') }}</th>
<th>{{ $__t('Next estimated tracking') }}</th>
<th>{{ $__t('Last tracked') }}</th>
<th class="@if(!GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS) d-none @endif allow-grouping">{{ $__t('Assigned to') }}</th>
<th class="d-none">Hidden status</th>
<th class="d-none">Hidden assigned to user id</th>
<div class="row">
<div class="col">
{{-- TODO: DataTables: dynamic data: chores --}}
<table id="chores-overview-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#chores-overview-table" href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Chore') }}</th>
<th>{{ $__t('Next estimated tracking') }}</th>
<th>{{ $__t('Last tracked') }}</th>
<th class="@if (!GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS) d-none @endif allow-grouping">
{{ $__t('Assigned to') }}</th>
<th class="d-none">Hidden status</th>
<th class="d-none">Hidden assigned to user id</th>
@include('components.userfields_thead', array(
'userfields' => $userfields
))
@include('components.userfields_thead', [
'userfields' => $userfields,
])
</tr>
</thead>
<tbody class="d-none">
@foreach($currentChores as $curentChoreEntry)
<tr id="chore-{{ $curentChoreEntry->chore_id }}-row"
class="@if($curentChoreEntry->due_type == 'overdue') table-danger @elseif($curentChoreEntry->due_type == 'duetoday') table-info @elseif($curentChoreEntry->due_type == 'duesoon') table-warning @endif">
<td class="fit-content border-right">
<a class="btn btn-success btn-sm track-chore-button permission-CHORE_TRACK_EXECUTION"
href="#"
data-toggle="tooltip"
data-placement="left"
title="{{ $__t('Track chore execution') }}"
data-chore-id="{{ $curentChoreEntry->chore_id }}"
data-chore-name="{{ FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->name }}">
<i class="fas fa-play"></i>
</a>
<a class="btn btn-secondary btn-sm track-chore-button skip permission-CHORE_TRACK_EXECUTION @if(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type == \Grocy\Services\ChoresService::CHORE_PERIOD_TYPE_MANUALLY) disabled @endif"
href="#"
data-toggle="tooltip"
data-placement="left"
title="{{ $__t('Skip next chore schedule') }}"
data-chore-id="{{ $curentChoreEntry->chore_id }}"
data-chore-name="{{ FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->name }}">
<i class="fas fa-forward"></i>
</a>
<div class="dropdown d-inline-block">
<button class="btn btn-sm btn-light text-secondary"
type="button"
data-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="table-inline-menu dropdown-menu dropdown-menu-right">
<a class="dropdown-item chore-name-cell"
data-chore-id="{{ $curentChoreEntry->chore_id }}"
type="button"
href="#">
<span class="dropdown-item-text">{{ $__t('Chore overview') }}</span>
</a>
<a class="dropdown-item show-as-dialog-link"
type="button"
href="{{ $U('/choresjournal?embedded&chore=') }}{{ $curentChoreEntry->chore_id }}">
<span class="dropdown-item-text">{{ $__t('Chore journal') }}</span>
</a>
<a class="dropdown-item permission-MASTER_DATA_EDIT"
type="button"
href="{{ $U('/chore/') }}{{ $curentChoreEntry->chore_id }}">
<span class="dropdown-item-text">{{ $__t('Edit chore') }}</span>
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item"
type="button"
href="{{ $U('/chore/' . $curentChoreEntry->chore_id . '/grocycode?download=true') }}">
{!! str_replace('grocycode', '<span class="ls-n1">grocycode</span>', $__t('Download %s grocycode', $__t('Chore'))) !!}
</a>
@if(GROCY_FEATURE_FLAG_LABEL_PRINTER)
<a class="dropdown-item chore-grocycode-label-print"
data-chore-id="{{ $curentChoreEntry->chore_id }}"
type="button"
href="#">
{!! str_replace('grocycode', '<span class="ls-n1">grocycode</span>', $__t('Print %s grocycode on label printer', $__t('Chore'))) !!}
</a>
@endif
</div>
</div>
</td>
<td class="chore-name-cell cursor-link"
data-chore-id="{{ $curentChoreEntry->chore_id }}">
{{ FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->name }}
</td>
<td>
@if(FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type !== \Grocy\Services\ChoresService::CHORE_PERIOD_TYPE_MANUALLY)
<span id="chore-{{ $curentChoreEntry->chore_id }}-next-execution-time">{{ $curentChoreEntry->next_estimated_execution_time }}</span>
<time id="chore-{{ $curentChoreEntry->chore_id }}-next-execution-time-timeago"
class="timeago timeago-contextual @if($curentChoreEntry->track_date_only == 1) timeago-date-only @endif"
datetime="{{ $curentChoreEntry->next_estimated_execution_time }}"></time>
@else
<span>-</span>
@endif
</td>
<td>
<span id="chore-{{ $curentChoreEntry->chore_id }}-last-tracked-time">{{ $curentChoreEntry->last_tracked_time }}</span>
<time id="chore-{{ $curentChoreEntry->chore_id }}-last-tracked-time-timeago"
class="timeago timeago-contextual @if($curentChoreEntry->track_date_only == 1) timeago-date-only @endif"
datetime="{{ $curentChoreEntry->last_tracked_time }}"></time>
</td>
</tr>
</thead>
<tbody class="d-none">
@foreach ($currentChores as $curentChoreEntry)
<tr id="chore-{{ $curentChoreEntry->chore_id }}-row"
class="@if ($curentChoreEntry->due_type == 'overdue') table-danger @elseif($curentChoreEntry->due_type == 'duetoday') table-info @elseif($curentChoreEntry->due_type == 'duesoon') table-warning @endif">
<td class="fit-content border-right">
<a class="btn btn-success btn-sm track-chore-button permission-CHORE_TRACK_EXECUTION"
href="#" data-toggle="tooltip" data-placement="left"
title="{{ $__t('Track chore execution') }}"
data-chore-id="{{ $curentChoreEntry->chore_id }}"
data-chore-name="{{ FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->name }}">
<i class="fas fa-play"></i>
</a>
<a class="btn btn-secondary btn-sm track-chore-button skip permission-CHORE_TRACK_EXECUTION @if (FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type == \Grocy\Services\ChoresService::CHORE_PERIOD_TYPE_MANUALLY) disabled @endif"
href="#" data-toggle="tooltip" data-placement="left"
title="{{ $__t('Skip next chore schedule') }}"
data-chore-id="{{ $curentChoreEntry->chore_id }}"
data-chore-name="{{ FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->name }}">
<i class="fas fa-forward"></i>
</a>
<div class="dropdown d-inline-block">
<button class="btn btn-sm btn-light text-secondary" type="button"
data-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="table-inline-menu dropdown-menu dropdown-menu-right">
<a class="dropdown-item chore-name-cell"
data-chore-id="{{ $curentChoreEntry->chore_id }}" type="button" href="#">
<span class="dropdown-item-text">{{ $__t('Chore overview') }}</span>
</a>
<a class="dropdown-item show-as-dialog-link" type="button"
href="{{ $U('/choresjournal?embedded&chore=') }}{{ $curentChoreEntry->chore_id }}">
<span class="dropdown-item-text">{{ $__t('Chore journal') }}</span>
</a>
<a class="dropdown-item permission-MASTER_DATA_EDIT" type="button"
href="{{ $U('/chore/') }}{{ $curentChoreEntry->chore_id }}">
<span class="dropdown-item-text">{{ $__t('Edit chore') }}</span>
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" type="button"
href="{{ $U('/chore/' . $curentChoreEntry->chore_id . '/grocycode?download=true') }}">
{!! str_replace('grocycode', '<span class="ls-n1">grocycode</span>', $__t('Download %s grocycode', $__t('Chore'))) !!}
</a>
@if (GROCY_FEATURE_FLAG_LABEL_PRINTER)
<a class="dropdown-item chore-grocycode-label-print"
data-chore-id="{{ $curentChoreEntry->chore_id }}" type="button" href="#">
{!! str_replace('grocycode', '<span class="ls-n1">grocycode</span>', $__t('Print %s grocycode on label printer', $__t('Chore'))) !!}
</a>
@endif
</div>
</div>
</td>
<td class="chore-name-cell cursor-link" data-chore-id="{{ $curentChoreEntry->chore_id }}">
{{ FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->name }}
</td>
<td>
@if (FindObjectInArrayByPropertyValue($chores, 'id', $curentChoreEntry->chore_id)->period_type !== \Grocy\Services\ChoresService::CHORE_PERIOD_TYPE_MANUALLY)
<span
id="chore-{{ $curentChoreEntry->chore_id }}-next-execution-time">{{ $curentChoreEntry->next_estimated_execution_time }}</span>
<time id="chore-{{ $curentChoreEntry->chore_id }}-next-execution-time-timeago"
class="timeago timeago-contextual @if ($curentChoreEntry->track_date_only == 1) timeago-date-only @endif"
datetime="{{ $curentChoreEntry->next_estimated_execution_time }}"></time>
@else
<span>-</span>
@endif
</td>
<td>
<span
id="chore-{{ $curentChoreEntry->chore_id }}-last-tracked-time">{{ $curentChoreEntry->last_tracked_time }}</span>
<time id="chore-{{ $curentChoreEntry->chore_id }}-last-tracked-time-timeago"
class="timeago timeago-contextual @if ($curentChoreEntry->track_date_only == 1) timeago-date-only @endif"
datetime="{{ $curentChoreEntry->last_tracked_time }}"></time>
</td>
<td class="@if(!GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS) d-none @endif">
<span id="chore-{{ $curentChoreEntry->chore_id }}-next-execution-assigned-user">
@if(!empty($curentChoreEntry->next_execution_assigned_to_user_id))
{{ FindObjectInArrayByPropertyValue($users, 'id', $curentChoreEntry->next_execution_assigned_to_user_id)->display_name }}
@else
<span>-</span>
@endif
</span>
</td>
<td id="chore-{{ $curentChoreEntry->chore_id }}-due-filter-column"
class="d-none">
{{ $curentChoreEntry->due_type }}
@if($curentChoreEntry->due_type == 'duetoday')
duesoon
@endif
</td>
<td class="d-none">
@if(!empty($curentChoreEntry->next_execution_assigned_to_user_id))
xx{{ $curentChoreEntry->next_execution_assigned_to_user_id }}xx
</td>
@endif
<td class="@if (!GROCY_FEATURE_FLAG_CHORES_ASSIGNMENTS) d-none @endif">
<span id="chore-{{ $curentChoreEntry->chore_id }}-next-execution-assigned-user">
@if (!empty($curentChoreEntry->next_execution_assigned_to_user_id))
{{ FindObjectInArrayByPropertyValue($users, 'id', $curentChoreEntry->next_execution_assigned_to_user_id)->display_name }}
@else
<span>-</span>
@endif
</span>
</td>
<td id="chore-{{ $curentChoreEntry->chore_id }}-due-filter-column" class="d-none">
{{ $curentChoreEntry->due_type }}
@if ($curentChoreEntry->due_type == 'duetoday')
duesoon
@endif
</td>
<td class="d-none">
@if (!empty($curentChoreEntry->next_execution_assigned_to_user_id))
xx{{ $curentChoreEntry->next_execution_assigned_to_user_id }}xx
</td>
@endif
@include('components.userfields_tbody', array(
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $curentChoreEntry->chore_id)
))
@include('components.userfields_tbody', [
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue(
$userfieldValues,
'object_id',
$curentChoreEntry->chore_id
),
])
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<div class="modal fade"
id="choresoverview-chorecard-modal"
tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-center">
<div class="modal-body">
@include('components.chorecard')
</div>
<div class="modal-footer">
<button type="button"
class="btn btn-secondary"
data-dismiss="modal">{{ $__t('Close') }}</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="choresoverview-chorecard-modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-center">
<div class="modal-body">
@include('components.chorecard')
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ $__t('Close') }}</button>
</div>
</div>
</div>
</div>
@stop

View File

@ -11,21 +11,15 @@
<hr class="my-2">
<form id="choretracking-form"
novalidate>
<form id="choretracking-form" novalidate>
<div class="form-group">
<label class="w-100"
for="chore_id">
<label class="w-100" for="chore_id">
{{ $__t('Chore') }}
<i id="barcode-lookup-hint"
class="fas fa-barcode float-right mt-1"></i>
<i id="barcode-lookup-hint" class="fas fa-barcode float-right mt-1"></i>
</label>
<select class="form-control combobox barcodescanner-input"
id="chore_id"
name="chore_id"
required
data-target="@chorepicker">
{{-- TODO: Select2: dynamic data: chores --}}
<select class="form-control combobox barcodescanner-input" id="chore_id" name="chore_id" required data-target="@chorepicker">
<option value=""></option>
@foreach($chores as $chore)
<option value="{{ $chore->id }}">{{ $chore->name }}</option>
@ -52,10 +46,7 @@
'prefillByUserId' => GROCY_USER_ID
))
@else
<input type="hidden"
id="user_id"
name="user_id"
value="{{ GROCY_USER_ID }}">
<input type="hidden" id="user_id" name="user_id" value="{{ GROCY_USER_ID }}">
@endif
@include('components.userfieldsform', array(
@ -76,4 +67,4 @@
</div>
@include('components.barcodescanner')
@stop
@stop

View File

@ -26,6 +26,17 @@
margin-right: 36px !important;
}
.input-group-prepend #barcodescanner-start-button {
position: static;
right: unset;
margin: unset;
}
.input-group>#barcodescanner-start-button-container+.select2-hidden-accessible+.select2-container--bootstrap>.selection>.select2-selection, .input-group>#barcodescanner-start-button-container+.select2-hidden-accessible+.select2-container--bootstrap>.selection>.select2-selection.form-control {
border-top-right-radius: .25rem;
border-bottom-right-radius: .25rem;
}
</style>
@endpush

View File

@ -1,37 +1,50 @@
@once
@push('componentScripts')
<script src="{{ $U('/viewjs/components/locationpicker.js', true) }}?v={{ $version }}"></script>
@endpush
@push('componentScripts')
<script src="{{ $U('/viewjs/components/locationpicker.js', true) }}?v={{ $version }}"></script>
@endpush
@endonce
@php if(empty($prefillByName)) { $prefillByName = ''; } @endphp
@php if(empty($prefillById)) { $prefillById = ''; } @endphp
@php if(!isset($isRequired)) { $isRequired = true; } @endphp
@php if(empty($hint)) { $hint = ''; } @endphp
@php if(empty($nextInputSelector)) { $nextInputSelector = ''; } @endphp
@php
if (empty($prefillByName)) {
$prefillByName = '';
}
@endphp
@php
if (empty($prefillById)) {
$prefillById = '';
}
@endphp
@php
if (!isset($isRequired)) {
$isRequired = true;
}
@endphp
@php
if (empty($hint)) {
$hint = '';
}
@endphp
@php
if (empty($nextInputSelector)) {
$nextInputSelector = '';
}
@endphp
<div class="form-group"
data-next-input-selector="{{ $nextInputSelector }}"
data-prefill-by-name="{{ $prefillByName }}"
data-prefill-by-id="{{ $prefillById }}">
<label for="location_id">{{ $__t('Location') }}
@if(!empty($hint))
<i class="fas fa-question-circle text-muted"
data-toggle="tooltip"
data-trigger="hover click"
title="{{ $hint }}"></i>
@endif
</label>
<select class="form-control location-combobox"
id="location_id"
name="location_id"
@if($isRequired)
required
@endif>
<option value=""></option>
@foreach($locations as $location)
<option value="{{ $location->id }}">{{ $location->name }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('You have to select a location') }}</div>
<div class="form-group" data-next-input-selector="{{ $nextInputSelector }}"
data-prefill-by-name="{{ $prefillByName }}" data-prefill-by-id="{{ $prefillById }}">
<label for="location_id">{{ $__t('Location') }}
@if (!empty($hint))
<i class="fas fa-question-circle text-muted" data-toggle="tooltip" data-trigger="hover click"
title="{{ $hint }}"></i>
@endif
</label>
{{-- TODO: Select2: dynamic data: locations --}}
<select class="form-control location-combobox" id="location_id" name="location_id"
@if ($isRequired) required @endif>
<option value=""></option>
@foreach ($locations as $location)
<option value="{{ $location->id }}">{{ $location->name }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('You have to select a location') }}</div>
</div>

View File

@ -1,72 +1,74 @@
@once
@push('componentScripts')
<script src="{{ $U('/viewjs/components/productpicker.js', true) }}?v={{ $version }}"></script>
@endpush
@push('componentScripts')
<script src="{{ $U('/viewjs/components/productpicker.js', true) }}?v={{ $version }}"></script>
@endpush
@endonce
@php if(empty($disallowAddProductWorkflows)) { $disallowAddProductWorkflows = false; } @endphp
@php if(empty($disallowAllProductWorkflows)) { $disallowAllProductWorkflows = false; } @endphp
@php if(empty($prefillByName)) { $prefillByName = ''; } @endphp
@php if(empty($prefillById)) { $prefillById = ''; } @endphp
@php if(!isset($isRequired)) { $isRequired = true; } @endphp
@php if(!isset($label)) { $label = 'Product'; } @endphp
@php if(!isset($disabled)) { $disabled = false; } @endphp
@php if(empty($hint)) { $hint = ''; } @endphp
@php if(empty($nextInputSelector)) { $nextInputSelector = ''; } @endphp
@php if(empty($validationMessage)) { $validationMessage = 'You have to select a product'; } @endphp
@php
if (empty($disallowAddProductWorkflows)) {
$disallowAddProductWorkflows = false;
}
if (empty($disallowAllProductWorkflows)) {
$disallowAllProductWorkflows = false;
}
if (empty($prefillByName)) {
$prefillByName = '';
}
if (empty($prefillById)) {
$prefillById = '';
}
if (!isset($isRequired)) {
$isRequired = true;
}
if (!isset($label)) {
$label = 'Product';
}
if (!isset($disabled)) {
$disabled = false;
}
if (empty($hint)) {
$hint = '';
}
if (empty($nextInputSelector)) {
$nextInputSelector = '';
}
if (empty($validationMessage)) {
$validationMessage = 'You have to select a product';
}
if (empty($productsQuery)) {
$productsQuery = '';
}
@endphp
<div class="form-group"
data-next-input-selector="{{ $nextInputSelector }}"
data-disallow-add-product-workflows="{{ BoolToString($disallowAddProductWorkflows) }}"
data-disallow-all-product-workflows="{{ BoolToString($disallowAllProductWorkflows) }}"
data-prefill-by-name="{{ $prefillByName }}"
data-prefill-by-id="{{ $prefillById }}">
<label class="w-100"
for="product_id">
{{ $__t($label) }}
@if(!$disallowAllProductWorkflows)
<i class="fas fa-question-circle text-muted"
data-toggle="tooltip"
data-trigger="hover click"
title="{{ $__t('Type a new product name or barcode and hit TAB or ENTER to start a workflow') }}"></i>
@endif
@if(!empty($hint))
<i class="fas fa-question-circle text-muted"
data-toggle="tooltip"
data-trigger="hover click"
title="{{ $hint }}"></i>
@endif
<span id="barcode-lookup-disabled-hint"
class="small text-muted d-none float-right"> {{ $__t('Barcode lookup is disabled') }}</span>
<i id="barcode-lookup-hint"
class="fas fa-barcode float-right mt-1"></i>
</label>
<select class="form-control product-combobox barcodescanner-input"
id="product_id"
name="product_id"
@if($isRequired)
required
@endif
@if($disabled)
disabled
@endif
data-target="@productpicker">
<option value=""></option>
@foreach($products as $product)
@php $bc = null;
if(isset($barcodes)) {
$bc = FindObjectInArrayByPropertyValue($barcodes, 'product_id', $product->id);
}
@endphp
<option data-additional-searchdata="@if(isset($bc)){{ $bc->barcodes }}@endif,"
value="{{ $product->id }}">{{ $product->name }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t($validationMessage) }}</div>
<div id="custom-productpicker-error"
class="form-text text-danger d-none"></div>
<div id="flow-info-InplaceAddBarcodeToExistingProduct"
class="form-text text-info small d-none"><strong><span id="InplaceAddBarcodeToExistingProduct"></span></strong> {{ $__t('will be added to the list of barcodes for the selected product on submit') }}</div>
<div class="form-group" data-next-input-selector="{{ $nextInputSelector }}"
data-disallow-add-product-workflows="{{ BoolToString($disallowAddProductWorkflows) }}"
data-disallow-all-product-workflows="{{ BoolToString($disallowAllProductWorkflows) }}"
data-prefill-by-name="{{ $prefillByName }}" data-prefill-by-id="{{ $prefillById }}"
data-products-query="{{ $productsQuery }}">
<label class="w-100" for="product_id">
{{ $__t($label) }}
@if (!$disallowAllProductWorkflows)
<i class="fas fa-question-circle text-muted" data-toggle="tooltip" data-trigger="hover click"
title="{{ $__t('Type a new product name or barcode and hit TAB or ENTER to start a workflow') }}"></i>
@endif
@if (!empty($hint))
<i class="fas fa-question-circle text-muted" data-toggle="tooltip" data-trigger="hover click"
title="{{ $hint }}"></i>
@endif
<span id="barcode-lookup-disabled-hint" class="small text-muted d-none float-right">
{{ $__t('Barcode lookup is disabled') }}</span>
<i id="barcode-lookup-hint" class="fas fa-barcode float-right mt-1"></i>
</label>
<div class="input-group">
<select class="select2 custom-control custom-select barcodescanner-input" id="product_id" name="product_id"
@if ($isRequired) required @endif @if ($disabled) disabled @endif
data-target="@productpicker"></select>
<div class="invalid-feedback">{{ $__t($validationMessage) }}</div>
</div>
<div id="custom-productpicker-error" class="form-text text-danger d-none"></div>
<div id="flow-info-InplaceAddBarcodeToExistingProduct" class="form-text text-info small d-none"><strong><span
id="InplaceAddBarcodeToExistingProduct"></span></strong>
{{ $__t('will be added to the list of barcodes for the selected product on submit') }}</div>
</div>
@include('components.barcodescanner')

View File

@ -1,42 +1,53 @@
@once
@push('componentScripts')
<script src="{{ $U('/viewjs/components/recipepicker.js', true) }}?v={{ $version }}"></script>
@endpush
@push('componentScripts')
<script src="{{ $U('/viewjs/components/recipepicker.js', true) }}?v={{ $version }}"></script>
@endpush
@endonce
@php if(empty($prefillByName)) { $prefillByName = ''; } @endphp
@php if(empty($prefillById)) { $prefillById = ''; } @endphp
@php if(!isset($isRequired)) { $isRequired = true; } @endphp
@php if(empty($hint)) { $hint = ''; } @endphp
@php if(empty($nextInputSelector)) { $nextInputSelector = ''; } @endphp
@php
if (empty($prefillByName)) {
$prefillByName = '';
}
@endphp
@php
if (empty($prefillById)) {
$prefillById = '';
}
@endphp
@php
if (!isset($isRequired)) {
$isRequired = true;
}
@endphp
@php
if (empty($hint)) {
$hint = '';
}
@endphp
@php
if (empty($nextInputSelector)) {
$nextInputSelector = '';
}
@endphp
<div class="form-group"
data-next-input-selector="{{ $nextInputSelector }}"
data-prefill-by-name="{{ $prefillByName }}"
data-prefill-by-id="{{ $prefillById }}">
<label class="w-100"
for="recipe_id">{{ $__t('Recipe') }}
@if(!empty($hint))
<i class="fas fa-question-circle text-muted"
data-toggle="tooltip"
data-trigger="hover click"
title="{{ $hint }}"></i>
@endif
<i class="fas fa-barcode float-right mt-1"></i>
</label>
<select class="form-control recipe-combobox barcodescanner-input"
id="recipe_id"
name="recipe_id"
data-target="@recipepicker"
@if($isRequired)
required
@endif>
<option value=""></option>
@foreach($recipes as $recipe)
<option value="{{ $recipe->id }}">{{ $recipe->name }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('You have to select a recipe') }}</div>
<div class="form-group" data-next-input-selector="{{ $nextInputSelector }}"
data-prefill-by-name="{{ $prefillByName }}" data-prefill-by-id="{{ $prefillById }}">
<label class="w-100" for="recipe_id">{{ $__t('Recipe') }}
@if (!empty($hint))
<i class="fas fa-question-circle text-muted" data-toggle="tooltip" data-trigger="hover click"
title="{{ $hint }}"></i>
@endif
<i class="fas fa-barcode float-right mt-1"></i>
</label>
{{-- TODO: Select2: dynamic data: recipes --}}
<select class="form-control recipe-combobox barcodescanner-input" id="recipe_id" name="recipe_id"
data-target="@recipepicker" @if ($isRequired) required @endif>
<option value=""></option>
@foreach ($recipes as $recipe)
<option value="{{ $recipe->id }}">{{ $recipe->name }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('You have to select a recipe') }}</div>
</div>
@include('components.barcodescanner')

View File

@ -1,32 +1,47 @@
@once
@push('componentScripts')
<script src="{{ $U('/viewjs/components/shoppinglocationpicker.js', true) }}?v={{ $version }}"></script>
@endpush
@push('componentScripts')
<script src="{{ $U('/viewjs/components/shoppinglocationpicker.js', true) }}?v={{ $version }}"></script>
@endpush
@endonce
@php if(empty($prefillByName)) { $prefillByName = ''; } @endphp
@php if(empty($prefillById)) { $prefillById = ''; } @endphp
@php if(!isset($isRequired)) { $isRequired = false; } @endphp
@php if(empty($hint)) { $hint = ''; } @endphp
@php if(empty($nextInputSelector)) { $nextInputSelector = ''; } @endphp
@php
if (empty($prefillByName)) {
$prefillByName = '';
}
@endphp
@php
if (empty($prefillById)) {
$prefillById = '';
}
@endphp
@php
if (!isset($isRequired)) {
$isRequired = false;
}
@endphp
@php
if (empty($hint)) {
$hint = '';
}
@endphp
@php
if (empty($nextInputSelector)) {
$nextInputSelector = '';
}
@endphp
<div class="form-group"
data-next-input-selector="{{ $nextInputSelector }}"
data-prefill-by-name="{{ $prefillByName }}"
data-prefill-by-id="{{ $prefillById }}">
<label for="shopping_location_id">{{ $__t($label) }}&nbsp;&nbsp;<span @if(!empty($hintId))id="{{ $hintId }}"
@endif
class="small text-muted">{{ $hint }}</span></label>
<select class="form-control shopping-location-combobox"
id="shopping_location_id"
name="shopping_location_id"
@if($isRequired)
required
@endif>
<option value=""></option>
@foreach($shoppinglocations as $shoppinglocation)
<option value="{{ $shoppinglocation->id }}">{{ $shoppinglocation->name }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('You have to select a store') }}</div>
<div class="form-group" data-next-input-selector="{{ $nextInputSelector }}"
data-prefill-by-name="{{ $prefillByName }}" data-prefill-by-id="{{ $prefillById }}">
<label for="shopping_location_id">{{ $__t($label) }}&nbsp;&nbsp;<span
@if (!empty($hintId)) id="{{ $hintId }}" @endif
class="small text-muted">{{ $hint }}</span></label>
{{-- TODO: Select2: dynamic data: shopping_locations --}}
<select class="form-control shopping-location-combobox" id="shopping_location_id" name="shopping_location_id"
@if ($isRequired) required @endif>
<option value=""></option>
@foreach ($shoppinglocations as $shoppinglocation)
<option value="{{ $shoppinglocation->id }}">{{ $shoppinglocation->name }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('You have to select a store') }}</div>
</div>

View File

@ -1,25 +1,34 @@
@once
@push('componentScripts')
<script src="{{ $U('/viewjs/components/userpicker.js', true) }}?v={{ $version }}"></script>
@endpush
@push('componentScripts')
<script src="{{ $U('/viewjs/components/userpicker.js', true) }}?v={{ $version }}"></script>
@endpush
@endonce
@php if(empty($prefillByUsername)) { $prefillByUsername = ''; } @endphp
@php if(empty($prefillByUserId)) { $prefillByUserId = ''; } @endphp
@php if(!isset($nextInputSelector)) { $nextInputSelector = ''; } @endphp
@php
if (empty($prefillByUsername)) {
$prefillByUsername = '';
}
@endphp
@php
if (empty($prefillByUserId)) {
$prefillByUserId = '';
}
@endphp
@php
if (!isset($nextInputSelector)) {
$nextInputSelector = '';
}
@endphp
<div class="form-group"
data-next-input-selector="{{ $nextInputSelector }}"
data-prefill-by-username="{{ $prefillByUsername }}"
data-prefill-by-user-id="{{ $prefillByUserId }}">
<label for="user_id">{{ $__t($label) }}</label>
<select class="form-control user-combobox"
id="user_id"
name="user_id">
<option value=""></option>
@foreach($users as $user)
<option data-additional-searchdata="{{ $user->username }}"
value="{{ $user->id }}">{{ GetUserDisplayName($user) }}</option>
@endforeach
</select>
<div class="form-group" data-next-input-selector="{{ $nextInputSelector }}"
data-prefill-by-username="{{ $prefillByUsername }}" data-prefill-by-user-id="{{ $prefillByUserId }}">
<label for="user_id">{{ $__t($label) }}</label>
{{-- TODO: Select2: dynamic data: users --}}
<select class="form-control user-combobox" id="user_id" name="user_id">
<option value=""></option>
@foreach ($users as $user)
<option data-additional-searchdata="{{ $user->username }}" value="{{ $user->id }}">
{{ GetUserDisplayName($user) }}</option>
@endforeach
</select>
</div>

View File

@ -5,151 +5,136 @@
@section('viewJsName', 'consume')
@push('pageScripts')
<script src="{{ $U('/js/grocy_uisound.js?v=', true) }}{{ $version }}"></script>
<script src="{{ $U('/js/grocy_uisound.js?v=', true) }}{{ $version }}"></script>
@endpush
@section('content')
<script>
Grocy.QuantityUnits = {!! json_encode($quantityUnits) !!};
Grocy.QuantityUnitConversionsResolved = {!! json_encode($quantityUnitConversionsResolved) !!};
Grocy.DefaultMinAmount = '{{$DEFAULT_MIN_AMOUNT}}';
</script>
<script>
Grocy.QuantityUnits = {!! json_encode($quantityUnits) !!};
Grocy.QuantityUnitConversionsResolved = {!! json_encode($quantityUnitConversionsResolved) !!};
Grocy.DefaultMinAmount = '{{ $DEFAULT_MIN_AMOUNT }}';
</script>
<div class="row">
<div class="col-12 col-md-6 col-xl-4 pb-3">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<button class="btn btn-outline-dark d-md-none mt-2 float-right order-1 order-md-3 hide-when-embedded"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
id="related-links">
@if(!$embedded)
<button id="scan-mode-button"
class="btn @if(boolval($userSettings['scan_mode_consume_enabled'])) btn-success @else btn-danger @endif m-1 mt-md-0 mb-md-0 float-right"
data-toggle="tooltip"
title="{{ $__t('When enabled, after changing/scanning a product and if all fields could be automatically populated (by product and/or barcode defaults), the transaction is automatically submitted') }}">{{ $__t('Scan mode') }} <span id="scan-mode-status">@if(boolval($userSettings['scan_mode_consume_enabled'])) {{ $__t('on') }} @else {{ $__t('off') }} @endif</span></button>
<input id="scan-mode"
type="checkbox"
class="d-none user-setting-control"
data-setting-key="scan_mode_consume_enabled"
@if(boolval($userSettings['scan_mode_consume_enabled']))
checked
@endif>
@else
<script>
Grocy.UserSettings.scan_mode_consume_enabled = false;
</script>
@endif
</div>
</div>
<div class="row">
<div class="col-12 col-md-6 col-xl-4 pb-3">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<button class="btn btn-outline-dark d-md-none mt-2 float-right order-1 order-md-3 hide-when-embedded"
type="button" data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100" id="related-links">
@if (!$embedded)
<button id="scan-mode-button"
class="btn @if (boolval($userSettings['scan_mode_consume_enabled'])) btn-success @else btn-danger @endif m-1 mt-md-0 mb-md-0 float-right"
data-toggle="tooltip"
title="{{ $__t('When enabled, after changing/scanning a product and if all fields could be automatically populated (by product and/or barcode defaults), the transaction is automatically submitted') }}">{{ $__t('Scan mode') }}
<span id="scan-mode-status">
@if (boolval($userSettings['scan_mode_consume_enabled']))
{{ $__t('on') }}
@else
{{ $__t('off') }}
@endif
</span></button>
<input id="scan-mode" type="checkbox" class="d-none user-setting-control"
data-setting-key="scan_mode_consume_enabled" @if (boolval($userSettings['scan_mode_consume_enabled'])) checked @endif>
@else
<script>
Grocy.UserSettings.scan_mode_consume_enabled = false;
</script>
@endif
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<form id="consume-form"
novalidate>
<form id="consume-form" novalidate>
@include('components.productpicker', array(
'products' => $products,
'barcodes' => $barcodes,
'nextInputSelector' => '#amount',
'disallowAddProductWorkflows' => true
))
@include('components.productpicker', [
'productsQuery' => 'query%5B%5D=active%3D1&only_in_stock=1&order=name',
'nextInputSelector' => '#amount',
'disallowAddProductWorkflows' => true,
])
<div id="consume-exact-amount-group"
class="form-group d-none">
<div class="custom-control custom-checkbox">
<input class="form-check-input custom-control-input"
type="checkbox"
id="consume-exact-amount"
name="consume-exact-amount"
value="1">
<label class="form-check-label custom-control-label"
for="consume-exact-amount">{{ $__t('Consume exact amount') }}
</label>
</div>
</div>
<div id="consume-exact-amount-group" class="form-group d-none">
<div class="custom-control custom-checkbox">
<input class="form-check-input custom-control-input" type="checkbox" id="consume-exact-amount"
name="consume-exact-amount" value="1">
<label class="form-check-label custom-control-label"
for="consume-exact-amount">{{ $__t('Consume exact amount') }}
</label>
</div>
</div>
@include('components.productamountpicker', array(
'value' => 1,
'additionalHtmlContextHelp' => '<div id="tare-weight-handling-info"
class="text-info font-italic d-none">' . $__t('Tare weight handling enabled - please weigh the whole container, the amount to be posted will be automatically calculcated') . '</div>'
))
@include('components.productamountpicker', [
'value' => 1,
'additionalHtmlContextHelp' =>
'<div id="tare-weight-handling-info" class="text-info font-italic d-none">' .
$__t(
'Tare weight handling enabled - please weigh the whole container, the amount to be posted will be automatically calculcated'
) .
'</div>',
])
<div class="form-group @if(!GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) d-none @endif">
<label for="location_id">{{ $__t('Location') }}</label>
<select required
class="custom-control custom-select location-combobox"
id="location_id"
name="location_id">
<option></option>
@foreach($locations as $location)
<option value="{{ $location->id }}">{{ $location->name }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('A location is required') }}</div>
</div>
<div class="form-group @if (!GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) d-none @endif">
<label for="location_id">{{ $__t('Location') }}</label>
{{-- TODO: Select2: dynamic data: locations --}}
<select required class="custom-control custom-select location-combobox" id="location_id"
name="location_id">
<option></option>
@foreach ($locations as $location)
<option value="{{ $location->id }}">{{ $location->name }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('A location is required') }}</div>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input class="form-check-input custom-control-input"
type="checkbox"
id="spoiled"
name="spoiled"
value="1">
<label class="form-check-label custom-control-label"
for="spoiled">{{ $__t('Spoiled') }}
</label>
</div>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input class="form-check-input custom-control-input" type="checkbox" id="spoiled" name="spoiled"
value="1">
<label class="form-check-label custom-control-label" for="spoiled">{{ $__t('Spoiled') }}
</label>
</div>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input class="form-check-input custom-control-input"
type="checkbox"
id="use_specific_stock_entry"
name="use_specific_stock_entry"
value="1">
<label class="form-check-label custom-control-label"
for="use_specific_stock_entry">{{ $__t('Use a specific stock item') }}
&nbsp;<i class="fas fa-question-circle text-muted"
data-toggle="tooltip"
data-trigger="hover click"
title="{{ $__t('The first item in this list would be picked by the default rule which is "Opened first, then first due first, then first in first out"') }}"></i>
</label>
</div>
<select disabled
class="custom-control custom-select mt-2"
id="specific_stock_entry"
name="specific_stock_entry">
<option></option>
</select>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input class="form-check-input custom-control-input" type="checkbox" id="use_specific_stock_entry"
name="use_specific_stock_entry" value="1">
<label class="form-check-label custom-control-label"
for="use_specific_stock_entry">{{ $__t('Use a specific stock item') }}
&nbsp;<i class="fas fa-question-circle text-muted" data-toggle="tooltip"
data-trigger="hover click"
title="{{ $__t('The first item in this list would be picked by the default rule which is "Opened first, then first due first, then first in first out"') }}"></i>
</label>
</div>
<select disabled class="custom-control custom-select mt-2" id="specific_stock_entry"
name="specific_stock_entry">
<option></option>
</select>
</div>
@if (GROCY_FEATURE_FLAG_RECIPES)
@include('components.recipepicker', array(
'recipes' => $recipes,
'isRequired' => false,
'hint' => $__t('This is for statistical purposes only')
))
@endif
@if (GROCY_FEATURE_FLAG_RECIPES)
@include('components.recipepicker', [
'recipes' => $recipes,
'isRequired' => false,
'hint' => $__t('This is for statistical purposes only'),
])
@endif
<button id="save-consume-button"
class="btn btn-success">{{ $__t('OK') }}</button>
<button id="save-consume-button" class="btn btn-success">{{ $__t('OK') }}</button>
@if(GROCY_FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING)
<button id="save-mark-as-open-button"
class="btn btn-secondary permission-STOCK_OPEN">{{ $__t('Mark as opened') }}</button>
@endif
@if (GROCY_FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING)
<button id="save-mark-as-open-button"
class="btn btn-secondary permission-STOCK_OPEN">{{ $__t('Mark as opened') }}</button>
@endif
</form>
</div>
</form>
</div>
<div class="col-12 col-md-6 col-xl-4 hide-when-embedded">
@include('components.productcard')
</div>
</div>
<div class="col-12 col-md-6 col-xl-4 hide-when-embedded">
@include('components.productcard')
</div>
</div>
@stop

View File

@ -5,185 +5,156 @@
@section('viewJsName', 'equipment')
@section('content')
<div class="row">
<div class="col-12 col-md-4 pb-3">
<div class="title-related-links border-bottom mb-2 py-1">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
id="related-links">
<a class="btn btn-primary responsive-button m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/equipment/new') }}">
{{ $__t('Add') }}
</a>
</div>
</div>
<div class="row">
<div class="col-12 col-md-4 pb-3">
<div class="title-related-links border-bottom mb-2 py-1">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100" id="related-links">
<a class="btn btn-primary responsive-button m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/equipment/new') }}">
{{ $__t('Add') }}
</a>
</div>
</div>
<div class="row collapse d-md-flex"
id="table-filter-row">
<div class="col">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text"
id="search"
class="form-control"
placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row collapse d-md-flex" id="table-filter-row">
<div class="col">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text" id="search" class="form-control" placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button" class="btn btn-sm btn-outline-info" href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<table id="equipment-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#equipment-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Name') }}</th>
{{-- TODO: DataTables: dynamic data: equipment --}}
<table id="equipment-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#equipment-table" href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Name') }}</th>
@include('components.userfields_thead', array(
'userfields' => $userfields
))
@include('components.userfields_thead', [
'userfields' => $userfields,
])
</tr>
</thead>
<tbody class="d-none">
@foreach($equipment as $equipmentItem)
<tr data-equipment-id="{{ $equipmentItem->id }}">
<td class="fit-content border-right">
<a class="btn btn-info btn-sm hide-when-embedded hide-on-fullscreen-card"
href="{{ $U('/equipment/') }}{{ $equipmentItem->id }}"
data-toggle="tooltip"
title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<div class="dropdown d-inline-block">
<button class="btn btn-sm btn-light text-secondary"
type="button"
data-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="table-inline-menu dropdown-menu dropdown-menu-right hide-on-fullscreen-card hide-when-embedded">
<a class="dropdown-item equipment-delete-button"
type="button"
href="#"
data-equipment-id="{{ $equipmentItem->id }}"
data-equipment-name="{{ $equipmentItem->name }}">
<span class="dropdown-item-text">{{ $__t('Delete this item') }}</span>
</a>
</div>
</div>
</td>
<td>
{{ $equipmentItem->name }}
</td>
</tr>
</thead>
<tbody class="d-none">
@foreach ($equipment as $equipmentItem)
<tr data-equipment-id="{{ $equipmentItem->id }}">
<td class="fit-content border-right">
<a class="btn btn-info btn-sm hide-when-embedded hide-on-fullscreen-card"
href="{{ $U('/equipment/') }}{{ $equipmentItem->id }}" data-toggle="tooltip"
title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<div class="dropdown d-inline-block">
<button class="btn btn-sm btn-light text-secondary" type="button"
data-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<div
class="table-inline-menu dropdown-menu dropdown-menu-right hide-on-fullscreen-card hide-when-embedded">
<a class="dropdown-item equipment-delete-button" type="button" href="#"
data-equipment-id="{{ $equipmentItem->id }}"
data-equipment-name="{{ $equipmentItem->name }}">
<span class="dropdown-item-text">{{ $__t('Delete this item') }}</span>
</a>
</div>
</div>
</td>
<td>
{{ $equipmentItem->name }}
</td>
@include('components.userfields_tbody', array(
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $equipmentItem->id)
))
@include('components.userfields_tbody', [
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue(
$userfieldValues,
'object_id',
$equipmentItem->id
),
])
</tr>
@endforeach
</tbody>
</table>
</div>
</tr>
@endforeach
</tbody>
</table>
</div>
<div class="col-12 col-md-8">
<ul class="nav nav-tabs grocy-tabs">
<li class="nav-item">
<a class="nav-link active"
data-toggle="tab"
href="#instruction-manual-tab">{{ $__t('Instruction manual') }}</a>
</li>
<li class="nav-item">
<a class="nav-link"
data-toggle="tab"
href="#description-tab">{{ $__t('Notes') }}</a>
</li>
</ul>
<div class="tab-content grocy-tabs">
<div class="tab-pane fade show active"
id="instruction-manual-tab">
<div id="selectedEquipmentInstructionManualCard"
class="card">
<div class="card-header card-header-fullscreen">
<span class="selected-equipment-name"></span>
<a id="selectedEquipmentInstructionManualToggleFullscreenButton"
class="btn btn-sm btn-outline-secondary py-0 float-right mr-1"
href="#"
data-toggle="tooltip"
title="{{ $__t('Expand to fullscreen') }}">
<i class="fas fa-expand-arrows-alt"></i>
</a>
<a id="selectedEquipmentInstructionManualDownloadButton"
class="btn btn-sm btn-outline-secondary py-0 float-right mr-1"
href="#"
target="_blank"
data-toggle="tooltip"
title="{{ $__t('Download file') }}">
<i class="fas fa-file-download"></i>
</a>
</div>
<div class="card-body py-0 px-0">
<p id="selected-equipment-has-no-instruction-manual-hint"
class="text-muted font-italic d-none pt-3 pl-3">{{ $__t('The selected equipment has no instruction manual') }}</p>
<embed id="selected-equipment-instruction-manual"
class="embed-responsive embed-responsive-4by3"
src=""
type="application/pdf">
</div>
</div>
</div>
<div class="tab-pane fade"
id="description-tab">
<div id="selectedEquipmentDescriptionCard"
class="card">
<div class="card-header card-header-fullscreen">
<span class="selected-equipment-name"></span>
<a id="selectedEquipmentDescriptionToggleFullscreenButton"
class="btn btn-sm btn-outline-secondary py-0 float-right"
href="#"
data-toggle="tooltip"
title="{{ $__t('Expand to fullscreen') }}">
<i class="fas fa-expand-arrows-alt"></i>
</a>
</div>
<div class="card-body">
<div id="description-tab-content"
class="mb-0"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col-12 col-md-8">
<ul class="nav nav-tabs grocy-tabs">
<li class="nav-item">
<a class="nav-link active" data-toggle="tab"
href="#instruction-manual-tab">{{ $__t('Instruction manual') }}</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="tab" href="#description-tab">{{ $__t('Notes') }}</a>
</li>
</ul>
<div class="tab-content grocy-tabs">
<div class="tab-pane fade show active" id="instruction-manual-tab">
<div id="selectedEquipmentInstructionManualCard" class="card">
<div class="card-header card-header-fullscreen">
<span class="selected-equipment-name"></span>
<a id="selectedEquipmentInstructionManualToggleFullscreenButton"
class="btn btn-sm btn-outline-secondary py-0 float-right mr-1" href="#"
data-toggle="tooltip" title="{{ $__t('Expand to fullscreen') }}">
<i class="fas fa-expand-arrows-alt"></i>
</a>
<a id="selectedEquipmentInstructionManualDownloadButton"
class="btn btn-sm btn-outline-secondary py-0 float-right mr-1" href="#" target="_blank"
data-toggle="tooltip" title="{{ $__t('Download file') }}">
<i class="fas fa-file-download"></i>
</a>
</div>
<div class="card-body py-0 px-0">
<p id="selected-equipment-has-no-instruction-manual-hint"
class="text-muted font-italic d-none pt-3 pl-3">
{{ $__t('The selected equipment has no instruction manual') }}</p>
<embed id="selected-equipment-instruction-manual" class="embed-responsive embed-responsive-4by3"
src="" type="application/pdf">
</div>
</div>
</div>
<div class="tab-pane fade" id="description-tab">
<div id="selectedEquipmentDescriptionCard" class="card">
<div class="card-header card-header-fullscreen">
<span class="selected-equipment-name"></span>
<a id="selectedEquipmentDescriptionToggleFullscreenButton"
class="btn btn-sm btn-outline-secondary py-0 float-right" href="#" data-toggle="tooltip"
title="{{ $__t('Expand to fullscreen') }}">
<i class="fas fa-expand-arrows-alt"></i>
</a>
</div>
<div class="card-body">
<div id="description-tab-content" class="mb-0"></div>
</div>
</div>
</div>
</div>
</div>
</div>
@stop

View File

@ -21,33 +21,32 @@
novalidate>
@include('components.productpicker', array(
'products' => $products,
'barcodes' => $barcodes,
'nextInputSelector' => '#new_amount'
'productsQuery' => 'query%5B%5D=active%3D1&order=name%3Acollate%20nocase',
'nextInputSelector' => '#new_amount'
))
@include('components.productamountpicker', array(
'value' => 1,
'label' => 'New stock amount',
'additionalHtmlElements' => '<div id="inventory-change-info"
class="form-text text-muted d-none ml-3 my-0 w-100"></div>',
'additionalHtmlContextHelp' => '<div id="tare-weight-handling-info"
class="text-info font-italic d-none">' . $__t('Tare weight handling enabled - please weigh the whole container, the amount to be posted will be automatically calculcated') . '</div>'
'value' => 1,
'label' => 'New stock amount',
'additionalHtmlElements' => '<div id="inventory-change-info"
class="form-text text-muted d-none ml-3 my-0 w-100"></div>',
'additionalHtmlContextHelp' => '<div id="tare-weight-handling-info"
class="text-info font-italic d-none">' . $__t('Tare weight handling enabled - please weigh the whole container, the amount to be posted will be automatically calculcated') . '</div>'
))
@if(boolval($userSettings['show_purchased_date_on_purchase']))
@include('components.datetimepicker2', array(
'id' => 'purchased_date',
'label' => 'Purchased date',
'format' => 'YYYY-MM-DD',
'hint' => $__t('This will apply to added products'),
'initWithNow' => true,
'limitEndToNow' => false,
'limitStartToNow' => false,
'invalidFeedback' => $__t('A purchased date is required'),
'nextInputSelector' => '#best_before_date',
'additionalCssClasses' => 'date-only-datetimepicker2',
'activateNumberPad' => GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_FIELD_NUMBER_PAD
'id' => 'purchased_date',
'label' => 'Purchased date',
'format' => 'YYYY-MM-DD',
'hint' => $__t('This will apply to added products'),
'initWithNow' => true,
'limitEndToNow' => false,
'limitStartToNow' => false,
'invalidFeedback' => $__t('A purchased date is required'),
'nextInputSelector' => '#best_before_date',
'additionalCssClasses' => 'date-only-datetimepicker2',
'activateNumberPad' => GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_FIELD_NUMBER_PAD
))
@endif
@ -59,44 +58,44 @@
}
@endphp
@include('components.datetimepicker', array(
'id' => 'best_before_date',
'label' => 'Due date',
'hint' => $__t('This will apply to added products'),
'format' => 'YYYY-MM-DD',
'initWithNow' => false,
'limitEndToNow' => false,
'limitStartToNow' => false,
'invalidFeedback' => $__t('A due date is required'),
'nextInputSelector' => '#best_before_date',
'additionalGroupCssClasses' => 'date-only-datetimepicker',
'shortcutValue' => '2999-12-31',
'shortcutLabel' => 'Never overdue',
'earlierThanInfoLimit' => date('Y-m-d'),
'earlierThanInfoText' => $__t('The given date is earlier than today, are you sure?'),
'additionalGroupCssClasses' => $additionalGroupCssClasses,
'activateNumberPad' => GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_FIELD_NUMBER_PAD
'id' => 'best_before_date',
'label' => 'Due date',
'hint' => $__t('This will apply to added products'),
'format' => 'YYYY-MM-DD',
'initWithNow' => false,
'limitEndToNow' => false,
'limitStartToNow' => false,
'invalidFeedback' => $__t('A due date is required'),
'nextInputSelector' => '#best_before_date',
'additionalGroupCssClasses' => 'date-only-datetimepicker',
'shortcutValue' => '2999-12-31',
'shortcutLabel' => 'Never overdue',
'earlierThanInfoLimit' => date('Y-m-d'),
'earlierThanInfoText' => $__t('The given date is earlier than today, are you sure?'),
'additionalGroupCssClasses' => $additionalGroupCssClasses,
'activateNumberPad' => GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_FIELD_NUMBER_PAD
))
@php $additionalGroupCssClasses = ''; @endphp
@if(GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
@include('components.numberpicker', array(
'id' => 'price',
'label' => 'Price',
'min' => '0.' . str_repeat('0', $userSettings['stock_decimal_places_prices']),
'decimals' => $userSettings['stock_decimal_places_prices'],
'value' => '',
'hint' => $__t('Per stock quantity unit', GROCY_CURRENCY),
'additionalHtmlContextHelp' => '<i class="fas fa-question-circle text-muted"
data-toggle="tooltip"
data-trigger="hover click"
title="' . $__t('This will apply to added products') . '"></i>',
'isRequired' => false,
'additionalCssClasses' => 'locale-number-input locale-number-currency'
'id' => 'price',
'label' => 'Price',
'min' => '0.' . str_repeat('0', $userSettings['stock_decimal_places_prices']),
'decimals' => $userSettings['stock_decimal_places_prices'],
'value' => '',
'hint' => $__t('Per stock quantity unit', GROCY_CURRENCY),
'additionalHtmlContextHelp' => '<i class="fas fa-question-circle text-muted"
data-toggle="tooltip"
data-trigger="hover click"
title="' . $__t('This will apply to added products') . '"></i>',
'isRequired' => false,
'additionalCssClasses' => 'locale-number-input locale-number-currency'
))
@include('components.shoppinglocationpicker', array(
'label' => 'Store',
'shoppinglocations' => $shoppinglocations
'label' => 'Store',
'shoppinglocations' => $shoppinglocations
))
@else
<input type="hidden"
@ -107,8 +106,8 @@
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
@include('components.locationpicker', array(
'locations' => $locations,
'hint' => $__t('This will apply to added products')
'locations' => $locations,
'hint' => $__t('This will apply to added products')
))
@endif

View File

@ -73,6 +73,10 @@
rel="stylesheet">
<link href="{{ $U('/node_modules/@fontsource/noto-sans/latin.css?v=', true) }}{{ $version }}"
rel="stylesheet">
<link href="{{ $U('/node_modules/select2/dist/css/select2.min.css?v=', true) }}{{ $version }}"
rel="stylesheet">
<link href="{{ $U('/node_modules/select2-theme-bootstrap4/dist/select2-bootstrap.min.css?v=', true) }}{{ $version }}"
rel="stylesheet">
<link href="{{ $U('/css/grocy.css?v=', true) }}{{ $version }}"
rel="stylesheet">
<link href="{{ $U('/css/grocy_night_mode.css?v=', true) }}{{ $version }}"
@ -706,6 +710,7 @@
@if(!empty($__t('bootstrap-select_locale') && $__t('bootstrap-select_locale') != 'x'))<script src="{{ $U('/node_modules', true) }}/bootstrap-select/dist/js/i18n/defaults-{{ $__t('bootstrap-select_locale') }}.js?v={{ $version }}"></script>@endif
<script src="{{ $U('/node_modules/jquery-lazy/jquery.lazy.min.js?v=', true) }}{{ $version }}"></script>
<script src="{{ $U('/node_modules/nosleep.js/dist/NoSleep.min.js?v=', true) }}{{ $version }}"></script>
<script src="{{ $U('/node_modules/select2/dist/js/select2.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>
@ -722,4 +727,4 @@
@endif
</body>
</html>
</html>

View File

@ -4,101 +4,102 @@
@section('viewJsName', 'locationcontentsheet')
@push('pageStyles')
<style>
@media print {
.page:not(:last-child) {
page-break-after: always !important;
}
<style>
@media print {
.page:not(:last-child) {
page-break-after: always !important;
}
.page.no-page-break {
page-break-after: avoid !important;
}
.page.no-page-break {
page-break-after: avoid !important;
}
/*
Workaround because of Firefox bug
see https://github.com/twbs/bootstrap/issues/22753
and https://bugzilla.mozilla.org/show_bug.cgi?id=1413121
*/
.row {
display: inline !important;
}
}
/*
Workaround because of Firefox bug
see https://github.com/twbs/bootstrap/issues/22753
and https://bugzilla.mozilla.org/show_bug.cgi?id=1413121
*/
.row {
display: inline !important;
}
}
</style>
</style>
@endpush
@section('content')
<div class="title-related-links d-print-none">
<h2 class="title">
@yield('title')
<i class="fas fa-question-circle text-muted small"
data-toggle="tooltip"
data-trigger="hover click"
title="{{ $__t('Here you can print a page per location with the current stock, maybe to hang it there and note the consumed things on it') }}"></i>
</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
id="related-links">
<a class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right print-all-locations-button"
href="#">
{{ $__t('Print') . ' (' . $__t('all locations') . ')' }}
</a>
</div>
</div>
<div class="title-related-links d-print-none">
<h2 class="title">
@yield('title')
<i class="fas fa-question-circle text-muted small" data-toggle="tooltip" data-trigger="hover click"
title="{{ $__t('Here you can print a page per location with the current stock, maybe to hang it there and note the consumed things on it') }}"></i>
</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button" data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100" id="related-links">
<a class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right print-all-locations-button"
href="#">
{{ $__t('Print') . ' (' . $__t('all locations') . ')' }}
</a>
</div>
</div>
<hr class="my-2 d-print-none">
<hr class="my-2 d-print-none">
@foreach($locations as $location)
<div class="page">
<h1 class="pt-4 text-center">
<img src="{{ $U('/img/grocy_logo.svg?v=', true) }}{{ $version }}"
height="30"
class="d-none d-print-flex mx-auto">
{{ $location->name }}
<a class="btn btn-outline-dark btn-sm responsive-button print-single-location-button d-print-none"
href="#">
{{ $__t('Print') . ' (' . $__t('this location') . ')' }}
</a>
</h1>
<h6 class="mb-4 d-none d-print-block text-center">
{{ $__t('Time of printing') }}:
<span class="d-inline print-timestamp"></span>
</h6>
<div class="row w-75">
<div class="col">
<table class="table">
<thead>
<tr>
<th>{{ $__t('Product') }}</th>
<th>{{ $__t('Amount') }}</th>
<th>{{ $__t('Consumed amount') . ' / ' . $__t('Notes') }}</th>
</tr>
</thead>
<tbody>
@php $currentStockEntriesForLocation = FindAllObjectsInArrayByPropertyValue($currentStockLocationContent, 'location_id', $location->id); @endphp
@foreach($currentStockEntriesForLocation as $currentStockEntry)
<tr>
<td>
{{ FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name }}
</td>
<td>
<span class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->amount }}</span> <span id="product-{{ $currentStockEntry->product_id }}-qu-name">{{ $__n($currentStockEntry->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name_plural, true) }}</span>
<span class="small font-italic">@if($currentStockEntry->amount_opened > 0){{ $__t('%s opened', $currentStockEntry->amount_opened) }}@endif</span>
</td>
<td class=""></td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
@endforeach
@foreach ($locations as $location)
<div class="page">
<h1 class="pt-4 text-center">
<img src="{{ $U('/img/grocy_logo.svg?v=', true) }}{{ $version }}" height="30"
class="d-none d-print-flex mx-auto">
{{ $location->name }}
<a class="btn btn-outline-dark btn-sm responsive-button print-single-location-button d-print-none" href="#">
{{ $__t('Print') . ' (' . $__t('this location') . ')' }}
</a>
</h1>
<h6 class="mb-4 d-none d-print-block text-center">
{{ $__t('Time of printing') }}:
<span class="d-inline print-timestamp"></span>
</h6>
<div class="row w-75">
<div class="col">
{{-- TODO: DataTables: dynamic data: stock --}}
<table class="table">
<thead>
<tr>
<th>{{ $__t('Product') }}</th>
<th>{{ $__t('Amount') }}</th>
<th>{{ $__t('Consumed amount') . ' / ' . $__t('Notes') }}</th>
</tr>
</thead>
<tbody>
@php $currentStockEntriesForLocation = FindAllObjectsInArrayByPropertyValue($currentStockLocationContent, 'location_id', $location->id); @endphp
@foreach ($currentStockEntriesForLocation as $currentStockEntry)
<tr>
<td>
{{ FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name }}
</td>
<td>
<span
class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->amount }}</span>
<span
id="product-{{ $currentStockEntry->product_id }}-qu-name">{{ $__n($currentStockEntry->amount,FindObjectInArrayByPropertyValue($quantityunits,'id',FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name,FindObjectInArrayByPropertyValue($quantityunits,'id',FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name_plural,true) }}</span>
<span class="small font-italic">
@if ($currentStockEntry->amount_opened > 0)
{{ $__t('%s opened', $currentStockEntry->amount_opened) }}
@endif
</span>
</td>
<td class=""></td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
@endforeach
@stop

View File

@ -5,122 +5,108 @@
@section('viewJsName', 'locations')
@section('content')
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
id="related-links">
<a class="btn btn-primary responsive-button m-1 mt-md-0 mb-md-0 float-right show-as-dialog-link"
href="{{ $U('/location/new?embedded') }}">
{{ $__t('Add') }}
</a>
<a class="btn btn-outline-secondary m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/userfields?entity=locations') }}">
{{ $__t('Configure userfields') }}
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100" id="related-links">
<a class="btn btn-primary responsive-button m-1 mt-md-0 mb-md-0 float-right show-as-dialog-link"
href="{{ $U('/location/new?embedded') }}">
{{ $__t('Add') }}
</a>
<a class="btn btn-outline-secondary m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/userfields?entity=locations') }}">
{{ $__t('Configure userfields') }}
</a>
</div>
</div>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row collapse d-md-flex"
id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text"
id="search"
class="form-control"
placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row collapse d-md-flex" id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text" id="search" class="form-control" placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button" class="btn btn-sm btn-outline-info" href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row">
<div class="col">
<table id="locations-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#locations-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Name') }}</th>
<th>{{ $__t('Description') }}</th>
<div class="row">
<div class="col">
{{-- TODO: DataTables: dynamic data: locations --}}
<table id="locations-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#locations-table" href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Name') }}</th>
<th>{{ $__t('Description') }}</th>
@include('components.userfields_thead', array(
'userfields' => $userfields
))
@include('components.userfields_thead', [
'userfields' => $userfields,
])
</tr>
</thead>
<tbody class="d-none">
@foreach($locations as $location)
<tr>
<td class="fit-content border-right">
<a class="btn btn-info btn-sm show-as-dialog-link"
href="{{ $U('/location/') }}{{ $location->id }}?embedded"
data-toggle="tooltip"
title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger btn-sm location-delete-button"
href="#"
data-location-id="{{ $location->id }}"
data-location-name="{{ $location->name }}"
data-toggle="tooltip"
title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
{{ $location->name }}
</td>
<td>
{{ $location->description }}
</td>
</tr>
</thead>
<tbody class="d-none">
@foreach ($locations as $location)
<tr>
<td class="fit-content border-right">
<a class="btn btn-info btn-sm show-as-dialog-link"
href="{{ $U('/location/') }}{{ $location->id }}?embedded" data-toggle="tooltip"
title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger btn-sm location-delete-button" href="#"
data-location-id="{{ $location->id }}" data-location-name="{{ $location->name }}"
data-toggle="tooltip" title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
{{ $location->name }}
</td>
<td>
{{ $location->description }}
</td>
@include('components.userfields_tbody', array(
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $location->id)
))
@include('components.userfields_tbody', [
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue(
$userfieldValues,
'object_id',
$location->id
),
])
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@stop

View File

@ -5,140 +5,120 @@
@section('viewJsName', 'manageapikeys')
@push('pageScripts')
<script src="{{ $U('/node_modules/bwip-js/dist/bwip-js-min.js?v=', true) }}{{ $version }}"></script>
<script src="{{ $U('/node_modules/bwip-js/dist/bwip-js-min.js?v=', true) }}{{ $version }}"></script>
@endpush
@push('pageStyles')
<link href="{{ $U('/node_modules/animate.css/animate.min.css?v=', true) }}{{ $version }}"
rel="stylesheet">
<link href="{{ $U('/node_modules/animate.css/animate.min.css?v=', true) }}{{ $version }}" rel="stylesheet">
@endpush
@section('content')
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
id="related-links">
<a class="btn btn-primary responsive-button m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/manageapikeys/new') }}">
{{ $__t('Add') }}
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100" id="related-links">
<a class="btn btn-primary responsive-button m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/manageapikeys/new') }}">
{{ $__t('Add') }}
</a>
</div>
</div>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row collapse d-md-flex"
id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text"
id="search"
class="form-control"
placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row collapse d-md-flex" id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text" id="search" class="form-control" placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button" class="btn btn-sm btn-outline-info" href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row">
<div class="col">
<table id="apikeys-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#apikeys-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('API key') }}</th>
<th class="allow-grouping">{{ $__t('User') }}</th>
<th>{{ $__t('Expires') }}</th>
<th>{{ $__t('Last used') }}</th>
<th>{{ $__t('Created') }}</th>
<th class="allow-grouping">{{ $__t('Key type') }}</th>
</tr>
</thead>
<tbody class="d-none">
@foreach($apiKeys as $apiKey)
<tr id="apiKeyRow_{{ $apiKey->id }}">
<td class="fit-content border-right">
<a class="btn btn-danger btn-sm apikey-delete-button"
href="#"
data-apikey-id="{{ $apiKey->id }}"
data-apikey-apikey="{{ $apiKey->api_key }}"
data-toggle="tooltip"
title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
<a class="btn btn-info btn-sm apikey-show-qr-button"
href="#"
data-apikey-key="{{ $apiKey->api_key }}"
data-apikey-type="{{ $apiKey->key_type }}"
data-toggle="tooltip"
title="{{ $__t('Show a QR-Code for this API key') }}">
<i class="fas fa-qrcode"></i>
</a>
</td>
<td>
{{ $apiKey->api_key }}
</td>
<td>
{{ GetUserDisplayName(FindObjectInArrayByPropertyValue($users, 'id', $apiKey->user_id)) }}
</td>
<td>
{{ $apiKey->expires }}
<time class="timeago timeago-contextual"
datetime="{{ $apiKey->expires }}"></time>
</td>
<td>
@if(empty($apiKey->last_used)){{ $__t('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>
<td>
{{ $apiKey->key_type }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="col">
{{-- TODO: DataTables: dynamic data: API keys --}}
<table id="apikeys-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#apikeys-table" href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('API key') }}</th>
<th class="allow-grouping">{{ $__t('User') }}</th>
<th>{{ $__t('Expires') }}</th>
<th>{{ $__t('Last used') }}</th>
<th>{{ $__t('Created') }}</th>
<th class="allow-grouping">{{ $__t('Key type') }}</th>
</tr>
</thead>
<tbody class="d-none">
@foreach ($apiKeys as $apiKey)
<tr id="apiKeyRow_{{ $apiKey->id }}">
<td class="fit-content border-right">
<a class="btn btn-danger btn-sm apikey-delete-button" href="#"
data-apikey-id="{{ $apiKey->id }}" data-apikey-apikey="{{ $apiKey->api_key }}"
data-toggle="tooltip" title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
<a class="btn btn-info btn-sm apikey-show-qr-button" href="#"
data-apikey-key="{{ $apiKey->api_key }}"
data-apikey-type="{{ $apiKey->key_type }}" data-toggle="tooltip"
title="{{ $__t('Show a QR-Code for this API key') }}">
<i class="fas fa-qrcode"></i>
</a>
</td>
<td>
{{ $apiKey->api_key }}
</td>
<td>
{{ GetUserDisplayName(FindObjectInArrayByPropertyValue($users, 'id', $apiKey->user_id)) }}
</td>
<td>
{{ $apiKey->expires }}
<time class="timeago timeago-contextual" datetime="{{ $apiKey->expires }}"></time>
</td>
<td>
@if (empty($apiKey->last_used))
{{ $__t('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>
<td>
{{ $apiKey->key_type }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@stop

View File

@ -5,365 +5,311 @@
@section('viewJsName', 'mealplan')
@push('pageScripts')
<script src="{{ $U('/node_modules/fullcalendar/dist/fullcalendar.min.js?v=', true) }}{{ $version }}"></script>
@if(!empty($__t('fullcalendar_locale') && $__t('fullcalendar_locale') != 'x'))<script src="{{ $U('/node_modules', true) }}/fullcalendar/dist/locale/{{ $__t('fullcalendar_locale') }}.js?v={{ $version }}"></script>@endif
<script src="{{ $U('/node_modules/fullcalendar/dist/fullcalendar.min.js?v=', true) }}{{ $version }}"></script>
@if (!empty($__t('fullcalendar_locale') && $__t('fullcalendar_locale') != 'x'))
<script
src="{{ $U('/node_modules', true) }}/fullcalendar/dist/locale/{{ $__t('fullcalendar_locale') }}.js?v={{ $version }}">
</script>
@endif
@endpush
@push('pageStyles')
<link href="{{ $U('/node_modules/fullcalendar/dist/fullcalendar.min.css?v=', true) }}{{ $version }}"
rel="stylesheet">
<link href="{{ $U('/node_modules/fullcalendar/dist/fullcalendar.min.css?v=', true) }}{{ $version }}"
rel="stylesheet">
<style>
.fc-event-container {
border-bottom: 1px solid !important;
border-color: #d6d6d6 !important;
}
<style>
.fc-event-container {
border-bottom: 1px solid !important;
border-color: #d6d6d6 !important;
}
.img-fluid {
max-width: 90%;
max-height: 140px;
}
.img-fluid {
max-width: 90%;
max-height: 140px;
}
.fc-time-grid-container,
hr.fc-divider {
display: none;
}
.fc-time-grid-container,
hr.fc-divider {
display: none;
}
.fc-axis {
width: 25px !important;
}
.fc-axis {
width: 25px !important;
}
.fc-axis div {
transform: translateX(-50%) translateY(-50%) rotate(-90deg);
font-weight: bold;
font-size: 1.75em;
letter-spacing: 0.1em;
position: absolute;
top: 50%;
left: 0;
margin-left: 17px;
min-width: 100px;
line-height: 0.55;
text-align: center;
}
.fc-axis div {
transform: translateX(-50%) translateY(-50%) rotate(-90deg);
font-weight: bold;
font-size: 1.75em;
letter-spacing: 0.1em;
position: absolute;
top: 50%;
left: 0;
margin-left: 17px;
min-width: 100px;
line-height: 0.55;
text-align: center;
}
.fc-axis .small {
font-size: 60%;
letter-spacing: normal;
}
.fc-axis .small {
font-size: 60%;
letter-spacing: normal;
}
.fc-content-skeleton {
padding-bottom: 0 !important;
}
.fc-content-skeleton {
padding-bottom: 0 !important;
}
.calendar[data-primary-section='false'] .fc-toolbar.fc-header-toolbar,
.calendar[data-primary-section='false'] .fc-head {
display: none;
}
.calendar[data-primary-section='false'] .fc-toolbar.fc-header-toolbar,
.calendar[data-primary-section='false'] .fc-head {
display: none;
}
.calendar[data-primary-section='false'] {
border-top: #d6d6d6 solid 5px;
}
.calendar[data-primary-section='false'] {
border-top: #d6d6d6 solid 5px;
}
@media (min-width: 400px) {
.table-inline-menu.dropdown-menu {
width: 200px !important;
}
}
@media (min-width: 400px) {
.table-inline-menu.dropdown-menu {
width: 200px !important;
}
}
</style>
</style>
@endpush
@section('content')
<script>
var fullcalendarEventSources = {!! json_encode(array($fullcalendarEventSources)) !!}
var internalRecipes = {!! json_encode($internalRecipes) !!}
var recipesResolved = {!! json_encode($recipesResolved) !!}
<script>
var fullcalendarEventSources = {!! json_encode([$fullcalendarEventSources]) !!}
var internalRecipes = {!! json_encode($internalRecipes) !!}
var recipesResolved = {!! json_encode($recipesResolved) !!}
Grocy.QuantityUnits = {!! json_encode($quantityUnits) !!};
Grocy.QuantityUnitConversionsResolved = {!! json_encode($quantityUnitConversionsResolved) !!};
Grocy.QuantityUnits = {!! json_encode($quantityUnits) !!};
Grocy.QuantityUnitConversionsResolved = {!! json_encode($quantityUnitConversionsResolved) !!};
Grocy.MealPlanFirstDayOfWeek = '{{ GROCY_MEAL_PLAN_FIRST_DAY_OF_WEEK }}';
</script>
Grocy.MealPlanFirstDayOfWeek = '{{ GROCY_MEAL_PLAN_FIRST_DAY_OF_WEEK }}';
</script>
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right d-print-none">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100 d-print-none"
id="related-links">
<a id="print-meal-plan-button"
class="btn btn-outline-dark m-1 mt-md-0 mb-md-0 float-right">
{{ $__t('Print') }}
</a>
<a class="btn btn-outline-secondary m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/mealplansections') }}">
{{ $__t('Configure sections') }}
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right d-print-none">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100 d-print-none" id="related-links">
<a id="print-meal-plan-button" class="btn btn-outline-dark m-1 mt-md-0 mb-md-0 float-right">
{{ $__t('Print') }}
</a>
<a class="btn btn-outline-secondary m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/mealplansections') }}">
{{ $__t('Configure sections') }}
</a>
</div>
</div>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
@foreach($usedMealplanSections as $mealplanSection)
<div class="row">
<div class="col">
<div class="calendar"
data-section-id="{{ $mealplanSection->id }}"
data-section-name="{{ $mealplanSection->name }}<br><span class='small text-muted'>{{ $mealplanSection->time_info }}</span>"
data-primary-section="{{ BoolToString($loop->first) }}"
{{--
$loop->last doesn't work however, is always null...
--}}
data-last-section="{{ BoolToString(array_values(array_slice($usedMealplanSections->fetchAll(), -1))[0]->id == $mealplanSection->id) }}">
</div>
</div>
</div>
@endforeach
@foreach ($usedMealplanSections as $mealplanSection)
<div class="row">
<div class="col">
<div class="calendar" data-section-id="{{ $mealplanSection->id }}"
data-section-name="{{ $mealplanSection->name }}<br><span class='small text-muted'>{{ $mealplanSection->time_info }}</span>"
data-primary-section="{{ BoolToString($loop->first) }}" {{-- $loop->last doesn't work however, is always null... --}}
data-last-section="{{ BoolToString(array_values(array_slice($usedMealplanSections->fetchAll(), -1))[0]->id == $mealplanSection->id) }}">
</div>
</div>
</div>
@endforeach
{{-- Default empty calendar/section when no single meal plan entry is in the given date range --}}
@if($usedMealplanSections->count() === 0)
<div class="row">
<div class="col">
<div class="calendar"
data-section-id="-1"
data-section-name=""
data-primary-section="true"
data-last-section="true">
</div>
</div>
</div>
@endif
{{-- Default empty calendar/section when no single meal plan entry is in the given date range --}}
@if ($usedMealplanSections->count() === 0)
<div class="row">
<div class="col">
<div class="calendar" data-section-id="-1" data-section-name="" data-primary-section="true"
data-last-section="true">
</div>
</div>
</div>
@endif
<div class="modal fade"
id="add-recipe-modal"
tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 id="add-recipe-modal-title"
class="modal-title w-100"></h4>
</div>
<div class="modal-body">
<form id="add-recipe-form"
novalidate>
<div class="modal fade" id="add-recipe-modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 id="add-recipe-modal-title" class="modal-title w-100"></h4>
</div>
<div class="modal-body">
<form id="add-recipe-form" novalidate>
@include('components.recipepicker', array(
'recipes' => $recipes,
'isRequired' => true,
'nextInputSelector' => '#recipe_servings'
))
@include('components.recipepicker', [
'recipes' => $recipes,
'isRequired' => true,
'nextInputSelector' => '#recipe_servings',
])
@include('components.numberpicker', array(
'id' => 'recipe_servings',
'label' => 'Servings',
'min' => $DEFAULT_MIN_AMOUNT,
'decimals' => $userSettings['stock_decimal_places_amounts'],
'value' => '1',
'additionalCssClasses' => 'locale-number-input locale-number-quantity-amount'
))
@include('components.numberpicker', [
'id' => 'recipe_servings',
'label' => 'Servings',
'min' => $DEFAULT_MIN_AMOUNT,
'decimals' => $userSettings['stock_decimal_places_amounts'],
'value' => '1',
'additionalCssClasses' => 'locale-number-input locale-number-quantity-amount',
])
<div class="form-group">
<label for="period_type">{{ $__t('Section') }}</label>
<select class="custom-control custom-select"
id="section_id_recipe"
name="section_id_recipe"
required>
@foreach($mealplanSections as $mealplanSection)
<option value="{{ $mealplanSection->id }}">{{ $mealplanSection->name }}</option>
@endforeach
</select>
</div>
<div class="form-group">
<label for="period_type">{{ $__t('Section') }}</label>
{{-- TODO: Select2: dynamic data: meal_plan_sections --}}
<select class="custom-control custom-select" id="section_id_recipe" name="section_id_recipe"
required>
@foreach ($mealplanSections as $mealplanSection)
<option value="{{ $mealplanSection->id }}">{{ $mealplanSection->name }}</option>
@endforeach
</select>
</div>
<input type="hidden"
id="day"
name="day"
value="">
<input type="hidden"
name="type"
value="recipe">
<input type="hidden" id="day" name="day" value="">
<input type="hidden" name="type" value="recipe">
</form>
</div>
<div class="modal-footer">
<button type="button"
class="btn btn-secondary"
data-dismiss="modal">{{ $__t('Cancel') }}</button>
<button id="save-add-recipe-button"
data-dismiss="modal"
class="btn btn-success">{{ $__t('Save') }}</button>
</div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ $__t('Cancel') }}</button>
<button id="save-add-recipe-button" data-dismiss="modal"
class="btn btn-success">{{ $__t('Save') }}</button>
</div>
</div>
</div>
</div>
<div class="modal fade"
id="add-note-modal"
tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 id="add-note-modal-title"
class="modal-title w-100"></h4>
</div>
<div class="modal-body">
<form id="add-note-form"
novalidate>
<div class="modal fade" id="add-note-modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 id="add-note-modal-title" class="modal-title w-100"></h4>
</div>
<div class="modal-body">
<form id="add-note-form" novalidate>
<div class="form-group">
<label for="note">{{ $__t('Note') }}</label>
<textarea class="form-control"
rows="2"
id="note"
name="note"></textarea>
</div>
<div class="form-group">
<label for="note">{{ $__t('Note') }}</label>
<textarea class="form-control" rows="2" id="note" name="note"></textarea>
</div>
<div class="form-group">
<label for="period_type">{{ $__t('Section') }}</label>
<select class="custom-control custom-select"
id="section_id_note"
name="section_id_note"
required>
@foreach($mealplanSections as $mealplanSection)
<option value="{{ $mealplanSection->id }}">{{ $mealplanSection->name }}</option>
@endforeach
</select>
</div>
<div class="form-group">
<label for="period_type">{{ $__t('Section') }}</label>
{{-- TODO: Select2: dynamic data: meal_plan_sections --}}
<select class="custom-control custom-select" id="section_id_note" name="section_id_note"
required>
@foreach ($mealplanSections as $mealplanSection)
<option value="{{ $mealplanSection->id }}">{{ $mealplanSection->name }}</option>
@endforeach
</select>
</div>
<input type="hidden"
name="type"
value="note">
<input type="hidden" name="type" value="note">
</form>
</div>
<div class="modal-footer">
<button type="button"
class="btn btn-secondary"
data-dismiss="modal">{{ $__t('Cancel') }}</button>
<button id="save-add-note-button"
data-dismiss="modal"
class="btn btn-success">{{ $__t('Save') }}</button>
</div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ $__t('Cancel') }}</button>
<button id="save-add-note-button" data-dismiss="modal"
class="btn btn-success">{{ $__t('Save') }}</button>
</div>
</div>
</div>
</div>
<div class="modal fade"
id="add-product-modal"
tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 id="add-product-modal-title"
class="modal-title w-100"></h4>
</div>
<div class="modal-body">
<form id="add-product-form"
novalidate>
<div class="modal fade" id="add-product-modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 id="add-product-modal-title" class="modal-title w-100"></h4>
</div>
<div class="modal-body">
<form id="add-product-form" novalidate>
@include('components.productpicker', array(
'products' => $products,
'nextInputSelector' => '#amount'
))
@include('components.productpicker', [
'productsQuery' => 'order=name%3Acollate%20nocase',
'nextInputSelector' => '#amount',
])
@include('components.productamountpicker', array(
'value' => 1,
'additionalGroupCssClasses' => 'mb-0'
))
@include('components.productamountpicker', [
'value' => 1,
'additionalGroupCssClasses' => 'mb-0',
])
<div class="form-group">
<label for="period_type">{{ $__t('Section') }}</label>
<select class="custom-control custom-select"
id="section_id_product"
name="section_id_product"
required>
@foreach($mealplanSections as $mealplanSection)
<option value="{{ $mealplanSection->id }}">{{ $mealplanSection->name }}</option>
@endforeach
</select>
</div>
<div class="form-group">
<label for="period_type">{{ $__t('Section') }}</label>
{{-- TODO: Select2: dynamic data: meal_plan_sections --}}
<select class="custom-control custom-select" id="section_id_product" name="section_id_product"
required>
@foreach ($mealplanSections as $mealplanSection)
<option value="{{ $mealplanSection->id }}">{{ $mealplanSection->name }}</option>
@endforeach
</select>
</div>
<input type="hidden"
name="type"
value="product">
<input type="hidden" name="type" value="product">
</form>
</div>
<div class="modal-footer">
<button type="button"
class="btn btn-secondary"
data-dismiss="modal">{{ $__t('Cancel') }}</button>
<button id="save-add-product-button"
data-dismiss="modal"
class="btn btn-success">{{ $__t('Save') }}</button>
</div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ $__t('Cancel') }}</button>
<button id="save-add-product-button" data-dismiss="modal"
class="btn btn-success">{{ $__t('Save') }}</button>
</div>
</div>
</div>
</div>
<div class="modal fade"
id="copy-day-modal"
tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 id="copy-day-modal-title"
class="modal-title w-100"></h4>
</div>
<div class="modal-body">
<form id="copy-day-form"
novalidate>
<div class="modal fade" id="copy-day-modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 id="copy-day-modal-title" class="modal-title w-100"></h4>
</div>
<div class="modal-body">
<form id="copy-day-form" novalidate>
@include('components.datetimepicker', array(
'id' => 'copy_to_date',
'label' => 'Day',
'format' => 'YYYY-MM-DD',
'initWithNow' => false,
'limitEndToNow' => false,
'limitStartToNow' => false,
'isRequired' => true,
'additionalCssClasses' => 'date-only-datetimepicker',
'invalidFeedback' => $__t('A date is required')
))
@include('components.datetimepicker', [
'id' => 'copy_to_date',
'label' => 'Day',
'format' => 'YYYY-MM-DD',
'initWithNow' => false,
'limitEndToNow' => false,
'limitStartToNow' => false,
'isRequired' => true,
'additionalCssClasses' => 'date-only-datetimepicker',
'invalidFeedback' => $__t('A date is required'),
])
</form>
</div>
<div class="modal-footer">
<button type="button"
class="btn btn-secondary"
data-dismiss="modal">{{ $__t('Cancel') }}</button>
<button id="save-copy-day-button"
data-dismiss="modal"
class="btn btn-primary">{{ $__t('Copy') }}</button>
</div>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ $__t('Cancel') }}</button>
<button id="save-copy-day-button" data-dismiss="modal"
class="btn btn-primary">{{ $__t('Copy') }}</button>
</div>
</div>
</div>
</div>
<div class="modal fade"
id="mealplan-productcard-modal"
tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-center">
<div class="modal-body">
@include('components.productcard')
</div>
<div class="modal-footer">
<button type="button"
class="btn btn-secondary"
data-dismiss="modal">{{ $__t('Close') }}</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="mealplan-productcard-modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-center">
<div class="modal-body">
@include('components.productcard')
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ $__t('Close') }}</button>
</div>
</div>
</div>
</div>
@stop

View File

@ -5,111 +5,94 @@
@section('viewJsName', 'mealplansections')
@section('content')
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
id="related-links">
<a class="btn btn-primary responsive-button m-1 mt-md-0 mb-md-0 float-right show-as-dialog-link"
href="{{ $U('/mealplansection/new?embedded') }}">
{{ $__t('Add') }}
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100" id="related-links">
<a class="btn btn-primary responsive-button m-1 mt-md-0 mb-md-0 float-right show-as-dialog-link"
href="{{ $U('/mealplansection/new?embedded') }}">
{{ $__t('Add') }}
</a>
</div>
</div>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row collapse d-md-flex"
id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text"
id="search"
class="form-control"
placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row collapse d-md-flex" id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text" id="search" class="form-control" placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button" class="btn btn-sm btn-outline-info" href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row">
<div class="col">
<table id="mealplansections-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#mealplansections-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Name') }}</th>
<th>{{ $__t('Sort number') }}</th>
<th>{{ $__t('Time') }}</th>
</tr>
</thead>
<tbody class="d-none">
@foreach($mealplanSections as $mealplanSection)
<tr>
<td class="fit-content border-right">
<a class="btn btn-info btn-sm show-as-dialog-link"
href="{{ $U('/mealplansection/') }}{{ $mealplanSection->id }}?embedded"
data-toggle="tooltip"
title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger btn-sm mealplansection-delete-button"
href="#"
data-mealplansection-id="{{ $mealplanSection->id }}"
data-mealplansection-name="{{ $mealplanSection->name }}"
data-toggle="tooltip"
title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
{{ $mealplanSection->name }}
</td>
<td>
{{ $mealplanSection->sort_number }}
</td>
<td>
{{ $mealplanSection->time_info }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="col">
{{-- TODO: DataTables: dynamic data: meal_plan_sections --}}
<table id="mealplansections-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#mealplansections-table" href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Name') }}</th>
<th>{{ $__t('Sort number') }}</th>
<th>{{ $__t('Time') }}</th>
</tr>
</thead>
<tbody class="d-none">
@foreach ($mealplanSections as $mealplanSection)
<tr>
<td class="fit-content border-right">
<a class="btn btn-info btn-sm show-as-dialog-link"
href="{{ $U('/mealplansection/') }}{{ $mealplanSection->id }}?embedded"
data-toggle="tooltip" title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger btn-sm mealplansection-delete-button" href="#"
data-mealplansection-id="{{ $mealplanSection->id }}"
data-mealplansection-name="{{ $mealplanSection->name }}" data-toggle="tooltip"
title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
{{ $mealplanSection->name }}
</td>
<td>
{{ $mealplanSection->sort_number }}
</td>
<td>
{{ $mealplanSection->time_info }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@stop

View File

@ -1,113 +1,104 @@
@extends('layout.default')
@if($mode == 'edit')
@section('title', $__t('Edit Barcode'))
@if ($mode == 'edit')
@section('title', $__t('Edit Barcode'))
@else
@section('title', $__t('Create Barcode'))
@section('title', $__t('Create Barcode'))
@endif
@section('viewJsName', 'productbarcodeform')
@section('content')
<script>
Grocy.QuantityUnits = {!! json_encode($quantityUnits) !!};
Grocy.QuantityUnitConversionsResolved = {!! json_encode($quantityUnitConversionsResolved) !!};
</script>
<script>
Grocy.QuantityUnits = {!! json_encode($quantityUnits) !!};
Grocy.QuantityUnitConversionsResolved = {!! json_encode($quantityUnitConversionsResolved) !!};
</script>
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<h2>
<span class="text-muted small">{{ $__t('Barcode for product') }} <strong>{{ $product->name }}</strong></span>
</h2>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<h2>
<span class="text-muted small">{{ $__t('Barcode for product') }}
<strong>{{ $product->name }}</strong></span>
</h2>
</div>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row">
<div class="col-lg-6 col-12">
<div class="row">
<div class="col-lg-6 col-12">
<script>
Grocy.EditMode = '{{ $mode }}';
Grocy.EditObjectProduct = {!! json_encode($product) !!};
</script>
<script>
Grocy.EditMode = '{{ $mode }}';
Grocy.EditObjectProduct = {!! json_encode($product) !!};
</script>
@if($mode == 'edit')
<script>
Grocy.EditObjectId = {{ $barcode->id }};
Grocy.EditObject = {!! json_encode($barcode) !!};
</script>
@endif
@if ($mode == 'edit')
<script>
Grocy.EditObjectId = {{ $barcode->id }};
Grocy.EditObject = {!! json_encode($barcode) !!};
</script>
@endif
<form id="barcode-form"
novalidate>
<form id="barcode-form" novalidate>
<input type="hidden"
name="product_id"
value="{{ $product->id }}">
<input type="hidden" name="product_id" value="{{ $product->id }}">
<div class="form-group">
<label for="name">{{ $__t('Barcode') }}&nbsp;<i class="fas fa-barcode"></i></label>
<div class="input-group">
<input type="text"
class="form-control barcodescanner-input"
required
id="barcode"
name="barcode"
value="@if($mode == 'edit'){{ $barcode->barcode }}@endif"
data-target="#barcode">
@include('components.barcodescanner')
</div>
</div>
<div class="form-group">
<label for="name">{{ $__t('Barcode') }}&nbsp;<i class="fas fa-barcode"></i></label>
<div class="input-group">
<input type="text" class="form-control barcodescanner-input" required id="barcode" name="barcode"
value="@if ($mode == 'edit') {{ $barcode->barcode }} @endif"
data-target="#barcode">
@include('components.barcodescanner')
</div>
</div>
@php if($mode == 'edit') { $value = $barcode->amount; } else { $value = ''; } @endphp
@include('components.productamountpicker', array(
'value' => $value,
'isRequired' => false
))
@php
if ($mode == 'edit') {
$value = $barcode->amount;
} else {
$value = '';
}
@endphp
@include('components.productamountpicker', [
'value' => $value,
'isRequired' => false,
])
@if(GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
<div class="form-group">
<label for="shopping_location_id_id">{{ $__t('Store') }}</label>
<select class="custom-control custom-select"
id="shopping_location_id"
name="shopping_location_id">
<option></option>
@foreach($shoppinglocations as $store)
<option @if($mode=='edit'
&&
$store->id == $barcode->shopping_location_id) selected="selected" @endif value="{{ $store->id }}">{{ $store->name }}</option>
@endforeach
</select>
</div>
@else
<input type="hidden"
name="shopping_location_id"
id="shopping_location_id"
value="1">
@endif
@if (GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
<div class="form-group">
<label for="shopping_location_id_id">{{ $__t('Store') }}</label>
{{-- TODO: Select2: dynamic data: shopping_locations --}}
<select class="custom-control custom-select" id="shopping_location_id" name="shopping_location_id">
<option></option>
@foreach ($shoppinglocations as $store)
<option @if ($mode == 'edit' && $store->id == $barcode->shopping_location_id) selected="selected" @endif
value="{{ $store->id }}">{{ $store->name }}</option>
@endforeach
</select>
</div>
@else
<input type="hidden" name="shopping_location_id" id="shopping_location_id" value="1">
@endif
<div class="form-group">
<label for="note">{{ $__t('Note') }}</label>
<input type="text"
class="form-control"
id="note"
name="note"
value="@if($mode == 'edit'){{ $barcode->note }}@endif">
</div>
<div class="form-group">
<label for="note">{{ $__t('Note') }}</label>
<input type="text" class="form-control" id="note" name="note"
value="@if ($mode == 'edit') {{ $barcode->note }} @endif">
</div>
@include('components.userfieldsform', array(
'userfields' => $userfields,
'entity' => 'product_barcodes'
))
@include('components.userfieldsform', [
'userfields' => $userfields,
'entity' => 'product_barcodes',
])
<button id="save-barcode-button"
class="btn btn-success">{{ $__t('Save') }}</button>
<button id="save-barcode-button" class="btn btn-success">{{ $__t('Save') }}</button>
</form>
</div>
</div>
</form>
</div>
</div>
@stop

File diff suppressed because it is too large Load Diff

View File

@ -5,129 +5,116 @@
@section('viewJsName', 'productgroups')
@section('content')
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
id="related-links">
<a class="btn btn-primary responsive-button show-as-dialog-link m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/productgroup/new?embedded') }}">
{{ $__t('Add') }}
</a>
<a class="btn btn-outline-secondary m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/userfields?entity=product_groups') }}">
{{ $__t('Configure userfields') }}
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100" id="related-links">
<a class="btn btn-primary responsive-button show-as-dialog-link m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/productgroup/new?embedded') }}">
{{ $__t('Add') }}
</a>
<a class="btn btn-outline-secondary m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/userfields?entity=product_groups') }}">
{{ $__t('Configure userfields') }}
</a>
</div>
</div>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row collapse d-md-flex"
id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text"
id="search"
class="form-control"
placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row collapse d-md-flex" id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text" id="search" class="form-control" placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button" class="btn btn-sm btn-outline-info" href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row">
<div class="col">
<table id="productgroups-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#productgroups-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Name') }}</th>
<th>{{ $__t('Description') }}</th>
<th>{{ $__t('Product count') }}</th>
<div class="row">
<div class="col">
{{-- TODO: DataTables: dynamic data: product_groups --}}
<table id="productgroups-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#productgroups-table" href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Name') }}</th>
<th>{{ $__t('Description') }}</th>
<th>{{ $__t('Product count') }}</th>
@include('components.userfields_thead', array(
'userfields' => $userfields
))
</tr>
</thead>
<tbody class="d-none">
@foreach($productGroups as $productGroup)
<tr>
<td class="fit-content border-right">
<a class="btn btn-info btn-sm show-as-dialog-link"
href="{{ $U('/productgroup/') }}{{ $productGroup->id }}?embedded"
data-toggle="tooltip"
title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger btn-sm product-group-delete-button"
href="#"
data-group-id="{{ $productGroup->id }}"
data-group-name="{{ $productGroup->name }}"
data-toggle="tooltip"
title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
{{ $productGroup->name }}
</td>
<td>
{{ $productGroup->description }}
</td>
<td>
{{ count(FindAllObjectsInArrayByPropertyValue($products, 'product_group_id', $productGroup->id)) }}
<a class="btn btn-link btn-sm text-body"
href="{{ $U('/products?product-group=') . $productGroup->id }}">
<i class="fas fa-external-link-alt"></i>
</a>
</td>
@include('components.userfields_thead', [
'userfields' => $userfields,
])
</tr>
</thead>
<tbody class="d-none">
@foreach ($productGroups as $productGroup)
<tr>
<td class="fit-content border-right">
<a class="btn btn-info btn-sm show-as-dialog-link"
href="{{ $U('/productgroup/') }}{{ $productGroup->id }}?embedded"
data-toggle="tooltip" title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger btn-sm product-group-delete-button" href="#"
data-group-id="{{ $productGroup->id }}"
data-group-name="{{ $productGroup->name }}" data-toggle="tooltip"
title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
{{ $productGroup->name }}
</td>
<td>
{{ $productGroup->description }}
</td>
<td>
{{ count(FindAllObjectsInArrayByPropertyValue($products, 'product_group_id', $productGroup->id)) }}
<a class="btn btn-link btn-sm text-body"
href="{{ $U('/products?product-group=') . $productGroup->id }}">
<i class="fas fa-external-link-alt"></i>
</a>
</td>
@include('components.userfields_tbody', array(
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $productGroup->id)
))
@include('components.userfields_tbody', [
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue(
$userfieldValues,
'object_id',
$productGroup->id
),
])
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@stop

View File

@ -5,268 +5,148 @@
@section('viewJsName', 'products')
@section('content')
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
id="related-links">
<a class="btn btn-primary responsive-button m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/product/new') }}">
{{ $__t('Add') }}
</a>
<a class="btn btn-outline-secondary m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/userfields?entity=products') }}">
{{ $__t('Configure userfields') }}
</a>
<a class="btn btn-outline-secondary m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/stocksettings#productpresets') }}">
{{ $__t('Presets for new products') }}
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100" id="related-links">
<a class="btn btn-primary responsive-button m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/product/new') }}">
{{ $__t('Add') }}
</a>
<a class="btn btn-outline-secondary m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/userfields?entity=products') }}">
{{ $__t('Configure userfields') }}
</a>
<a class="btn btn-outline-secondary m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/stocksettings#productpresets') }}">
{{ $__t('Presets for new products') }}
</a>
</div>
</div>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row collapse d-md-flex"
id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text"
id="search"
class="form-control"
placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Product group') }}</span>
</div>
<select class="custom-control custom-select"
id="product-group-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach($productGroups as $productGroup)
<option value="{{ $productGroup->id }}">{{ $productGroup->name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-12 col-md-6 col-xl-2">
<div class="form-check custom-control custom-checkbox">
<input class="form-check-input custom-control-input"
type="checkbox"
id="show-disabled">
<label class="form-check-label custom-control-label"
for="show-disabled">
{{ $__t('Show disabled') }}
</label>
</div>
</div>
<div class="col-12 col-md-6 col-xl-2">
<div class="form-check custom-control custom-checkbox">
<input class="form-check-input custom-control-input"
type="checkbox"
id="show-only-in-stock">
<label class="form-check-label custom-control-label"
for="show-only-in-stock">
{{ $__t('Show only in-stock products') }}
</label>
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row collapse d-md-flex mb-3" id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text" id="search" class="form-control" placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Product group') }}</span>
</div>
{{-- TODO: Select2: dynamic data: product_groups --}}
<select class="custom-control custom-select" id="product-group-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach ($productGroups as $productGroup)
<option value="{{ $productGroup->id }}">{{ $productGroup->name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-12 col-md-6 col-xl-2">
<div class="form-check custom-control custom-checkbox">
<input class="form-check-input custom-control-input" type="checkbox" id="show-disabled">
<label class="form-check-label custom-control-label" for="show-disabled">
{{ $__t('Show disabled') }}
</label>
</div>
</div>
<div class="col-12 col-md-6 col-xl-2">
<div class="form-check custom-control custom-checkbox">
<input class="form-check-input custom-control-input" type="checkbox" id="show-only-in-stock">
<label class="form-check-label custom-control-label" for="show-only-in-stock">
{{ $__t('Show only in-stock products') }}
</label>
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button" class="btn btn-sm btn-outline-info" href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row">
<div class="col">
<table id="products-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#products-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Name') }}</th>
<th class="@if(!GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) d-none @endif allow-grouping">{{ $__t('Location') }}</th>
<th class="allow-grouping">{{ $__t('Min. stock amount') }}</th>
<th class="">{{ $__t('Default quantity unit purchase') }}</th>
<th class="allow-grouping">{{ $__t('Quantity unit stock') }}</th>
<th class="">{{ $__t('Product group') }}</th>
<th class="@if(!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif allow-grouping">{{ $__t('Default store') }}</th>
<div class="row">
<div class="col">
<table id="products-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#products-table" href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Name') }}</th>
<th class="@if (!GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) d-none @endif allow-grouping">{{ $__t('Location') }}
</th>
<th class="allow-grouping">{{ $__t('Min. stock amount') }}</th>
<th class="">{{ $__t('Default quantity unit purchase') }}</th>
<th class="allow-grouping">{{ $__t('Quantity unit stock') }}</th>
<th class="">{{ $__t('Product group') }}</th>
<th class="@if (!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif allow-grouping">
{{ $__t('Default store') }}</th>
@include('components.userfields_thead', [
'userfields' => $userfields,
])
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
@include('components.userfields_thead', array(
'userfields' => $userfields
))
<div class="modal fade" id="merge-products-modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-center">
<div class="modal-header">
<h4 class="modal-title w-100">{{ $__t('Merge products') }}</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label for="merge-products-keep">{{ $__t('Product to keep') }}&nbsp;<i
class="fas fa-question-circle text-muted" data-toggle="tooltip" data-trigger="hover click"
title="{{ $__t('After merging, this product will be kept') }}"></i>
</label>
<select class="select2 custom-control custom-select" id="merge-products-keep"></select>
</div>
<div class="form-group">
<label for="merge-products-remove">{{ $__t('Product to remove') }}&nbsp;<i
class="fas fa-question-circle text-muted" data-toggle="tooltip" data-trigger="hover click"
title="{{ $__t('After merging, all occurences of this product will be replaced by "Product to keep" (means this product will not exist anymore)') }}"></i>
</label>
<select class="select2 custom-control custom-select" id="merge-products-remove"></select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ $__t('Cancel') }}</button>
<button id="merge-products-save-button" type="button" class="btn btn-primary"
data-dismiss="modal">{{ $__t('OK') }}</button>
</div>
</div>
</div>
</div>
</tr>
</thead>
<tbody class="d-none">
@foreach($products as $product)
<tr class="@if($product->active == 0) text-muted @endif">
<td class="fit-content border-right">
<a class="btn btn-info btn-sm"
href="{{ $U('/product/') }}{{ $product->id }}"
data-toggle="tooltip"
title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger btn-sm product-delete-button"
href="#"
data-product-id="{{ $product->id }}"
data-product-name="{{ $product->name }}"
data-toggle="tooltip"
title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
<div class="dropdown d-inline-block">
<button class="btn btn-sm btn-light text-secondary"
type="button"
data-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="table-inline-menu dropdown-menu dropdown-menu-right">
<a class="dropdown-item"
type="button"
href="{{ $U('/product/new?copy-of=') }}{{ $product->id }}">
<span class="dropdown-item-text">{{ $__t('Copy') }}</span>
</a>
<a class="dropdown-item merge-products-button"
data-product-id="{{ $product->id }}"
type="button"
href="#">
<span class="dropdown-item-text">{{ $__t('Merge') }}</span>
</a>
</div>
</div>
</td>
<td>
{{ $product->name }}
@if(!empty($product->picture_file_name))
<i class="fas fa-image text-muted"
data-toggle="tooltip"
title="{{ $__t('This product has a picture') }}"></i>
@endif
</td>
<td class="@if(!GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) d-none @endif">
@php
$location = FindObjectInArrayByPropertyValue($locations, 'id', $product->location_id);
@endphp
@if($location != null)
{{ $location->name }}
@endif
</td>
<td>
<span class="locale-number locale-number-quantity-amount">{{ $product->min_stock_amount }}</span>
</td>
<td>
{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', $product->qu_id_purchase)->name }}
</td>
<td>
{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', $product->qu_id_stock)->name }}
</td>
<td>
@if(!empty($product->product_group_id)) {{ FindObjectInArrayByPropertyValue($productGroups, 'id', $product->product_group_id)->name }} @endif
</td>
<td class="@if(!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif">
@php
$store = FindObjectInArrayByPropertyValue($shoppingLocations, 'id', $product->shopping_location_id);
@endphp
@if($store != null)
{{ $store->name }}
@endif
</td>
@include('components.userfields_tbody', array(
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $product->id)
))
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<div class="modal fade"
id="merge-products-modal"
tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-center">
<div class="modal-header">
<h4 class="modal-title w-100">{{ $__t('Merge products') }}</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label for="merge-products-keep">{{ $__t('Product to keep') }}&nbsp;<i class="fas fa-question-circle text-muted"
data-toggle="tooltip"
data-trigger="hover click"
title="{{ $__t('After merging, this product will be kept') }}"></i>
</label>
<select class="custom-control custom-select"
id="merge-products-keep">
<option></option>
@foreach($products as $product)
<option value="{{ $product->id }}">{{ $product->name }}</option>
@endforeach
</select>
</div>
<div class="form-group">
<label for="merge-products-remove">{{ $__t('Product to remove') }}&nbsp;<i class="fas fa-question-circle text-muted"
data-toggle="tooltip"
data-trigger="hover click"
title="{{ $__t('After merging, all occurences of this product will be replaced by "Product to keep" (means this product will not exist anymore)') }}"></i>
</label>
<select class="custom-control custom-select"
id="merge-products-remove">
<option></option>
@foreach($products as $product)
<option value="{{ $product->id }}">{{ $product->name }}</option>
@endforeach
</select>
</div>
</div>
<div class="modal-footer">
<button type="button"
class="btn btn-secondary"
data-dismiss="modal">{{ $__t('Cancel') }}</button>
<button id="merge-products-save-button"
type="button"
class="btn btn-primary"
data-dismiss="modal">{{ $__t('OK') }}</button>
</div>
</div>
</div>
</div>
<script>
var userfields = {!! json_encode($userfields) !!};
</script>
@stop

View File

@ -53,62 +53,61 @@
novalidate>
@include('components.productpicker', array(
'products' => $products,
'barcodes' => $barcodes,
'nextInputSelector' => '#display_amount'
'productsQuery' => 'query%5B%5D=active%3D1&order=name%3Acollate%20nocase',
'nextInputSelector' => '#display_amount'
))
@include('components.productamountpicker', array(
'value' => 1,
'additionalHtmlContextHelp' => '<div id="tare-weight-handling-info"
class="text-info font-italic d-none">' . $__t('Tare weight handling enabled - please weigh the whole container, the amount to be posted will be automatically calculcated') . '</div>'
'value' => 1,
'additionalHtmlContextHelp' => '<div id="tare-weight-handling-info"
class="text-info font-italic d-none">' . $__t('Tare weight handling enabled - please weigh the whole container, the amount to be posted will be automatically calculcated') . '</div>'
))
@if(boolval($userSettings['show_purchased_date_on_purchase']))
@include('components.datetimepicker2', array(
'id' => 'purchased_date',
'label' => 'Purchased date',
'format' => 'YYYY-MM-DD',
'initWithNow' => true,
'limitEndToNow' => false,
'limitStartToNow' => false,
'invalidFeedback' => $__t('A purchased date is required'),
'nextInputSelector' => '#best_before_date',
'additionalCssClasses' => 'date-only-datetimepicker2',
'activateNumberPad' => GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_FIELD_NUMBER_PAD
'id' => 'purchased_date',
'label' => 'Purchased date',
'format' => 'YYYY-MM-DD',
'initWithNow' => true,
'limitEndToNow' => false,
'limitStartToNow' => false,
'invalidFeedback' => $__t('A purchased date is required'),
'nextInputSelector' => '#best_before_date',
'additionalCssClasses' => 'date-only-datetimepicker2',
'activateNumberPad' => GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_FIELD_NUMBER_PAD
))
@endif
@if(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
@include('components.datetimepicker', array(
'id' => 'best_before_date',
'label' => 'Due date',
'format' => 'YYYY-MM-DD',
'initWithNow' => false,
'limitEndToNow' => false,
'limitStartToNow' => false,
'invalidFeedback' => $__t('A due date is required'),
'nextInputSelector' => '#price',
'additionalCssClasses' => 'date-only-datetimepicker',
'shortcutValue' => '2999-12-31',
'shortcutLabel' => 'Never overdue',
'earlierThanInfoLimit' => date('Y-m-d'),
'earlierThanInfoText' => $__t('The given date is earlier than today, are you sure?'),
'activateNumberPad' => GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_FIELD_NUMBER_PAD
'id' => 'best_before_date',
'label' => 'Due date',
'format' => 'YYYY-MM-DD',
'initWithNow' => false,
'limitEndToNow' => false,
'limitStartToNow' => false,
'invalidFeedback' => $__t('A due date is required'),
'nextInputSelector' => '#price',
'additionalCssClasses' => 'date-only-datetimepicker',
'shortcutValue' => '2999-12-31',
'shortcutLabel' => 'Never overdue',
'earlierThanInfoLimit' => date('Y-m-d'),
'earlierThanInfoText' => $__t('The given date is earlier than today, are you sure?'),
'activateNumberPad' => GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_FIELD_NUMBER_PAD
))
@endif
@if(GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
@include('components.numberpicker', array(
'id' => 'price',
'label' => 'Price',
'min' => '0.' . str_repeat('0', $userSettings['stock_decimal_places_prices']),
'decimals' => $userSettings['stock_decimal_places_prices'],
'value' => '',
'contextInfoId' => 'price-hint',
'isRequired' => false,
'additionalGroupCssClasses' => 'mb-1',
'additionalCssClasses' => 'locale-number-input locale-number-currency'
'id' => 'price',
'label' => 'Price',
'min' => '0.' . str_repeat('0', $userSettings['stock_decimal_places_prices']),
'decimals' => $userSettings['stock_decimal_places_prices'],
'value' => '',
'contextInfoId' => 'price-hint',
'isRequired' => false,
'additionalGroupCssClasses' => 'mb-1',
'additionalCssClasses' => 'locale-number-input locale-number-currency'
))
<div class="custom-control custom-radio custom-control-inline mt-n2 mb-3">
@ -131,8 +130,8 @@
for="price-type-total-price">{{ $__t('Total price') }}</label>
</div>
@include('components.shoppinglocationpicker', array(
'label' => 'Store',
'shoppinglocations' => $shoppinglocations
'label' => 'Store',
'shoppinglocations' => $shoppinglocations
))
@else
<input type="hidden"
@ -143,8 +142,8 @@
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
@include('components.locationpicker', array(
'locations' => $locations,
'isRequired' => false
'locations' => $locations,
'isRequired' => false
))
@endif

View File

@ -1,122 +1,116 @@
@extends('layout.default')
@if($mode == 'edit')
@section('title', $__t('Edit QU conversion'))
@if ($mode == 'edit')
@section('title', $__t('Edit QU conversion'))
@else
@section('title', $__t('Create QU conversion'))
@section('title', $__t('Create QU conversion'))
@endif
@section('viewJsName', 'quantityunitconversionform')
@section('content')
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<h2>
@if($product != null)
<span class="text-muted small">{{ $__t('Override for product') }} <strong>{{ $product->name }}</strong></span>
@else
<span class="text-muted small">{{ $__t('Default for QU') }} <strong>{{ $defaultQuUnit->name }}</strong></span>
@endif
</h2>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<h2>
@if ($product != null)
<span class="text-muted small">{{ $__t('Override for product') }}
<strong>{{ $product->name }}</strong></span>
@else
<span class="text-muted small">{{ $__t('Default for QU') }}
<strong>{{ $defaultQuUnit->name }}</strong></span>
@endif
</h2>
</div>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row">
<div class="col-lg-6 col-12">
<div class="row">
<div class="col-lg-6 col-12">
<script>
Grocy.EditMode = '{{ $mode }}';
</script>
<script>
Grocy.EditMode = '{{ $mode }}';
</script>
@if($mode == 'edit')
<script>
Grocy.EditObjectId = {{ $quConversion->id }};
</script>
@endif
@if ($mode == 'edit')
<script>
Grocy.EditObjectId = {{ $quConversion->id }};
</script>
@endif
<form id="quconversion-form"
novalidate>
<form id="quconversion-form" novalidate>
@if($product != null)
<input type="hidden"
name="product_id"
value="{{ $product->id }}">
@endif
@if ($product != null)
<input type="hidden" name="product_id" value="{{ $product->id }}">
@endif
<div class="form-group">
<label for="from_qu_id">{{ $__t('Quantity unit from') }}</label>
<select required
class="custom-control custom-select input-group-qu"
id="from_qu_id"
name="from_qu_id">
<option></option>
@foreach($quantityunits as $quantityunit)
<option @if(($product
!=null
&&
$quantityunit->id == $product->qu_id_stock) || ($defaultQuUnit != null && $quantityunit->id == $defaultQuUnit->id))) selected="selected" @endif value="{{ $quantityunit->id }}" data-plural-form="{{ $quantityunit->name_plural }}">{{ $quantityunit->name }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('A quantity unit is required') }}</div>
</div>
<div class="form-group">
<label for="from_qu_id">{{ $__t('Quantity unit from') }}</label>
{{-- TODO: Select2: dynamic data: quantity_units --}}
<select required class="custom-control custom-select input-group-qu" id="from_qu_id" name="from_qu_id">
<option></option>
@foreach ($quantityunits as $quantityunit)
<option @if (($product != null && $quantityunit->id == $product->qu_id_stock) || ($defaultQuUnit != null && $quantityunit->id == $defaultQuUnit->id)) ) selected="selected" @endif
value="{{ $quantityunit->id }}" data-plural-form="{{ $quantityunit->name_plural }}">
{{ $quantityunit->name }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('A quantity unit is required') }}</div>
</div>
<div class="form-group">
<label for="to_qu_id">{{ $__t('Quantity unit to') }}</label>
<select required
class="custom-control custom-select input-group-qu"
id="to_qu_id"
name="to_qu_id">
<option></option>
@foreach($quantityunits as $quantityunit)
<option @if($mode=='edit'
&&
$quantityunit->id == $quConversion->to_qu_id) selected="selected" @endif value="{{ $quantityunit->id }}" data-plural-form="{{ $quantityunit->name_plural }}">{{ $quantityunit->name }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('A quantity unit is required') }}</div>
</div>
<div class="form-group">
<label for="to_qu_id">{{ $__t('Quantity unit to') }}</label>
{{-- TODO: Select2: dynamic data: quantity_units --}}
<select required class="custom-control custom-select input-group-qu" id="to_qu_id" name="to_qu_id">
<option></option>
@foreach ($quantityunits as $quantityunit)
<option @if ($mode == 'edit' && $quantityunit->id == $quConversion->to_qu_id) selected="selected" @endif
value="{{ $quantityunit->id }}" data-plural-form="{{ $quantityunit->name_plural }}">
{{ $quantityunit->name }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('A quantity unit is required') }}</div>
</div>
@php if($mode == 'edit') { $value = $quConversion->factor; } else { $value = 1; } @endphp
@include('components.numberpicker', array(
'id' => 'factor',
'label' => 'Factor',
'min' => $DEFAULT_MIN_AMOUNT,
'decimals' => $userSettings['stock_decimal_places_amounts'],
'value' => $value,
'additionalHtmlElements' => '<p id="qu-conversion-info"
class="form-text text-info d-none"></p>',
'additionalCssClasses' => 'input-group-qu locale-number-input locale-number-quantity-amount'
))
@php
if ($mode == 'edit') {
$value = $quConversion->factor;
} else {
$value = 1;
}
@endphp
@include('components.numberpicker', [
'id' => 'factor',
'label' => 'Factor',
'min' => $DEFAULT_MIN_AMOUNT,
'decimals' => $userSettings['stock_decimal_places_amounts'],
'value' => $value,
'additionalHtmlElements' => '<p id="qu-conversion-info"
class="form-text text-info d-none"></p>',
'additionalCssClasses' => 'input-group-qu locale-number-input locale-number-quantity-amount',
])
<div class="form-group @if($mode == 'edit') d-none @endif">
<div class="custom-control custom-checkbox">
<input checked
class="form-check-input custom-control-input"
type="checkbox"
id="create_inverse"
name="create_inverse:skip"
value="1">
<label class="form-check-label custom-control-label"
for="create_inverse">{{ $__t('Create inverse QU conversion') }}</label>
</div>
<span id="qu-conversion-inverse-info"
class="form-text text-info d-none"></span>
</div>
<div class="form-group @if ($mode == 'edit') d-none @endif">
<div class="custom-control custom-checkbox">
<input checked class="form-check-input custom-control-input" type="checkbox" id="create_inverse"
name="create_inverse:skip" value="1">
<label class="form-check-label custom-control-label"
for="create_inverse">{{ $__t('Create inverse QU conversion') }}</label>
</div>
<span id="qu-conversion-inverse-info" class="form-text text-info d-none"></span>
</div>
@include('components.userfieldsform', array(
'userfields' => $userfields,
'entity' => 'quantity_unit_conversions'
))
@include('components.userfieldsform', [
'userfields' => $userfields,
'entity' => 'quantity_unit_conversions',
])
<button id="save-quconversion-button"
class="btn btn-success">{{ $__t('Save') }}</button>
<button id="save-quconversion-button" class="btn btn-success">{{ $__t('Save') }}</button>
</form>
</div>
</div>
</form>
</div>
</div>
@stop

View File

@ -1,170 +1,161 @@
@extends('layout.default')
@if($mode == 'edit')
@section('title', $__t('Edit quantity unit'))
@if ($mode == 'edit')
@section('title', $__t('Edit quantity unit'))
@else
@section('title', $__t('Create quantity unit'))
@section('title', $__t('Create quantity unit'))
@endif
@section('viewJsName', 'quantityunitform')
@section('content')
<div class="row">
<div class="col">
<h2 class="title">@yield('title')</h2>
</div>
</div>
<div class="row">
<div class="col">
<h2 class="title">@yield('title')</h2>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row">
<div class="col-lg-6 col-12">
<script>
Grocy.EditMode = '{{ $mode }}';
</script>
<div class="row">
<div class="col-lg-6 col-12">
<script>
Grocy.EditMode = '{{ $mode }}';
</script>
@if($mode == 'edit')
<script>
Grocy.EditObjectId = {{ $quantityUnit->id }};
</script>
@endif
@if ($mode == 'edit')
<script>
Grocy.EditObjectId = {{ $quantityUnit->id }};
</script>
@endif
<form id="quantityunit-form"
novalidate>
<form id="quantityunit-form" novalidate>
<div class="form-group">
<label for="name">{{ $__t('Name') }} <span class="small text-muted">{{ $__t('in singular form') }}</span></label>
<input type="text"
class="form-control"
required
id="name"
name="name"
value="@if($mode == 'edit'){{ $quantityUnit->name }}@endif">
<div class="invalid-feedback">{{ $__t('A name is required') }}</div>
</div>
<div class="form-group">
<label for="name">{{ $__t('Name') }} <span
class="small text-muted">{{ $__t('in singular form') }}</span></label>
<input type="text" class="form-control" required id="name" name="name"
value="@if ($mode == 'edit') {{ $quantityUnit->name }} @endif">
<div class="invalid-feedback">{{ $__t('A name is required') }}</div>
</div>
<div class="form-group">
<label for="name_plural">{{ $__t('Name') }} <span class="small text-muted">{{ $__t('in plural form') }}</span></label>
<input type="text"
class="form-control"
id="name_plural"
name="name_plural"
value="@if($mode == 'edit'){{ $quantityUnit->name_plural }}@endif">
</div>
<div class="form-group">
<label for="name_plural">{{ $__t('Name') }} <span
class="small text-muted">{{ $__t('in plural form') }}</span></label>
<input type="text" class="form-control" id="name_plural" name="name_plural"
value="@if ($mode == 'edit') {{ $quantityUnit->name_plural }} @endif">
</div>
@if($pluralCount > 2)
<div class="form-group">
<label for="plural_forms">
{{ $__t('Plural forms') }}<br>
<span class="small text-muted">
{{ $__t('One plural form per line, the current language requires') }}:<br>
{{ $__t('Plural count') }}: {{ $pluralCount }}<br>
{{ $__t('Plural rule') }}: {{ $pluralRule }}
</span>
</label>
<textarea class="form-control"
rows="3"
id="plural_forms"
name="plural_forms">@if($mode == 'edit'){{ $quantityUnit->plural_forms }}@endif</textarea>
</div>
@endif
@if ($pluralCount > 2)
<div class="form-group">
<label for="plural_forms">
{{ $__t('Plural forms') }}<br>
<span class="small text-muted">
{{ $__t('One plural form per line, the current language requires') }}:<br>
{{ $__t('Plural count') }}: {{ $pluralCount }}<br>
{{ $__t('Plural rule') }}: {{ $pluralRule }}
</span>
</label>
<textarea class="form-control" rows="3" id="plural_forms" name="plural_forms">
@if ($mode == 'edit')
{{ $quantityUnit->plural_forms }}
@endif
</textarea>
</div>
@endif
<div class="form-group">
<label for="description">{{ $__t('Description') }}</label>
<textarea class="form-control"
rows="2"
id="description"
name="description">@if($mode == 'edit'){{ $quantityUnit->description }}@endif</textarea>
</div>
<div class="form-group">
<label for="description">{{ $__t('Description') }}</label>
<textarea class="form-control" rows="2" id="description" name="description">
@if ($mode == 'edit')
{{ $quantityUnit->description }}
@endif
</textarea>
</div>
@include('components.userfieldsform', array(
'userfields' => $userfields,
'entity' => 'quantity_units'
))
@include('components.userfieldsform', [
'userfields' => $userfields,
'entity' => 'quantity_units',
])
<small class="my-2 form-text text-muted @if($mode == 'edit') d-none @endif">{{ $__t('Save & continue to add conversions') }}</small>
<small
class="my-2 form-text text-muted @if ($mode == 'edit') d-none @endif">{{ $__t('Save & continue to add conversions') }}</small>
<button class="save-quantityunit-button btn btn-success mb-2"
data-location="continue">{{ $__t('Save & continue') }}</button>
<button class="save-quantityunit-button btn btn-info mb-2"
data-location="return">{{ $__t('Save & return to quantity units') }}</button>
<button class="save-quantityunit-button btn btn-success mb-2"
data-location="continue">{{ $__t('Save & continue') }}</button>
<button class="save-quantityunit-button btn btn-info mb-2"
data-location="return">{{ $__t('Save & return to quantity units') }}</button>
@if(intval($pluralCount) > 2)
<button id="test-quantityunit-plural-forms-button"
class="btn btn-secondary">{{ $__t('Test plural forms') }}</button>
@endif
@if (intval($pluralCount) > 2)
<button id="test-quantityunit-plural-forms-button"
class="btn btn-secondary">{{ $__t('Test plural forms') }}</button>
@endif
</form>
</div>
</form>
</div>
<div class="col-lg-6 col-12 @if($mode == 'create') d-none @endif">
<div class="row">
<div class="col">
<div class="title-related-links">
<h4>
{{ $__t('Default conversions') }}
<small id="qu-conversion-headline-info"
class="text-muted font-italic"></small>
</h4>
<button class="btn btn-outline-dark d-md-none mt-2 float-right order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
id="related-links">
<a class="btn btn-outline-primary btn-sm m-1 mt-md-0 mb-md-0 float-right show-as-dialog-link"
href="{{ $U('/quantityunitconversion/new?embedded&qu-unit=' . $quantityUnit->id ) }}">
{{ $__t('Add') }}
</a>
</div>
</div>
<div class="col-lg-6 col-12 @if ($mode == 'create') d-none @endif">
<div class="row">
<div class="col">
<div class="title-related-links">
<h4>
{{ $__t('Default conversions') }}
<small id="qu-conversion-headline-info" class="text-muted font-italic"></small>
</h4>
<button class="btn btn-outline-dark d-md-none mt-2 float-right order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100" id="related-links">
<a class="btn btn-outline-primary btn-sm m-1 mt-md-0 mb-md-0 float-right show-as-dialog-link"
href="{{ $U('/quantityunitconversion/new?embedded&qu-unit=' . $quantityUnit->id) }}">
{{ $__t('Add') }}
</a>
</div>
</div>
<table id="qu-conversions-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#qu-conversions-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Factor') }}</th>
<th>{{ $__t('Unit') }}</th>
</tr>
</thead>
<tbody class="d-none">
@if($mode == "edit")
@foreach($defaultQuConversions as $defaultQuConversion)
<tr>
<td class="fit-content border-right">
<a class="btn btn-sm btn-info show-as-dialog-link"
href="{{ $U('/quantityunitconversion/' . $defaultQuConversion->id . '?embedded&qu-unit=' . $quantityUnit->id ) }}"
data-qu-conversion-id="{{ $defaultQuConversion->id }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-sm btn-danger qu-conversion-delete-button"
href="#"
data-qu-conversion-id="{{ $defaultQuConversion->id }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
<span class="locale-number locale-number-quantity-amount">{{ $defaultQuConversion->factor }}</span>
</td>
<td>
{{ FindObjectInArrayByPropertyValue($quantityUnits, 'id', $defaultQuConversion->to_qu_id)->name }}
</td>
</tr>
@endforeach
@endif
</tbody>
</table>
</div>
</div>
</div>
</div>
{{-- TODO: DataTables: dynamic data: quantity_unit_conversions --}}
<table id="qu-conversions-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#qu-conversions-table" href="#"><i
class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Factor') }}</th>
<th>{{ $__t('Unit') }}</th>
</tr>
</thead>
<tbody class="d-none">
@if ($mode == 'edit')
@foreach ($defaultQuConversions as $defaultQuConversion)
<tr>
<td class="fit-content border-right">
<a class="btn btn-sm btn-info show-as-dialog-link"
href="{{ $U('/quantityunitconversion/' . $defaultQuConversion->id . '?embedded&qu-unit=' . $quantityUnit->id) }}"
data-qu-conversion-id="{{ $defaultQuConversion->id }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-sm btn-danger qu-conversion-delete-button" href="#"
data-qu-conversion-id="{{ $defaultQuConversion->id }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
<span
class="locale-number locale-number-quantity-amount">{{ $defaultQuConversion->factor }}</span>
</td>
<td>
{{ FindObjectInArrayByPropertyValue($quantityUnits, 'id', $defaultQuConversion->to_qu_id)->name }}
</td>
</tr>
@endforeach
@endif
</tbody>
</table>
</div>
</div>
</div>
</div>
@stop

View File

@ -5,51 +5,47 @@
@section('viewJsName', 'quantityunitpluraltesting')
@push('pageStyles')
<link href="{{ $U('/node_modules/animate.css/animate.min.css?v=', true) }}{{ $version }}"
rel="stylesheet">
<link href="{{ $U('/node_modules/animate.css/animate.min.css?v=', true) }}{{ $version }}" rel="stylesheet">
@endpush
@section('content')
<div class="row">
<div class="col">
<h2 class="title">@yield('title')</h2>
</div>
</div>
<div class="row">
<div class="col">
<h2 class="title">@yield('title')</h2>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row">
<div class="col-lg-6 col-12">
<form id="quantityunitpluraltesting-form"
novalidate>
<div class="row">
<div class="col-lg-6 col-12">
<form id="quantityunitpluraltesting-form" novalidate>
<div class="form-group">
<label for="qu_id">{{ $__t('Quantity unit') }}</label>
<select class="custom-control custom-select"
id="qu_id"
name="qu_id">
<option></option>
@foreach($quantityUnits as $quantityUnit)
<option value="{{ $quantityUnit->id }}"
data-singular-form="{{ $quantityUnit->name }}"
data-plural-form="{{ $quantityUnit->name_plural }}">{{ $quantityUnit->name }}</option>
@endforeach
</select>
</div>
<div class="form-group">
<label for="qu_id">{{ $__t('Quantity unit') }}</label>
{{-- TODO: Select2: dynamic data: quantity_units --}}
<select class="custom-control custom-select" id="qu_id" name="qu_id">
<option></option>
@foreach ($quantityUnits as $quantityUnit)
<option value="{{ $quantityUnit->id }}" data-singular-form="{{ $quantityUnit->name }}"
data-plural-form="{{ $quantityUnit->name_plural }}">{{ $quantityUnit->name }}</option>
@endforeach
</select>
</div>
@include('components.numberpicker', array(
'id' => 'amount',
'label' => 'Amount',
'min' => 0,
'decimals' => $userSettings['stock_decimal_places_amounts'],
'isRequired' => false,
'value' => 1,
'additionalCssClasses' => 'locale-number-input locale-number-quantity-amount'
))
@include('components.numberpicker', [
'id' => 'amount',
'label' => 'Amount',
'min' => 0,
'decimals' => $userSettings['stock_decimal_places_amounts'],
'isRequired' => false,
'value' => 1,
'additionalCssClasses' => 'locale-number-input locale-number-quantity-amount',
])
</form>
</form>
<h2><strong>{{ $__t('Result') }}:</strong> <span id="result"></span></h2>
</div>
</div>
<h2><strong>{{ $__t('Result') }}:</strong> <span id="result"></span></h2>
</div>
</div>
@stop

View File

@ -5,121 +5,107 @@
@section('viewJsName', 'quantityunits')
@section('content')
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
id="related-links">
<a class="btn btn-primary responsive-button m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/quantityunit/new') }}">
{{ $__t('Add') }}
</a>
<a class="btn btn-outline-secondary m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/userfields?entity=quantity_units') }}">
{{ $__t('Configure userfields') }}
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100" id="related-links">
<a class="btn btn-primary responsive-button m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/quantityunit/new') }}">
{{ $__t('Add') }}
</a>
<a class="btn btn-outline-secondary m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/userfields?entity=quantity_units') }}">
{{ $__t('Configure userfields') }}
</a>
</div>
</div>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row collapse d-md-flex"
id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text"
id="search"
class="form-control"
placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row collapse d-md-flex" id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text" id="search" class="form-control" placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button" class="btn btn-sm btn-outline-info" href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row">
<div class="col">
<table id="quantityunits-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#quantityunits-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Name') }}</th>
<th>{{ $__t('Description') }}</th>
<div class="row">
<div class="col">
{{-- TODO: DataTables: dynamic data: quantity_units --}}
<table id="quantityunits-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#quantityunits-table" href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Name') }}</th>
<th>{{ $__t('Description') }}</th>
@include('components.userfields_thead', array(
'userfields' => $userfields
))
</tr>
</thead>
<tbody class="d-none">
@foreach($quantityunits as $quantityunit)
<tr>
<td class="fit-content border-right">
<a class="btn btn-info btn-sm"
href="{{ $U('/quantityunit/') }}{{ $quantityunit->id }}"
data-toggle="tooltip"
title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger btn-sm quantityunit-delete-button"
href="#"
data-quantityunit-id="{{ $quantityunit->id }}"
data-quantityunit-name="{{ $quantityunit->name }}"
data-toggle="tooltip"
title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
{{ $quantityunit->name }}
</td>
<td>
{{ $quantityunit->description }}
</td>
@include('components.userfields_thead', [
'userfields' => $userfields,
])
</tr>
</thead>
<tbody class="d-none">
@foreach ($quantityunits as $quantityunit)
<tr>
<td class="fit-content border-right">
<a class="btn btn-info btn-sm" href="{{ $U('/quantityunit/') }}{{ $quantityunit->id }}"
data-toggle="tooltip" title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger btn-sm quantityunit-delete-button" href="#"
data-quantityunit-id="{{ $quantityunit->id }}"
data-quantityunit-name="{{ $quantityunit->name }}" data-toggle="tooltip"
title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
{{ $quantityunit->name }}
</td>
<td>
{{ $quantityunit->description }}
</td>
@include('components.userfields_tbody', array(
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $quantityunit->id)
))
@include('components.userfields_tbody', [
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue(
$userfieldValues,
'object_id',
$quantityunit->id
),
])
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@stop

View File

@ -1,414 +1,388 @@
@extends('layout.default')
@if($mode == 'edit')
@section('title', $__t('Edit recipe'))
@if ($mode == 'edit')
@section('title', $__t('Edit recipe'))
@else
@section('title', $__t('Create recipe'))
@section('title', $__t('Create recipe'))
@endif
@section('viewJsName', 'recipeform')
@section('content')
<div class="row">
<div class="col">
<h2 class="title">@yield('title')</h2>
<div class="row">
<div class="col">
<h2 class="title">@yield('title')</h2>
<script>
Grocy.EditMode = '{{ $mode }}';
Grocy.QuantityUnits = {!! json_encode($quantityunits) !!};
Grocy.QuantityUnitConversionsResolved = {!! json_encode($quantityUnitConversionsResolved) !!};
</script>
<script>
Grocy.EditMode = '{{ $mode }}';
Grocy.QuantityUnits = {!! json_encode($quantityunits) !!};
Grocy.QuantityUnitConversionsResolved = {!! json_encode($quantityUnitConversionsResolved) !!};
</script>
@if($mode == 'edit')
<script>
Grocy.EditObjectId = {{ $recipe->id }};
</script>
@if ($mode == 'edit')
<script>
Grocy.EditObjectId = {{ $recipe->id }};
</script>
@if(!empty($recipe->picture_file_name))
<script>
Grocy.RecipePictureFileName = '{{ $recipe->picture_file_name }}';
</script>
@endif
@endif
</div>
</div>
@if (!empty($recipe->picture_file_name))
<script>
Grocy.RecipePictureFileName = '{{ $recipe->picture_file_name }}';
</script>
@endif
@endif
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row">
<div class="col-12 col-md-7 pb-3">
<form id="recipe-form"
novalidate>
<div class="row">
<div class="col-12 col-md-7 pb-3">
<form id="recipe-form" novalidate>
<div class="form-group">
<label for="name">{{ $__t('Name') }}</label>
<input type="text"
class="form-control"
required
id="name"
name="name"
value="@if($mode == 'edit'){{ $recipe->name }}@endif">
<div class="invalid-feedback">{{ $__t('A name is required') }}</div>
</div>
<div class="form-group">
<label for="name">{{ $__t('Name') }}</label>
<input type="text" class="form-control" required id="name" name="name"
value="@if ($mode == 'edit') {{ $recipe->name }} @endif">
<div class="invalid-feedback">{{ $__t('A name is required') }}</div>
</div>
@php if($mode == 'edit') { $value = $recipe->base_servings; } else { $value = 1; } @endphp
@include('components.numberpicker', array(
'id' => 'base_servings',
'label' => 'Servings',
'min' => $DEFAULT_MIN_AMOUNT,
'decimals' => $userSettings['stock_decimal_places_amounts'],
'value' => $value,
'hint' => $__t('The ingredients listed here result in this amount of servings'),
'additionalCssClasses' => 'locale-number-input locale-number-quantity-amount'
))
@php
if ($mode == 'edit') {
$value = $recipe->base_servings;
} else {
$value = 1;
}
@endphp
@include('components.numberpicker', [
'id' => 'base_servings',
'label' => 'Servings',
'min' => $DEFAULT_MIN_AMOUNT,
'decimals' => $userSettings['stock_decimal_places_amounts'],
'value' => $value,
'hint' => $__t('The ingredients listed here result in this amount of servings'),
'additionalCssClasses' => 'locale-number-input locale-number-quantity-amount',
])
<div class="form-group">
<div class="custom-control custom-checkbox">
<input @if($mode=='edit'
&&
$recipe->not_check_shoppinglist == 1) checked @endif class="form-check-input custom-control-input" type="checkbox" id="not_check_shoppinglist" name="not_check_shoppinglist" value="1">
<label class="form-check-label custom-control-label"
for="not_check_shoppinglist">
{{ $__t('Do not check against the shopping list when adding missing items to it') }}&nbsp;
<i class="fas fa-question-circle text-muted"
data-toggle="tooltip"
data-trigger="hover click"
title="{{ $__t('By default the amount to be added to the shopping list is "needed amount - stock amount - shopping list amount" - when this is enabled, it is only checked against the stock amount, not against what is already on the shopping list') }}"></i>
</label>
</div>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input @if ($mode == 'edit' && $recipe->not_check_shoppinglist == 1) checked @endif
class="form-check-input custom-control-input" type="checkbox" id="not_check_shoppinglist"
name="not_check_shoppinglist" value="1">
<label class="form-check-label custom-control-label" for="not_check_shoppinglist">
{{ $__t('Do not check against the shopping list when adding missing items to it') }}&nbsp;
<i class="fas fa-question-circle text-muted" data-toggle="tooltip" data-trigger="hover click"
title="{{ $__t('By default the amount to be added to the shopping list is "needed amount - stock amount - shopping list amount" - when this is enabled, it is only checked against the stock amount, not against what is already on the shopping list') }}"></i>
</label>
</div>
</div>
@include('components.productpicker', array(
'products' => $products,
'isRequired' => false,
'label' => 'Produces product',
'prefillById' => $mode == 'edit' ? $recipe->product_id : '',
'hint' => $__t('When a product is selected, one unit (per serving in stock quantity unit) will be added to stock on consuming this recipe'),
'disallowAllProductWorkflows' => true,
))
@include('components.productpicker', [
'productsQuery' => 'order=name%3Acollate%20nocase',
'isRequired' => false,
'label' => 'Produces product',
'prefillById' => $mode == 'edit' ? $recipe->product_id : '',
'hint' => $__t(
'When a product is selected, one unit (per serving in stock quantity unit) will be added to stock on consuming this recipe'
),
'disallowAllProductWorkflows' => true,
])
@include('components.userfieldsform', array(
'userfields' => $userfields,
'entity' => 'recipes'
))
@include('components.userfieldsform', [
'userfields' => $userfields,
'entity' => 'recipes',
])
<div class="form-group">
<label for="description">{{ $__t('Preparation') }}</label>
<textarea id="description"
class="form-control wysiwyg-editor"
name="description">@if($mode == 'edit'){{ $recipe->description }}@endif</textarea>
</div>
<div class="form-group">
<label for="description">{{ $__t('Preparation') }}</label>
<textarea id="description" class="form-control wysiwyg-editor" name="description">
@if ($mode == 'edit')
{{ $recipe->description }}
@endif
</textarea>
</div>
<small class="my-2 form-text text-muted @if($mode == 'edit') d-none @endif">{{ $__t('Save & continue to add ingredients and included recipes') }}</small>
<small
class="my-2 form-text text-muted @if ($mode == 'edit') d-none @endif">{{ $__t('Save & continue to add ingredients and included recipes') }}</small>
<button class="save-recipe btn btn-success mb-2"
data-location="continue">{{ $__t('Save & continue') }}</button>
<button class="save-recipe btn btn-info mb-2"
data-location="return">{{ $__t('Save & return to recipes') }}</button>
<button class="save-recipe btn btn-success mb-2"
data-location="continue">{{ $__t('Save & continue') }}</button>
<button class="save-recipe btn btn-info mb-2"
data-location="return">{{ $__t('Save & return to recipes') }}</button>
</form>
</div>
</form>
</div>
<div class="col-12 col-md-5 pb-3 @if($mode == 'create') d-none @endif">
<div class="row">
<div class="col">
<div class="title-related-links">
<h4>
{{ $__t('Ingredients list') }}
</h4>
<button class="btn btn-outline-dark d-md-none mt-2 float-right order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
id="related-links">
<a id="recipe-pos-add-button"
class="btn btn-outline-primary btn-sm recipe-pos-add-button m-1 mt-md-0 mb-md-0 float-right"
type="button"
href="#">
{{ $__t('Add') }}
</a>
</div>
</div>
<div class="col-12 col-md-5 pb-3 @if ($mode == 'create') d-none @endif">
<div class="row">
<div class="col">
<div class="title-related-links">
<h4>
{{ $__t('Ingredients list') }}
</h4>
<button class="btn btn-outline-dark d-md-none mt-2 float-right order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100" id="related-links">
<a id="recipe-pos-add-button"
class="btn btn-outline-primary btn-sm recipe-pos-add-button m-1 mt-md-0 mb-md-0 float-right"
type="button" href="#">
{{ $__t('Add') }}
</a>
</div>
</div>
<table id="recipes-pos-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#recipes-pos-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Product') }}</th>
<th>{{ $__t('Amount') }}</th>
<th class="fit-content">{{ $__t('Note') }}</th>
<th class="allow-grouping">{{ $__t('Ingredient group') }}</th>
</tr>
</thead>
<tbody class="d-none">
@if($mode == "edit")
@foreach($recipePositions as $recipePosition)
<tr>
<td class="fit-content border-right">
<a class="btn btn-sm btn-info recipe-pos-edit-button"
type="button"
href="#"
data-recipe-pos-id="{{ $recipePosition->id }}"
data-product-id="{{ $recipePosition->product_id }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-sm btn-danger recipe-pos-delete-button"
href="#"
data-recipe-pos-id="{{ $recipePosition->id }}"
data-recipe-pos-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $recipePosition->product_id)->name }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
{{ FindObjectInArrayByPropertyValue($products, 'id', $recipePosition->product_id)->name }}
</td>
<td>
@php
// The amount can't be non-numeric when using the frontend,
// but some users decide to edit the database manually and
// enter something like "4 or 5" in the amount column (brilliant)
// => So at least don't crash this view by just assuming 0 if that's the case
if (!is_numeric($recipePosition->amount))
{
$recipePosition->amount = 0;
}
$product = FindObjectInArrayByPropertyValue($products, 'id', $recipePosition->product_id);
$productQuConversions = FindAllObjectsInArrayByPropertyValue($quantityUnitConversionsResolved, 'product_id', $product->id);
$productQuConversions = FindAllObjectsInArrayByPropertyValue($productQuConversions, 'from_qu_id', $product->qu_id_stock);
$productQuConversion = FindObjectInArrayByPropertyValue($productQuConversions, 'to_qu_id', $recipePosition->qu_id);
if ($productQuConversion && $recipePosition->only_check_single_unit_in_stock == 0)
{
$recipePosition->amount = $recipePosition->amount * $productQuConversion->factor;
}
@endphp
@if(!empty($recipePosition->variable_amount))
{{ $recipePosition->variable_amount }}
@else
<span class="locale-number locale-number-quantity-amount">@if($recipePosition->amount == round($recipePosition->amount)){{ round($recipePosition->amount) }}@else{{ $recipePosition->amount }}@endif</span>
@endif
{{ $__n($recipePosition->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', $recipePosition->qu_id)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', $recipePosition->qu_id)->name_plural, true) }}
{{-- TODO: DataTables: dynamic data: recipes_pos --}}
<table id="recipes-pos-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#recipes-pos-table" href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Product') }}</th>
<th>{{ $__t('Amount') }}</th>
<th class="fit-content">{{ $__t('Note') }}</th>
<th class="allow-grouping">{{ $__t('Ingredient group') }}</th>
</tr>
</thead>
<tbody class="d-none">
@if ($mode == 'edit')
@foreach ($recipePositions as $recipePosition)
<tr>
<td class="fit-content border-right">
<a class="btn btn-sm btn-info recipe-pos-edit-button" type="button" href="#"
data-recipe-pos-id="{{ $recipePosition->id }}"
data-product-id="{{ $recipePosition->product_id }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-sm btn-danger recipe-pos-delete-button" href="#"
data-recipe-pos-id="{{ $recipePosition->id }}"
data-recipe-pos-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $recipePosition->product_id)->name }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
{{ FindObjectInArrayByPropertyValue($products, 'id', $recipePosition->product_id)->name }}
</td>
<td>
@php
// The amount can't be non-numeric when using the frontend,
// but some users decide to edit the database manually and
// enter something like "4 or 5" in the amount column (brilliant)
// => So at least don't crash this view by just assuming 0 if that's the case
if (!is_numeric($recipePosition->amount)) {
$recipePosition->amount = 0;
}
$product = FindObjectInArrayByPropertyValue($products, 'id', $recipePosition->product_id);
$productQuConversions = FindAllObjectsInArrayByPropertyValue($quantityUnitConversionsResolved, 'product_id', $product->id);
$productQuConversions = FindAllObjectsInArrayByPropertyValue($productQuConversions, 'from_qu_id', $product->qu_id_stock);
$productQuConversion = FindObjectInArrayByPropertyValue($productQuConversions, 'to_qu_id', $recipePosition->qu_id);
if ($productQuConversion && $recipePosition->only_check_single_unit_in_stock == 0) {
$recipePosition->amount = $recipePosition->amount * $productQuConversion->factor;
}
@endphp
@if (!empty($recipePosition->variable_amount))
{{ $recipePosition->variable_amount }}
@else
<span class="locale-number locale-number-quantity-amount">
@if ($recipePosition->amount == round($recipePosition->amount))
{{ round($recipePosition->amount) }}@else{{ $recipePosition->amount }}
@endif
</span>
@endif
{{ $__n($recipePosition->amount,FindObjectInArrayByPropertyValue($quantityunits, 'id', $recipePosition->qu_id)->name,FindObjectInArrayByPropertyValue($quantityunits, 'id', $recipePosition->qu_id)->name_plural,true) }}
@if(!empty($recipePosition->variable_amount))
<div class="small text-muted font-italic">{{ $__t('Variable amount') }}</div>
@endif
</td>
<td class="fit-content">
<a class="btn btn-sm btn-info recipe-pos-show-note-button @if(empty($recipePosition->note)) disabled @endif"
href="#"
data-toggle="tooltip"
data-placement="top"
title="{{ $__t('Show notes') }}"
data-recipe-pos-note="{{ $recipePosition->note }}">
<i class="fas fa-eye"></i>
</a>
</td>
<td>
{{ $recipePosition->ingredient_group }}
</td>
</tr>
@endforeach
@endif
</tbody>
</table>
</div>
</div>
@if (!empty($recipePosition->variable_amount))
<div class="small text-muted font-italic">{{ $__t('Variable amount') }}
</div>
@endif
</td>
<td class="fit-content">
<a class="btn btn-sm btn-info recipe-pos-show-note-button @if (empty($recipePosition->note)) disabled @endif"
href="#" data-toggle="tooltip" data-placement="top"
title="{{ $__t('Show notes') }}"
data-recipe-pos-note="{{ $recipePosition->note }}">
<i class="fas fa-eye"></i>
</a>
</td>
<td>
{{ $recipePosition->ingredient_group }}
</td>
</tr>
@endforeach
@endif
</tbody>
</table>
</div>
</div>
<div class="row mt-5">
<div class="col">
<div class="title-related-links">
<h4>
{{ $__t('Included recipes') }}
</h4>
<button class="btn btn-outline-dark d-md-none mt-2 float-right order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
id="related-links">
<a id="recipe-include-add-button"
class="btn btn-outline-primary btn-sm m-1 mt-md-0 mb-md-0 float-right"
href="#">
{{ $__t('Add') }}
</a>
</div>
</div>
<table id="recipes-includes-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#recipes-includes-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Recipe') }}</th>
<th>{{ $__t('Servings') }}</th>
</tr>
</thead>
<tbody class="d-none">
@if($mode == "edit")
@foreach($recipeNestings as $recipeNesting)
<tr>
<td class="fit-content border-right">
<a class="btn btn-sm btn-info recipe-include-edit-button"
href="#"
data-recipe-include-id="{{ $recipeNesting->id }}"
data-recipe-included-recipe-id="{{ $recipeNesting->includes_recipe_id }}"
data-recipe-included-recipe-servings="{{ $recipeNesting->servings }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-sm btn-danger recipe-include-delete-button"
href="#"
data-recipe-include-id="{{ $recipeNesting->id }}"
data-recipe-include-name="{{ FindObjectInArrayByPropertyValue($recipes, 'id', $recipeNesting->includes_recipe_id)->name }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
{{ FindObjectInArrayByPropertyValue($recipes, 'id', $recipeNesting->includes_recipe_id)->name }}
</td>
<td>
{{ $recipeNesting->servings }}
</td>
</tr>
@endforeach
@endif
</tbody>
</table>
</div>
</div>
<div class="row mt-5">
<div class="col">
<div class="title-related-links">
<h4>
{{ $__t('Included recipes') }}
</h4>
<button class="btn btn-outline-dark d-md-none mt-2 float-right order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100" id="related-links">
<a id="recipe-include-add-button"
class="btn btn-outline-primary btn-sm m-1 mt-md-0 mb-md-0 float-right" href="#">
{{ $__t('Add') }}
</a>
</div>
</div>
{{-- TODO: DataTables: dynamic data: recipes_nestings_resolved --}}
<table id="recipes-includes-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#recipes-includes-table" href="#"><i
class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Recipe') }}</th>
<th>{{ $__t('Servings') }}</th>
</tr>
</thead>
<tbody class="d-none">
@if ($mode == 'edit')
@foreach ($recipeNestings as $recipeNesting)
<tr>
<td class="fit-content border-right">
<a class="btn btn-sm btn-info recipe-include-edit-button" href="#"
data-recipe-include-id="{{ $recipeNesting->id }}"
data-recipe-included-recipe-id="{{ $recipeNesting->includes_recipe_id }}"
data-recipe-included-recipe-servings="{{ $recipeNesting->servings }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-sm btn-danger recipe-include-delete-button" href="#"
data-recipe-include-id="{{ $recipeNesting->id }}"
data-recipe-include-name="{{ FindObjectInArrayByPropertyValue($recipes, 'id', $recipeNesting->includes_recipe_id)->name }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
{{ FindObjectInArrayByPropertyValue($recipes, 'id', $recipeNesting->includes_recipe_id)->name }}
</td>
<td>
{{ $recipeNesting->servings }}
</td>
</tr>
@endforeach
@endif
</tbody>
</table>
</div>
</div>
<div class="row mt-5">
<div class="col">
<div class="title-related-links">
<h4>
{{ $__t('Picture') }}
</h4>
<div class="form-group w-75 m-0">
<div class="input-group">
<div class="custom-file">
<input type="file"
class="custom-file-input"
id="recipe-picture"
accept="image/*">
<label id="recipe-picture-label"
class="custom-file-label @if(empty($recipe->picture_file_name)) d-none @endif"
for="recipe-picture">
{{ $recipe->picture_file_name }}
</label>
<label id="recipe-picture-label-none"
class="custom-file-label @if(!empty($recipe->picture_file_name)) d-none @endif"
for="recipe-picture">
{{ $__t('No file selected') }}
</label>
</div>
<div class="input-group-append">
<span class="input-group-text"><i class="fas fa-trash"
id="delete-current-recipe-picture-button"></i></span>
</div>
</div>
</div>
</div>
@if(!empty($recipe->picture_file_name))
<img id="current-recipe-picture"
data-src="{{ $U('/api/files/recipepictures/' . base64_encode($recipe->picture_file_name) . '?force_serve_as=picture&best_fit_width=400') }}"
class="img-fluid img-thumbnail mt-2 lazy mb-5">
<p id="delete-current-recipe-picture-on-save-hint"
class="form-text text-muted font-italic d-none mb-5">{{ $__t('The current picture will be deleted on save') }}</p>
@else
<p id="no-current-recipe-picture-hint"
class="form-text text-muted font-italic mb-5">{{ $__t('No picture available') }}</p>
@endif
</div>
</div>
<div class="row">
<div class="col">
<div class="title-related-links">
<h4>
<span class="ls-n1">{{ $__t('grocycode') }}</span>
<i class="fas fa-question-circle text-muted"
data-toggle="tooltip"
data-trigger="hover click"
title="{{ $__t('grocycode is a unique referer to this %s in your grocy instance - print it onto a label and scan it like any other barcode', $__t('Recipe')) }}"></i>
</h4>
<p>
@if($mode == 'edit')
<img src="{{ $U('/recipe/' . $recipe->id . '/grocycode?size=60') }}"
class="float-lg-left">
@endif
</p>
<p>
<a class="btn btn-outline-primary btn-sm"
href="{{ $U('/recipe/' . $recipe->id . '/grocycode?download=true') }}">{{ $__t('Download') }}</a>
@if(GROCY_FEATURE_FLAG_LABEL_PRINTER)
<a class="btn btn-outline-primary btn-sm recipe-grocycode-label-print"
data-recipe-id="{{ $recipe->id }}"
href="#">
{{ $__t('Print on label printer') }}
</a>
@endif
</p>
</div>
</div>
</div>
</div>
<div class="row mt-5">
<div class="col">
<div class="title-related-links">
<h4>
{{ $__t('Picture') }}
</h4>
<div class="form-group w-75 m-0">
<div class="input-group">
<div class="custom-file">
<input type="file" class="custom-file-input" id="recipe-picture" accept="image/*">
<label id="recipe-picture-label"
class="custom-file-label @if (empty($recipe->picture_file_name)) d-none @endif"
for="recipe-picture">
{{ $recipe->picture_file_name }}
</label>
<label id="recipe-picture-label-none"
class="custom-file-label @if (!empty($recipe->picture_file_name)) d-none @endif"
for="recipe-picture">
{{ $__t('No file selected') }}
</label>
</div>
<div class="input-group-append">
<span class="input-group-text"><i class="fas fa-trash"
id="delete-current-recipe-picture-button"></i></span>
</div>
</div>
</div>
</div>
@if (!empty($recipe->picture_file_name))
<img id="current-recipe-picture"
data-src="{{ $U('/api/files/recipepictures/' .base64_encode($recipe->picture_file_name) .'?force_serve_as=picture&best_fit_width=400') }}"
class="img-fluid img-thumbnail mt-2 lazy mb-5">
<p id="delete-current-recipe-picture-on-save-hint"
class="form-text text-muted font-italic d-none mb-5">
{{ $__t('The current picture will be deleted on save') }}</p>
@else
<p id="no-current-recipe-picture-hint" class="form-text text-muted font-italic mb-5">
{{ $__t('No picture available') }}</p>
@endif
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="title-related-links">
<h4>
<span class="ls-n1">{{ $__t('grocycode') }}</span>
<i class="fas fa-question-circle text-muted" data-toggle="tooltip" data-trigger="hover click"
title="{{ $__t('grocycode is a unique referer to this %s in your grocy instance - print it onto a label and scan it like any other barcode',$__t('Recipe')) }}"></i>
</h4>
<p>
@if ($mode == 'edit')
<img src="{{ $U('/recipe/' . $recipe->id . '/grocycode?size=60') }}"
class="float-lg-left">
@endif
</p>
<p>
<a class="btn btn-outline-primary btn-sm"
href="{{ $U('/recipe/' . $recipe->id . '/grocycode?download=true') }}">{{ $__t('Download') }}</a>
@if (GROCY_FEATURE_FLAG_LABEL_PRINTER)
<a class="btn btn-outline-primary btn-sm recipe-grocycode-label-print"
data-recipe-id="{{ $recipe->id }}" href="#">
{{ $__t('Print on label printer') }}
</a>
@endif
</p>
</div>
</div>
</div>
</div>
<div class="modal fade"
id="recipe-include-editform-modal"
tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-center">
<div class="modal-header">
<h4 id="recipe-include-editform-title"
class="modal-title w-100"></h4>
</div>
<div class="modal-body">
<form id="recipe-include-form"
novalidate>
</div>
@include('components.recipepicker', array(
'recipes' => $recipes,
'isRequired' => true
))
<div class="modal fade" id="recipe-include-editform-modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-center">
<div class="modal-header">
<h4 id="recipe-include-editform-title" class="modal-title w-100"></h4>
</div>
<div class="modal-body">
<form id="recipe-include-form" novalidate>
@include('components.numberpicker', array(
'id' => 'includes_servings',
'label' => 'Servings',
'min' => $DEFAULT_MIN_AMOUNT,
'decimals' => $userSettings['stock_decimal_places_amounts'],
'value' => '1',
'additionalCssClasses' => 'locale-number-input locale-number-quantity-amount'
))
@include('components.recipepicker', [
'recipes' => $recipes,
'isRequired' => true,
])
</form>
</div>
<div class="modal-footer">
<button type="button"
class="btn btn-secondary"
data-dismiss="modal">{{ $__t('Cancel') }}</button>
<button id="save-recipe-include-button"
data-dismiss="modal"
class="btn btn-success">{{ $__t('Save') }}</button>
</div>
</div>
</div>
</div>
@include('components.numberpicker', [
'id' => 'includes_servings',
'label' => 'Servings',
'min' => $DEFAULT_MIN_AMOUNT,
'decimals' => $userSettings['stock_decimal_places_amounts'],
'value' => '1',
'additionalCssClasses' => 'locale-number-input locale-number-quantity-amount',
])
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ $__t('Cancel') }}</button>
<button id="save-recipe-include-button" data-dismiss="modal"
class="btn btn-success">{{ $__t('Save') }}</button>
</div>
</div>
</div>
</div>
@stop

View File

@ -46,8 +46,8 @@
novalidate>
@include('components.productpicker', array(
'products' => $products,
'nextInputSelector' => '#amount'
'productsQuery' => ($recipePosId === 'new' ? 'query%5B%5D=active%3D1&' : '') . 'order=name%3Acollate%20nocase',
'nextInputSelector' => '#amount'
))
<div class="form-group mb-2 @if(!GROCY_FEATURE_FLAG_STOCK) d-none @endif">
@ -66,9 +66,9 @@
@php if($mode == 'edit') { $value = $recipePos->amount; } else { $value = 1; } @endphp
@php if($mode == 'edit') { $initialQuId = $recipePos->qu_id; } else { $initialQuId = ''; } @endphp
@include('components.productamountpicker', array(
'value' => $value,
'initialQuId' => $initialQuId,
'additionalGroupCssClasses' => 'mb-2'
'value' => $value,
'initialQuId' => $initialQuId,
'additionalGroupCssClasses' => 'mb-2'
))
<div class="form-group">
@ -116,15 +116,15 @@
@if(GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
@php if($mode == 'edit') { $value = $recipePos->price_factor; } else { $value = 1; } @endphp
@include('components.numberpicker', array(
'id' => 'price_factor',
'label' => 'Price factor',
'min' => $DEFAULT_MIN_AMOUNT,
'decimals' => $userSettings['stock_decimal_places_amounts'],
'value' => '',
'hint' => $__t('The resulting price of this ingredient will be multiplied by this factor'),
'isRequired' => true,
'value' => $value,
'additionalCssClasses' => 'locale-number-input locale-number-quantity-amount'
'id' => 'price_factor',
'label' => 'Price factor',
'min' => $DEFAULT_MIN_AMOUNT,
'decimals' => $userSettings['stock_decimal_places_amounts'],
'value' => '',
'hint' => $__t('The resulting price of this ingredient will be multiplied by this factor'),
'isRequired' => true,
'value' => $value,
'additionalCssClasses' => 'locale-number-input locale-number-quantity-amount'
))
@else
<input type="hidden"

File diff suppressed because it is too large Load Diff

View File

@ -5,427 +5,433 @@
@section('viewJsName', 'shoppinglist')
@push('pageScripts')
<script src="{{ $U('/node_modules/bwip-js/dist/bwip-js-min.js?v=', true) }}{{ $version }}"></script>
<script src="{{ $U('/viewjs/purchase.js?v=', true) }}{{ $version }}"></script>
<script src="{{ $U('/node_modules/bwip-js/dist/bwip-js-min.js?v=', true) }}{{ $version }}">
</script>
<script src="{{ $U('/viewjs/purchase.js?v=', true) }}{{ $version }}"></script>
@endpush
@push('pageStyles')
<link href="{{ $U('/node_modules/animate.css/animate.min.css?v=', true) }}{{ $version }}"
rel="stylesheet">
<link href="{{ $U('/node_modules/animate.css/animate.min.css?v=', true) }}{{ $version }}" rel="stylesheet">
@endpush
@section('content')
<div class="row d-print-none hide-on-fullscreen-card">
<div class="col">
<div class="title-related-links">
<h2 class="title mr-2 order-0">
@yield('title')
</h2>
@if(GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
<h2 class="mb-0 mr-auto order-3 order-md-1 width-xs-sm-100">
<span class="text-muted small">{!! $__t('%s total value', '<span class="locale-number locale-number-currency">' . SumArrayValue($listItems, 'last_price_total') . '</span>') !!}</span>
</h2>
@endif
<div class="float-right">
<button class="btn btn-primary responsive-button d-md-none mt-2 order-1 order-md-3 show-as-dialog-link"
href="{{ $U('/shoppinglistitem/new?embedded&list=' . $selectedShoppingListId) }}">
{{ $__t('Add item') }}
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
id="related-links">
@if(GROCY_FEATURE_FLAG_SHOPPINGLIST_MULTIPLE_LISTS)
<div class="my-auto float-right">
<select class="custom-control custom-select custom-select-sm"
id="selected-shopping-list">
@foreach($shoppingLists as $shoppingList)
<option @if($shoppingList->id == $selectedShoppingListId) selected="selected" @endif value="{{ $shoppingList->id }}">{{ $shoppingList->name }}</option>
@endforeach
</select>
</div>
<a class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right show-as-dialog-link"
href="{{ $U('/shoppinglist/new?embedded') }}">
{{ $__t('New shopping list') }}
</a>
<a class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right show-as-dialog-link"
href="{{ $U('/shoppinglist/' . $selectedShoppingListId . '?embedded') }}">
{{ $__t('Edit shopping list') }}
</a>
<a id="delete-selected-shopping-list"
class="btn btn-outline-danger responsive-button m-1 mt-md-0 mb-md-0 float-right @if($selectedShoppingListId == 1) disabled @endif"
href="#">
{{ $__t('Delete shopping list') }}
</a>
@else
<input type="hidden"
name="selected-shopping-list"
id="selected-shopping-list"
value="1">
@endif
<a id="print-shopping-list-button"
class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right"
href="#">
{{ $__t('Print') }}
</a>
</div>
</div>
<div id="filter-container"
class="border-top border-bottom my-2 py-1">
<div id="table-filter-row"
data-status-filter="belowminstockamount"
class="collapse normal-message status-filter-message responsive-button @if(!GROCY_FEATURE_FLAG_STOCK) d-none @else d-md-inline-block @endif"><span class="d-block d-md-none">{{count($missingProducts)}} <i class="fas fa-exclamation-circle"></i></span><span class="d-none d-md-block">{{ $__n(count($missingProducts), '%s product is below defined min. stock amount', '%s products are below defined min. stock amount') }}</span></div>
<div id="related-links"
class="float-right mt-1 collapse d-md-block">
<a class="btn btn-primary responsive-button btn-sm mb-1 show-as-dialog-link d-none d-md-inline-block"
href="{{ $U('/shoppinglistitem/new?embedded&list=' . $selectedShoppingListId) }}">
{{ $__t('Add item') }}
</a>
<a id="clear-shopping-list"
class="btn btn-outline-danger btn-sm mb-1 responsive-button @if($listItems->count() == 0) disabled @endif"
href="#">
{{ $__t('Clear list') }}
</a>
<a id="add-all-items-to-stock-button"
class="btn btn-outline-primary btn-sm mb-1 responsive-button @if(!GROCY_FEATURE_FLAG_STOCK) d-none @endif"
href="#">
{{ $__t('Add all list items to stock') }}
</a>
<a id="add-products-below-min-stock-amount"
class="btn btn-outline-primary btn-sm mb-1 responsive-button @if(!GROCY_FEATURE_FLAG_STOCK) d-none @endif"
href="#">
{{ $__t('Add products that are below defined min. stock amount') }}
</a>
<a id="add-overdue-expired-products"
class="btn btn-outline-primary btn-sm mb-1 responsive-button @if(!GROCY_FEATURE_FLAG_STOCK) d-none @endif"
href="#">
{{ $__t('Add overdue/expired products') }}
</a>
</div>
</div>
</div>
</div>
<div class="row d-print-none hide-on-fullscreen-card">
<div class="col">
<div class="title-related-links">
<h2 class="title mr-2 order-0">
@yield('title')
</h2>
@if (GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
<h2 class="mb-0 mr-auto order-3 order-md-1 width-xs-sm-100">
<span class="text-muted small">{!! $__t('%s total value', '<span class="locale-number locale-number-currency">' . SumArrayValue($listItems, 'last_price_total') . '</span>') !!}</span>
</h2>
@endif
<div class="float-right">
<button class="btn btn-primary responsive-button d-md-none mt-2 order-1 order-md-3 show-as-dialog-link"
href="{{ $U('/shoppinglistitem/new?embedded&list=' . $selectedShoppingListId) }}">
{{ $__t('Add item') }}
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100" id="related-links">
@if (GROCY_FEATURE_FLAG_SHOPPINGLIST_MULTIPLE_LISTS)
<div class="my-auto float-right">
{{-- TODO: Select2: dynamic data: shopping_lists --}}
<select class="custom-control custom-select custom-select-sm" id="selected-shopping-list">
@foreach ($shoppingLists as $shoppingList)
<option @if ($shoppingList->id == $selectedShoppingListId) selected="selected" @endif
value="{{ $shoppingList->id }}">{{ $shoppingList->name }}</option>
@endforeach
</select>
</div>
<a class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right show-as-dialog-link"
href="{{ $U('/shoppinglist/new?embedded') }}">
{{ $__t('New shopping list') }}
</a>
<a class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right show-as-dialog-link"
href="{{ $U('/shoppinglist/' . $selectedShoppingListId . '?embedded') }}">
{{ $__t('Edit shopping list') }}
</a>
<a id="delete-selected-shopping-list"
class="btn btn-outline-danger responsive-button m-1 mt-md-0 mb-md-0 float-right @if ($selectedShoppingListId == 1) disabled @endif"
href="#">
{{ $__t('Delete shopping list') }}
</a>
@else
<input type="hidden" name="selected-shopping-list" id="selected-shopping-list" value="1">
@endif
<a id="print-shopping-list-button"
class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right" href="#">
{{ $__t('Print') }}
</a>
</div>
</div>
<div id="filter-container" class="border-top border-bottom my-2 py-1">
<div id="table-filter-row" data-status-filter="belowminstockamount"
class="collapse normal-message status-filter-message responsive-button @if (!GROCY_FEATURE_FLAG_STOCK) d-none @else d-md-inline-block @endif">
<span class="d-block d-md-none">{{ count($missingProducts) }} <i
class="fas fa-exclamation-circle"></i></span><span
class="d-none d-md-block">{{ $__n(count($missingProducts),'%s product is below defined min. stock amount','%s products are below defined min. stock amount') }}</span>
</div>
<div id="related-links" class="float-right mt-1 collapse d-md-block">
<a class="btn btn-primary responsive-button btn-sm mb-1 show-as-dialog-link d-none d-md-inline-block"
href="{{ $U('/shoppinglistitem/new?embedded&list=' . $selectedShoppingListId) }}">
{{ $__t('Add item') }}
</a>
<a id="clear-shopping-list"
class="btn btn-outline-danger btn-sm mb-1 responsive-button @if ($listItems->count() == 0) disabled @endif"
href="#">
{{ $__t('Clear list') }}
</a>
<a id="add-all-items-to-stock-button"
class="btn btn-outline-primary btn-sm mb-1 responsive-button @if (!GROCY_FEATURE_FLAG_STOCK) d-none @endif"
href="#">
{{ $__t('Add all list items to stock') }}
</a>
<a id="add-products-below-min-stock-amount"
class="btn btn-outline-primary btn-sm mb-1 responsive-button @if (!GROCY_FEATURE_FLAG_STOCK) d-none @endif"
href="#">
{{ $__t('Add products that are below defined min. stock amount') }}
</a>
<a id="add-overdue-expired-products"
class="btn btn-outline-primary btn-sm mb-1 responsive-button @if (!GROCY_FEATURE_FLAG_STOCK) d-none @endif"
href="#">
{{ $__t('Add overdue/expired products') }}
</a>
</div>
</div>
</div>
</div>
<div class="row collapse d-md-flex d-print-none hide-on-fullscreen-card"
id="table-filter-row">
<div class="col-12 col-md-5">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text"
id="search"
class="form-control"
placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col-12 col-md-4 col-lg-5">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Status') }}</span>
</div>
<select class="custom-control custom-select"
id="status-filter">
<option value="all">{{ $__t('All') }}</option>
<option class="@if(!GROCY_FEATURE_FLAG_STOCK) d-none @endif"
value="belowminstockamount">{{ $__t('Below min. stock amount') }}</option>
<option value="xxDONExx">{{ $__t('Only done items') }}</option>
<option value="xxUNDONExx">{{ $__t('Only undone items') }}</option>
</select>
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row collapse d-md-flex d-print-none hide-on-fullscreen-card" id="table-filter-row">
<div class="col-12 col-md-5">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text" id="search" class="form-control" placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col-12 col-md-4 col-lg-5">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Status') }}</span>
</div>
<select class="custom-control custom-select" id="status-filter">
<option value="all">{{ $__t('All') }}</option>
<option class="@if (!GROCY_FEATURE_FLAG_STOCK) d-none @endif" value="belowminstockamount">
{{ $__t('Below min. stock amount') }}</option>
<option value="xxDONExx">{{ $__t('Only done items') }}</option>
<option value="xxUNDONExx">{{ $__t('Only undone items') }}</option>
</select>
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button" class="btn btn-sm btn-outline-info" href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div id="shoppinglist-main"
class="row d-print-none">
<div class="@if(boolval($userSettings['shopping_list_show_calendar'])) col-12 col-md-8 @else col-12 @endif pb-3">
<table id="shoppinglist-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#shoppinglist-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th class="allow-grouping">{{ $__t('Product') }} / <em>{{ $__t('Note') }}</em></th>
<th>{{ $__t('Amount') }}</th>
<th class="allow-grouping">{{ $__t('Product group') }}</th>
<th class="d-none">Hidden status</th>
<th class="@if(!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif">{{ $__t('Last price (Unit)') }}</th>
<th class="@if(!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif">{{ $__t('Last price (Total)') }}</th>
<th class="@if(!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif allow-grouping">{{ $__t('Default store') }}</th>
<th>{{ $__t('Barcodes') }}</th>
<div id="shoppinglist-main" class="row d-print-none">
<div class="@if (boolval($userSettings['shopping_list_show_calendar'])) col-12 col-md-8 @else col-12 @endif pb-3">
{{-- TODO: DataTables: dynamic data: shopping_lists --}}
<table id="shoppinglist-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#shoppinglist-table" href="#"><i class="fas fa-eye"></i></a>
</th>
<th class="allow-grouping">{{ $__t('Product') }} / <em>{{ $__t('Note') }}</em></th>
<th>{{ $__t('Amount') }}</th>
<th class="allow-grouping">{{ $__t('Product group') }}</th>
<th class="d-none">Hidden status</th>
<th class="@if (!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif">{{ $__t('Last price (Unit)') }}</th>
<th class="@if (!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif">{{ $__t('Last price (Total)') }}
</th>
<th class="@if (!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif allow-grouping">
{{ $__t('Default store') }}</th>
<th>{{ $__t('Barcodes') }}</th>
@include('components.userfields_thead', array(
'userfields' => $userfields
))
@include('components.userfields_thead', array(
'userfields' => $productUserfields
))
@include('components.userfields_thead', [
'userfields' => $userfields,
])
@include('components.userfields_thead', [
'userfields' => $productUserfields,
])
</tr>
</thead>
<tbody class="d-none">
@foreach($listItems as $listItem)
<tr id="shoppinglistitem-{{ $listItem->id }}-row"
class="@if(FindObjectInArrayByPropertyValue($missingProducts, 'id', $listItem->product_id) !== null) table-info @endif @if($listItem->done == 1) text-muted text-strike-through @endif">
<td class="fit-content border-right">
<a class="btn btn-success btn-sm order-listitem-button"
href="#"
data-item-id="{{ $listItem->id }}"
data-item-done="{{ $listItem->done }}"
data-toggle="tooltip"
data-placement="right"
title="{{ $__t('Mark this item as done') }}">
<i class="fas fa-check"></i>
</a>
<a class="btn btn-sm btn-info show-as-dialog-link"
href="{{ $U('/shoppinglistitem/' . $listItem->id . '?embedded&list=' . $selectedShoppingListId ) }}"
data-toggle="tooltip"
data-placement="right"
title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-sm btn-danger shoppinglist-delete-button"
href="#"
data-shoppinglist-id="{{ $listItem->id }}"
data-toggle="tooltip"
data-placement="right"
title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
<a class="btn btn-sm btn-primary @if(!GROCY_FEATURE_FLAG_STOCK) d-none @endif @if(empty($listItem->product_id)) disabled @else shopping-list-stock-add-workflow-list-item-button @endif"
href="{{ $U('/purchase?embedded&flow=shoppinglistitemtostock&product=') }}{{ $listItem->product_id }}&amount={{ $listItem->amount }}&listitemid={{ $listItem->id }}&quId={{ $listItem->qu_id }}"
@if(!empty($listItem->product_id)) data-toggle="tooltip" title="{{ $__t('Add this item to stock') }}" @endif>
<i class="fas fa-box"></i>
</a>
</td>
<td class="product-name-cell cursor-link"
data-product-id="{{ $listItem->product_id }}">
@if(!empty($listItem->product_id)) {{ $listItem->product_name }}<br>@endif<em>{!! nl2br($listItem->note) !!}</em>
</td>
@if(!empty($listItem->product_id))
@php
$listItem->amount_origin_qu = $listItem->amount;
$product = FindObjectInArrayByPropertyValue($products, 'id', $listItem->product_id);
$productQuConversions = FindAllObjectsInArrayByPropertyValue($quantityUnitConversionsResolved, 'product_id', $product->id);
$productQuConversions = FindAllObjectsInArrayByPropertyValue($productQuConversions, 'from_qu_id', $product->qu_id_stock);
$productQuConversion = FindObjectInArrayByPropertyValue($productQuConversions, 'to_qu_id', $listItem->qu_id);
if ($productQuConversion)
{
$listItem->amount = $listItem->amount * $productQuConversion->factor;
}
@endphp
@endif
<td data-order={{
$listItem->amount }}>
<span class="locale-number locale-number-quantity-amount">{{ $listItem->amount }}</span> @if(!empty($listItem->product_id)){{ $__n($listItem->amount, $listItem->qu_name, $listItem->qu_name_plural, true) }}@endif
</td>
<td>
@if(!empty($listItem->product_group_name)) {{ $listItem->product_group_name }} @else <span class="font-italic font-weight-light">{{ $__t('Ungrouped') }}</span> @endif
</td>
<td id="shoppinglistitem-{{ $listItem->id }}-status-info"
class="d-none">
@if(FindObjectInArrayByPropertyValue($missingProducts, 'id', $listItem->product_id) !== null) belowminstockamount @endif
@if($listItem->done == 1) xxDONExx @else xxUNDONExx @endif
</td>
<td class="@if(!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif">
<span class="locale-number locale-number-currency">{{ $listItem->last_price_unit }}</span>
</td>
<td class="@if(!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif">
<span class="locale-number locale-number-currency">{{ $listItem->last_price_total }}</span>
</td>
<td class="@if(!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif">
{{ $listItem->default_shopping_location_name }}
</td>
<td>
@foreach(explode(',', $listItem->product_barcodes) as $barcode)
@if(!empty($barcode))
<img class="barcode img-fluid pr-2"
data-barcode="{{ $barcode }}">
@endif
@endforeach
</td>
</tr>
</thead>
<tbody class="d-none">
@foreach ($listItems as $listItem)
<tr id="shoppinglistitem-{{ $listItem->id }}-row"
class="@if (FindObjectInArrayByPropertyValue($missingProducts, 'id', $listItem->product_id) !== null) table-info @endif @if ($listItem->done == 1) text-muted text-strike-through @endif">
<td class="fit-content border-right">
<a class="btn btn-success btn-sm order-listitem-button" href="#"
data-item-id="{{ $listItem->id }}" data-item-done="{{ $listItem->done }}"
data-toggle="tooltip" data-placement="right"
title="{{ $__t('Mark this item as done') }}">
<i class="fas fa-check"></i>
</a>
<a class="btn btn-sm btn-info show-as-dialog-link"
href="{{ $U('/shoppinglistitem/' . $listItem->id . '?embedded&list=' . $selectedShoppingListId) }}"
data-toggle="tooltip" data-placement="right" title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-sm btn-danger shoppinglist-delete-button" href="#"
data-shoppinglist-id="{{ $listItem->id }}" data-toggle="tooltip"
data-placement="right" title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
<a class="btn btn-sm btn-primary @if (!GROCY_FEATURE_FLAG_STOCK) d-none @endif @if (empty($listItem->product_id)) disabled @else shopping-list-stock-add-workflow-list-item-button @endif"
href="{{ $U('/purchase?embedded&flow=shoppinglistitemtostock&product=') }}{{ $listItem->product_id }}&amount={{ $listItem->amount }}&listitemid={{ $listItem->id }}&quId={{ $listItem->qu_id }}"
@if (!empty($listItem->product_id)) data-toggle="tooltip" title="{{ $__t('Add this item to stock') }}" @endif>
<i class="fas fa-box"></i>
</a>
</td>
<td class="product-name-cell cursor-link" data-product-id="{{ $listItem->product_id }}">
@if (!empty($listItem->product_id))
{{ $listItem->product_name }}<br>
@endif
<em>
{!! nl2br($listItem->note) !!}</em>
</td>
@if (!empty($listItem->product_id))
@php
$listItem->amount_origin_qu = $listItem->amount;
$product = FindObjectInArrayByPropertyValue($products, 'id', $listItem->product_id);
$productQuConversions = FindAllObjectsInArrayByPropertyValue($quantityUnitConversionsResolved, 'product_id', $product->id);
$productQuConversions = FindAllObjectsInArrayByPropertyValue($productQuConversions, 'from_qu_id', $product->qu_id_stock);
$productQuConversion = FindObjectInArrayByPropertyValue($productQuConversions, 'to_qu_id', $listItem->qu_id);
if ($productQuConversion) {
$listItem->amount = $listItem->amount * $productQuConversion->factor;
}
@endphp
@endif
<td data-order={{ <td data-order=$listItem->amount }}>
<span class="locale-number locale-number-quantity-amount">{{ $listItem->amount }}</span>
@if (!empty($listItem->product_id))
{{ $__n($listItem->amount, $listItem->qu_name, $listItem->qu_name_plural, true) }}
@endif
</td>
<td>
@if (!empty($listItem->product_group_name))
{{ $listItem->product_group_name }}
@else
<span class="font-italic font-weight-light">{{ $__t('Ungrouped') }}</span>
@endif
</td>
<td id="shoppinglistitem-{{ $listItem->id }}-status-info" class="d-none">
@if (FindObjectInArrayByPropertyValue($missingProducts, 'id', $listItem->product_id) !== null)
belowminstockamount
@endif
@if ($listItem->done == 1)
xxDONExx
@else
xxUNDONExx
@endif
</td>
<td class="@if (!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif">
<span
class="locale-number locale-number-currency">{{ $listItem->last_price_unit }}</span>
</td>
<td class="@if (!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif">
<span
class="locale-number locale-number-currency">{{ $listItem->last_price_total }}</span>
</td>
<td class="@if (!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif">
{{ $listItem->default_shopping_location_name }}
</td>
<td>
@foreach (explode(',', $listItem->product_barcodes) as $barcode)
@if (!empty($barcode))
<img class="barcode img-fluid pr-2" data-barcode="{{ $barcode }}">
@endif
@endforeach
</td>
@include('components.userfields_tbody', array(
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $listItem->id)
))
@include('components.userfields_tbody', array(
'userfields' => $productUserfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($productUserfieldValues, 'object_id', $listItem->product_id)
))
@include('components.userfields_tbody', [
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue(
$userfieldValues,
'object_id',
$listItem->id
),
])
@include('components.userfields_tbody', [
'userfields' => $productUserfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue(
$productUserfieldValues,
'object_id',
$listItem->product_id
),
])
</tr>
@endforeach
</tbody>
</table>
</div>
</tr>
@endforeach
</tbody>
</table>
</div>
@if(boolval($userSettings['shopping_list_show_calendar']))
<div class="col-12 col-md-4 mt-md-2 d-print-none">
@include('components.calendarcard')
</div>
@endif
@if (boolval($userSettings['shopping_list_show_calendar']))
<div class="col-12 col-md-4 mt-md-2 d-print-none">
@include('components.calendarcard')
</div>
@endif
<div class="@if(boolval($userSettings['shopping_list_show_calendar'])) col-12 col-md-8 @else col-12 @endif d-print-none pt-2">
<div class="form-group">
<label class="text-larger font-weight-bold"
for="notes">{{ $__t('Notes') }}</label>
<a id="save-description-button"
class="btn btn-success btn-sm ml-1 mb-2"
href="#">{{ $__t('Save') }}</a>
<a id="clear-description-button"
class="btn btn-danger btn-sm ml-1 mb-2"
href="#">{{ $__t('Clear') }}</a>
<textarea class="form-control wysiwyg-editor"
id="description"
name="description">{{ FindObjectInArrayByPropertyValue($shoppingLists, 'id', $selectedShoppingListId)->description }}</textarea>
</div>
</div>
</div>
<div class="@if (boolval($userSettings['shopping_list_show_calendar'])) col-12 col-md-8 @else col-12 @endif d-print-none pt-2">
<div class="form-group">
<label class="text-larger font-weight-bold" for="notes">{{ $__t('Notes') }}</label>
<a id="save-description-button" class="btn btn-success btn-sm ml-1 mb-2" href="#">{{ $__t('Save') }}</a>
<a id="clear-description-button" class="btn btn-danger btn-sm ml-1 mb-2" href="#">{{ $__t('Clear') }}</a>
<textarea class="form-control wysiwyg-editor" id="description"
name="description">{{ FindObjectInArrayByPropertyValue($shoppingLists, 'id', $selectedShoppingListId)->description }}</textarea>
</div>
</div>
</div>
<div class="modal fade"
id="shopping-list-stock-add-workflow-modal"
tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-center">
<div class="modal-body">
<iframe id="shopping-list-stock-add-workflow-purchase-form-frame"
class="embed-responsive"
src=""></iframe>
</div>
<div class="modal-footer">
<span id="shopping-list-stock-add-workflow-purchase-item-count"
class="d-none mr-auto"></span>
<button id="shopping-list-stock-add-workflow-skip-button"
type="button"
class="btn btn-primary">{{ $__t('Skip') }}</button>
<button type="button"
class="btn btn-secondary"
data-dismiss="modal">{{ $__t('Close') }}</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="shopping-list-stock-add-workflow-modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-center">
<div class="modal-body">
<iframe id="shopping-list-stock-add-workflow-purchase-form-frame" class="embed-responsive"
src=""></iframe>
</div>
<div class="modal-footer">
<span id="shopping-list-stock-add-workflow-purchase-item-count" class="d-none mr-auto"></span>
<button id="shopping-list-stock-add-workflow-skip-button" type="button"
class="btn btn-primary">{{ $__t('Skip') }}</button>
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ $__t('Close') }}</button>
</div>
</div>
</div>
</div>
<div class="d-none d-print-block">
<div id="print-header">
<h1 class="text-center">
<img src="{{ $U('/img/grocy_logo.svg?v=', true) }}{{ $version }}"
height="30"
class="d-print-flex mx-auto">
{{ $__t("Shopping list") }}
</h1>
@if (FindObjectInArrayByPropertyValue($shoppingLists, 'id', $selectedShoppingListId)->name != $__t("Shopping list"))
<h3 class="text-center">
{{ FindObjectInArrayByPropertyValue($shoppingLists, 'id', $selectedShoppingListId)->name }}
</h3>
@endif
<h6 class="text-center mb-4">
{{ $__t('Time of printing') }}:
<span class="d-inline print-timestamp"></span>
</h6>
</div>
<div class="w-75 print-layout-container print-layout-type-table d-none">
<div>
<table id="shopping-list-print-shadow-table"
class="table table-sm table-striped nowrap">
<thead>
<tr>
<th>{{ $__t('Product') }} / <em>{{ $__t('Note') }}</em></th>
<th>{{ $__t('Amount') }}</th>
<th>{{ $__t('Product group') }}</th>
<div class="d-none d-print-block">
<div id="print-header">
<h1 class="text-center">
<img src="{{ $U('/img/grocy_logo.svg?v=', true) }}{{ $version }}" height="30"
class="d-print-flex mx-auto">
{{ $__t('Shopping list') }}
</h1>
@if (FindObjectInArrayByPropertyValue($shoppingLists, 'id', $selectedShoppingListId)->name != $__t('Shopping list'))
<h3 class="text-center">
{{ FindObjectInArrayByPropertyValue($shoppingLists, 'id', $selectedShoppingListId)->name }}
</h3>
@endif
<h6 class="text-center mb-4">
{{ $__t('Time of printing') }}:
<span class="d-inline print-timestamp"></span>
</h6>
</div>
<div class="w-75 print-layout-container print-layout-type-table d-none">
<div>
{{-- TODO: DataTables: dynamic data: uihelper_shopping_list --}}
<table id="shopping-list-print-shadow-table" class="table table-sm table-striped nowrap">
<thead>
<tr>
<th>{{ $__t('Product') }} / <em>{{ $__t('Note') }}</em></th>
<th>{{ $__t('Amount') }}</th>
<th>{{ $__t('Product group') }}</th>
@include('components.userfields_thead', array(
'userfields' => $userfields
))
@include('components.userfields_thead', array(
'userfields' => $productUserfields
))
@include('components.userfields_thead', [
'userfields' => $userfields,
])
@include('components.userfields_thead', [
'userfields' => $productUserfields,
])
</tr>
</thead>
<tbody>
@foreach($listItems as $listItem)
<tr>
<td>
@if(!empty($listItem->product_id)) {{ $listItem->product_name }}<br>@endif<em>{!! nl2br($listItem->note) !!}</em>
</td>
<td>
<span class="locale-number locale-number-quantity-amount">{{ $listItem->amount }}</span> @if(!empty($listItem->product_id)){{ $__n($listItem->amount, $listItem->qu_name, $listItem->qu_name_plural, true) }}@endif
</td>
<td>
@if(!empty($listItem->product_group_name)) {{ $listItem->product_group_name }} @else <span class="font-italic font-weight-light">{{ $__t('Ungrouped') }}</span> @endif
</td>
</tr>
</thead>
<tbody>
@foreach ($listItems as $listItem)
<tr>
<td>
@if (!empty($listItem->product_id))
{{ $listItem->product_name }}<br>
@endif
<em>
{!! nl2br($listItem->note) !!}</em>
</td>
<td>
<span
class="locale-number locale-number-quantity-amount">{{ $listItem->amount }}</span>
@if (!empty($listItem->product_id))
{{ $__n($listItem->amount, $listItem->qu_name, $listItem->qu_name_plural, true) }}
@endif
</td>
<td>
@if (!empty($listItem->product_group_name))
{{ $listItem->product_group_name }}
@else
<span class="font-italic font-weight-light">{{ $__t('Ungrouped') }}</span>
@endif
</td>
@include('components.userfields_tbody', array(
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $listItem->id)
))
@include('components.userfields_tbody', array(
'userfields' => $productUserfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($productUserfieldValues, 'object_id', $listItem->product_id)
))
@include('components.userfields_tbody', [
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue(
$userfieldValues,
'object_id',
$listItem->id
),
])
@include('components.userfields_tbody', [
'userfields' => $productUserfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue(
$productUserfieldValues,
'object_id',
$listItem->product_id
),
])
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<div class="w-75 print-layout-container print-layout-type-list d-none">
@foreach($listItems as $listItem)
<div class="py-0">
<span class="locale-number locale-number-quantity-amount">{{ $listItem->amount }}</span> @if(!empty($listItem->product_id)){{ $__n($listItem->amount, $listItem->qu_name, $listItem->qu_name_plural, true) }}@endif
@if(!empty($listItem->product_id)) {{ $listItem->product_name }}<br>@endif<em>{!! nl2br($listItem->note) !!}</em>
</div><br>
@endforeach
</div>
<div class="w-75 pt-3">
<div>
<h5>{{ $__t('Notes') }}</h5>
<p id="description-for-print"></p>
</div>
</div>
</div>
<div class="modal fade"
id="shoppinglist-productcard-modal"
tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-center">
<div class="modal-body">
@include('components.productcard')
</div>
<div class="modal-footer">
<button type="button"
class="btn btn-secondary"
data-dismiss="modal">{{ $__t('Close') }}</button>
</div>
</div>
</div>
</div>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<div class="w-75 print-layout-container print-layout-type-list d-none">
@foreach ($listItems as $listItem)
<div class="py-0">
<span class="locale-number locale-number-quantity-amount">{{ $listItem->amount }}</span>
@if (!empty($listItem->product_id))
{{ $__n($listItem->amount, $listItem->qu_name, $listItem->qu_name_plural, true) }}
@endif
@if (!empty($listItem->product_id))
{{ $listItem->product_name }}<br>
@endif
<em>{!! nl2br($listItem->note) !!}</em>
</div><br>
@endforeach
</div>
<div class="w-75 pt-3">
<div>
<h5>{{ $__t('Notes') }}</h5>
<p id="description-for-print"></p>
</div>
</div>
</div>
<div class="modal fade" id="shoppinglist-productcard-modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-center">
<div class="modal-body">
@include('components.productcard')
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ $__t('Close') }}</button>
</div>
</div>
</div>
</div>
@stop

View File

@ -1,101 +1,112 @@
@extends('layout.default')
@if($mode == 'edit')
@section('title', $__t('Edit shopping list item'))
@if ($mode == 'edit')
@section('title', $__t('Edit shopping list item'))
@else
@section('title', $__t('Create shopping list item'))
@section('title', $__t('Create shopping list item'))
@endif
@section('viewJsName', 'shoppinglistitemform')
@section('content')
<script>
Grocy.QuantityUnits = {!! json_encode($quantityUnits) !!};
Grocy.QuantityUnitConversionsResolved = {!! json_encode($quantityUnitConversionsResolved) !!};
</script>
<script>
Grocy.QuantityUnits = {!! json_encode($quantityUnits) !!};
Grocy.QuantityUnitConversionsResolved = {!! json_encode($quantityUnitConversionsResolved) !!};
</script>
<div class="row">
<div class="col">
<h2 class="title">@yield('title')</h2>
</div>
</div>
<div class="row">
<div class="col">
<h2 class="title">@yield('title')</h2>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row">
<div class="col-12 col-md-6 col-xl-4 pb-3">
<script>
Grocy.EditMode = '{{ $mode }}';
</script>
<div class="row">
<div class="col-12 col-md-6 col-xl-4 pb-3">
<script>
Grocy.EditMode = '{{ $mode }}';
</script>
@if($mode == 'edit')
<script>
Grocy.EditObjectId = {{ $listItem->id }};
</script>
@endif
@if ($mode == 'edit')
<script>
Grocy.EditObjectId = {{ $listItem->id }};
</script>
@endif
<form id="shoppinglist-form"
novalidate>
<form id="shoppinglist-form" novalidate>
@if(GROCY_FEATURE_FLAG_SHOPPINGLIST_MULTIPLE_LISTS)
<div class="form-group">
<label for="shopping_list_id">{{ $__t('Shopping list') }}</label>
<select class="custom-control custom-select"
id="shopping_list_id"
name="shopping_list_id">
@foreach($shoppingLists as $shoppingList)
<option @if($mode=='edit'
&&
$shoppingList->id == $listItem->shopping_list_id) selected="selected" @endif value="{{ $shoppingList->id }}">{{ $shoppingList->name }}</option>
@endforeach
</select>
</div>
@else
<input type="hidden"
id="shopping_list_id"
name="shopping_list_id"
value="1">
@endif
@if (GROCY_FEATURE_FLAG_SHOPPINGLIST_MULTIPLE_LISTS)
<div class="form-group">
<label for="shopping_list_id">{{ $__t('Shopping list') }}</label>
{{-- TODO: Select2: dynamic data: shopping_lists --}}
<select class="custom-control custom-select" id="shopping_list_id" name="shopping_list_id">
@foreach ($shoppingLists as $shoppingList)
<option @if ($mode == 'edit' && $shoppingList->id == $listItem->shopping_list_id) selected="selected" @endif
value="{{ $shoppingList->id }}">{{ $shoppingList->name }}</option>
@endforeach
</select>
</div>
@else
<input type="hidden" id="shopping_list_id" name="shopping_list_id" value="1">
@endif
<div>
@php if($mode == 'edit') { $productId = $listItem->product_id; } else { $productId = ''; } @endphp
@include('components.productpicker', array(
'products' => $products,
'nextInputSelector' => '#amount',
'isRequired' => true,
'prefillById' => $productId,
'validationMessage' => 'A product or a note is required'
))
</div>
<div>
@php
if ($mode == 'edit') {
$productId = $listItem->product_id;
} else {
$productId = '';
}
@endphp
@include('components.productpicker', [
'productsQuery' => 'query%5B%5D=active%3D1&order=name%3Acollate%20nocase',
'nextInputSelector' => '#amount',
'isRequired' => true,
'prefillById' => $productId,
'validationMessage' => 'A product or a note is required',
])
</div>
@php if($mode == 'edit') { $value = $listItem->amount; } else { $value = 1; } @endphp
@php if($mode == 'edit') { $initialQuId = $listItem->qu_id; } else { $initialQuId = ''; } @endphp
@include('components.productamountpicker', array(
'value' => $value,
'initialQuId' => $initialQuId,
'min' => $DEFAULT_MIN_AMOUNT,
'isRequired' => false
))
@php
if ($mode == 'edit') {
$value = $listItem->amount;
} else {
$value = 1;
}
@endphp
@php
if ($mode == 'edit') {
$initialQuId = $listItem->qu_id;
} else {
$initialQuId = '';
}
@endphp
@include('components.productamountpicker', [
'value' => $value,
'initialQuId' => $initialQuId,
'min' => $DEFAULT_MIN_AMOUNT,
'isRequired' => false,
])
<div class="form-group">
<label for="note">{{ $__t('Note') }}</label>
<textarea class="form-control"
required
rows="10"
id="note"
name="note">@if($mode == 'edit'){{ $listItem->note }}@endif</textarea>
<div class="invalid-feedback">{{ $__t('A product or a note is required') }}</div>
</div>
<div class="form-group">
<label for="note">{{ $__t('Note') }}</label>
<textarea class="form-control" required rows="10" id="note" name="note">
@if ($mode == 'edit')
{{ $listItem->note }}
@endif
</textarea>
<div class="invalid-feedback">{{ $__t('A product or a note is required') }}</div>
</div>
@include('components.userfieldsform', array(
'userfields' => $userfields,
'entity' => 'shopping_list'
))
@include('components.userfieldsform', [
'userfields' => $userfields,
'entity' => 'shopping_list',
])
<button id="save-shoppinglist-button"
class="btn btn-success">{{ $__t('Save') }}</button>
<button id="save-shoppinglist-button" class="btn btn-success">{{ $__t('Save') }}</button>
</form>
</div>
</div>
</form>
</div>
</div>
@stop

View File

@ -5,122 +5,109 @@
@section('viewJsName', 'shoppinglocations')
@section('content')
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
id="related-links">
<a class="btn btn-primary responsive-button m-1 mt-md-0 mb-md-0 float-right show-as-dialog-link"
href="{{ $U('/shoppinglocation/new?embedded') }}">
{{ $__t('Add') }}
</a>
<a class="btn btn-outline-secondary m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/userfields?entity=shopping_locations') }}">
{{ $__t('Configure userfields') }}
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100" id="related-links">
<a class="btn btn-primary responsive-button m-1 mt-md-0 mb-md-0 float-right show-as-dialog-link"
href="{{ $U('/shoppinglocation/new?embedded') }}">
{{ $__t('Add') }}
</a>
<a class="btn btn-outline-secondary m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/userfields?entity=shopping_locations') }}">
{{ $__t('Configure userfields') }}
</a>
</div>
</div>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row collapse d-md-flex"
id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text"
id="search"
class="form-control"
placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row collapse d-md-flex" id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text" id="search" class="form-control" placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button" class="btn btn-sm btn-outline-info" href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row">
<div class="col">
<table id="shoppinglocations-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#shoppinglocations-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Name') }}</th>
<th>{{ $__t('Description') }}</th>
<div class="row">
<div class="col">
{{-- TODO: DataTables: dynamic data: shopping_locations --}}
<table id="shoppinglocations-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#shoppinglocations-table" href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Name') }}</th>
<th>{{ $__t('Description') }}</th>
@include('components.userfields_thead', array(
'userfields' => $userfields
))
@include('components.userfields_thead', [
'userfields' => $userfields,
])
</tr>
</thead>
<tbody class="d-none">
@foreach($shoppinglocations as $shoppinglocation)
<tr>
<td class="fit-content border-right">
<a class="btn btn-info btn-sm show-as-dialog-link"
href="{{ $U('/shoppinglocation/') }}{{ $shoppinglocation->id }}?embedded"
data-toggle="tooltip"
title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger btn-sm shoppinglocation-delete-button"
href="#"
data-shoppinglocation-id="{{ $shoppinglocation->id }}"
data-shoppinglocation-name="{{ $shoppinglocation->name }}"
data-toggle="tooltip"
title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
{{ $shoppinglocation->name }}
</td>
<td>
{{ $shoppinglocation->description }}
</td>
</tr>
</thead>
<tbody class="d-none">
@foreach ($shoppinglocations as $shoppinglocation)
<tr>
<td class="fit-content border-right">
<a class="btn btn-info btn-sm show-as-dialog-link"
href="{{ $U('/shoppinglocation/') }}{{ $shoppinglocation->id }}?embedded"
data-toggle="tooltip" title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger btn-sm shoppinglocation-delete-button" href="#"
data-shoppinglocation-id="{{ $shoppinglocation->id }}"
data-shoppinglocation-name="{{ $shoppinglocation->name }}" data-toggle="tooltip"
title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
{{ $shoppinglocation->name }}
</td>
<td>
{{ $shoppinglocation->description }}
</td>
@include('components.userfields_tbody', array(
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $shoppinglocation->id)
))
@include('components.userfields_tbody', [
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue(
$userfieldValues,
'object_id',
$shoppinglocation->id
),
])
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@stop

View File

@ -4,303 +4,273 @@
@section('viewJsName', 'stockentries')
@push('pageStyles')
<link href="{{ $U('/node_modules/animate.css/animate.min.css?v=', true) }}{{ $version }}"
rel="stylesheet">
<link href="{{ $U('/node_modules/animate.css/animate.min.css?v=', true) }}{{ $version }}" rel="stylesheet">
@endpush
@push('pageScripts')
<script src="{{ $U('/viewjs/purchase.js?v=', true) }}{{ $version }}"></script>
<script src="{{ $U('/viewjs/purchase.js?v=', true) }}{{ $version }}"></script>
@endpush
@section('content')
<div class="row">
<div class="col">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
</div>
</div>
</div>
<div class="row">
<div class="col">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button" data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
</div>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row collapse d-md-flex"
id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
@include('components.productpicker', array(
'products' => $products,
'disallowAllProductWorkflows' => true,
'isRequired' => false
))
</div>
<div class="col">
<div class="float-right mt-3">
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row collapse d-md-flex" id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
@include('components.productpicker', [
'productsQuery' => 'query%5B%5D=active%3D1&order=name%3Acollate%20nocase',
'disallowAllProductWorkflows' => true,
'isRequired' => false,
])
</div>
<div class="col">
<div class="float-right mt-3">
<a id="clear-filter-button" class="btn btn-sm btn-outline-info" href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row">
<div class="col">
<table id="stockentries-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#stockentries-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th class="d-none">Hidden product_id</th> <!-- This must be in the first column for searching -->
<th class="allow-grouping">{{ $__t('Product') }}</th>
<th>{{ $__t('Amount') }}</th>
<th class="@if(!GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING) d-none @endif allow-grouping">{{ $__t('Due date') }}</th>
<th class="@if(!GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) d-none @endif allow-grouping">{{ $__t('Location') }}</th>
<th class="@if(!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif allow-grouping">{{ $__t('Store') }}</th>
<th class="@if(!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif">{{ $__t('Price') }}</th>
<th class="allow-grouping"
data-shadow-rowgroup-column="9">{{ $__t('Purchased date') }}</th>
<th class="d-none">Hidden purchased_date</th>
<th>{{ $__t('Timestamp') }}</th>
<div class="row">
<div class="col">
{{-- TODO: DataTables: dynamic data: stock --}}
<table id="stockentries-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#stockentries-table" href="#"><i class="fas fa-eye"></i></a>
</th>
<th class="d-none">Hidden product_id</th>
<!-- This must be in the first column for searching -->
<th class="allow-grouping">{{ $__t('Product') }}</th>
<th>{{ $__t('Amount') }}</th>
<th class="@if (!GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING) d-none @endif allow-grouping">{{ $__t('Due date') }}
</th>
<th class="@if (!GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) d-none @endif allow-grouping">{{ $__t('Location') }}
</th>
<th class="@if (!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif allow-grouping">{{ $__t('Store') }}
</th>
<th class="@if (!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif">{{ $__t('Price') }}</th>
<th class="allow-grouping" data-shadow-rowgroup-column="9">{{ $__t('Purchased date') }}</th>
<th class="d-none">Hidden purchased_date</th>
<th>{{ $__t('Timestamp') }}</th>
@include('components.userfields_thead', array(
'userfields' => $userfields
))
</tr>
</thead>
<tbody class="d-none">
@foreach($stockEntries as $stockEntry)
<tr id="stock-{{ $stockEntry->id }}-row"
data-due-type="{{ FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->due_type }}"
class="@if(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && $stockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime('-1 days')) && $stockEntry->amount > 0) @if(FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->due_type == 1) table-secondary @else table-danger @endif @elseif(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && $stockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime('+' . $nextXDays . ' days'))
&&
$stockEntry->amount > 0) table-warning @endif">
<td class="fit-content border-right">
<a class="btn btn-danger btn-sm stock-consume-button"
href="#"
data-toggle="tooltip"
data-placement="left"
title="{{ $__t('Consume this stock entry') }}"
data-product-id="{{ $stockEntry->product_id }}"
data-stock-id="{{ $stockEntry->stock_id }}"
data-stockrow-id="{{ $stockEntry->id }}"
data-location-id="{{ $stockEntry->location_id }}"
data-product-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->name }}"
data-product-qu-name="{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->qu_id_stock)->name }}"
data-consume-amount="{{ $stockEntry->amount }}">
<i class="fas fa-utensils"></i>
</a>
@if(GROCY_FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING)
<a class="btn btn-success btn-sm product-open-button @if($stockEntry->open == 1 || FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->enable_tare_weight_handling == 1) disabled @endif"
href="#"
data-toggle="tooltip"
data-placement="left"
title="{{ $__t('Mark this stock entry as open') }}"
data-product-id="{{ $stockEntry->product_id }}"
data-product-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->name }}"
data-product-qu-name="{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->qu_id_stock)->name }}"
data-stock-id="{{ $stockEntry->stock_id }}"
data-stockrow-id="{{ $stockEntry->id }}">
<i class="fas fa-box-open"></i>
</a>
@endif
<a class="btn btn-info btn-sm show-as-dialog-link"
href="{{ $U('/stockentry/' . $stockEntry->id . '?embedded') }}"
data-toggle="tooltip"
data-placement="left"
title="{{ $__t('Edit stock entry') }}">
<i class="fas fa-edit"></i>
</a>
<div class="dropdown d-inline-block">
<button class="btn btn-sm btn-light text-secondary"
type="button"
data-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="dropdown-menu">
@if(GROCY_FEATURE_FLAG_SHOPPINGLIST)
<a class="dropdown-item show-as-dialog-link"
type="button"
href="{{ $U('/shoppinglistitem/new?embedded&updateexistingproduct&product=' . $stockEntry->product_id ) }}">
<i class="fas fa-shopping-cart"></i> {{ $__t('Add to shopping list') }}
</a>
<div class="dropdown-divider"></div>
@endif
<a class="dropdown-item show-as-dialog-link"
type="button"
href="{{ $U('/purchase?embedded&product=' . $stockEntry->product_id ) }}">
<i class="fas fa-cart-plus"></i> {{ $__t('Purchase') }}
</a>
<a class="dropdown-item show-as-dialog-link"
type="button"
href="{{ $U('/consume?embedded&product=' . $stockEntry->product_id . '&locationId=' . $stockEntry->location_id . '&stockId=' . $stockEntry->stock_id) }}">
<i class="fas fa-utensils"></i> {{ $__t('Consume') }}
</a>
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
<a class="dropdown-item show-as-dialog-link"
type="button"
href="{{ $U('/transfer?embedded&product=' . $stockEntry->product_id . '&locationId=' . $stockEntry->location_id . '&stockId=' . $stockEntry->stock_id) }}">
<i class="fas fa-exchange-alt"></i> {{ $__t('Transfer') }}
</a>
@endif
<a class="dropdown-item show-as-dialog-link"
type="button"
href="{{ $U('/inventory?embedded&product=' . $stockEntry->product_id ) }}">
<i class="fas fa-list"></i> {{ $__t('Inventory') }}
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item stock-consume-button stock-consume-button-spoiled"
type="button"
href="#"
data-product-id="{{ $stockEntry->product_id }}"
data-product-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->name }}"
data-product-qu-name="{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->qu_id_stock)->name }}"
data-stock-id="{{ $stockEntry->stock_id }}"
data-stockrow-id="{{ $stockEntry->id }}"
data-location-id="{{ $stockEntry->location_id }}"
data-consume-amount="1">
{{ $__t('Consume this stock entry as spoiled', '1 ' . FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->name) }}
</a>
@if(GROCY_FEATURE_FLAG_RECIPES)
<a class="dropdown-item"
type="button"
href="{{ $U('/recipes?search=') }}{{ FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->name }}">
{{ $__t('Search for recipes containing this product') }}
</a>
@endif
<div class="dropdown-divider"></div>
<a class="dropdown-item product-name-cell"
data-product-id="{{ $stockEntry->product_id }}"
type="button"
href="#">
{{ $__t('Product overview') }}
</a>
<a class="dropdown-item show-as-dialog-link"
type="button"
href="{{ $U('/stockjournal?embedded&product=') }}{{ $stockEntry->product_id }}">
{{ $__t('Stock journal') }}
</a>
<a class="dropdown-item show-as-dialog-link"
type="button"
href="{{ $U('/stockjournal/summary?embedded&product=') }}{{ $stockEntry->product_id }}">
{{ $__t('Stock journal summary') }}
</a>
<a class="dropdown-item link-return"
type="button"
data-href="{{ $U('/product/') }}{{ $stockEntry->product_id }}">
{{ $__t('Edit product') }}
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item"
type="button"
href="{{ $U('/stockentry/' . $stockEntry->id . '/grocycode?download=true') }}">
{!! str_replace('grocycode', '<span class="ls-n1">grocycode</span>', $__t('Download %s grocycode', $__t('Stock entry'))) !!}
</a>
@if(GROCY_FEATURE_FLAG_LABEL_PRINTER)
<a class="dropdown-item stockentry-grocycode-label-print"
data-stock-id="{{ $stockEntry->id }}"
type="button"
href="#">
{!! str_replace('grocycode', '<span class="ls-n1">grocycode</span>', $__t('Print %s grocycode on label printer', $__t('Stock entry'))) !!}
</a>
@endif
<a class="dropdown-item stockentry-label-link"
type="button"
target="_blank"
href="{{ $U('/stockentry/' . $stockEntry->id . '/label') }}">
{{ $__t('Open stock entry label in new window') }}
</a>
</div>
</div>
</td>
<td class="d-none"
data-product-id="{{ $stockEntry->product_id }}">
{{ $stockEntry->product_id }}
</td>
<td class="product-name-cell cursor-link"
data-product-id="{{ $stockEntry->product_id }}">
{{ FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->name }}
</td>
<td data-order="{{ $stockEntry->amount }}">
<span id="stock-{{ $stockEntry->id }}-amount"
class="locale-number locale-number-quantity-amount">{{ $stockEntry->amount }}</span> <span id="product-{{ $stockEntry->product_id }}-qu-name">{{ $__n($stockEntry->amount, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->qu_id_stock)->name_plural, true) }}</span>
<span id="stock-{{ $stockEntry->id }}-opened-amount"
class="small font-italic">@if($stockEntry->open == 1){{ $__t('Opened') }}@endif</span>
</td>
<td class="@if(!GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING) d-none @endif">
<span id="stock-{{ $stockEntry->id }}-due-date">{{ $stockEntry->best_before_date }}</span>
<time id="stock-{{ $stockEntry->id }}-due-date-timeago"
class="timeago timeago-contextual"
@if($stockEntry->best_before_date != "") datetime="{{ $stockEntry->best_before_date }} 23:59:59" @endif></time>
</td>
<td id="stock-{{ $stockEntry->id }}-location"
class="@if(!GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) d-none @endif"
data-location-id="{{ $stockEntry->location_id }}">
{{ FindObjectInArrayByPropertyValue($locations, 'id', $stockEntry->location_id)->name }}
</td>
<td id="stock-{{ $stockEntry->id }}-shopping-location"
class="@if(!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif"
data-shopping-location-id="{{ $stockEntry->shopping_location_id }}">
@if (FindObjectInArrayByPropertyValue($shoppinglocations, 'id', $stockEntry->shopping_location_id) !== null)
{{ FindObjectInArrayByPropertyValue($shoppinglocations, 'id', $stockEntry->shopping_location_id)->name }}
@endif
</td>
<td id="stock-{{ $stockEntry->id }}-price"
class="@if(!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif"
class="locale-number locale-number-currency"
data-price-id="{{ $stockEntry->price }}">
{{ $stockEntry->price }}
</td>
<td>
<span id="stock-{{ $stockEntry->id }}-purchased-date">{{ $stockEntry->purchased_date }}</span>
<time id="stock-{{ $stockEntry->id }}-purchased-date-timeago"
class="timeago timeago-contextual"
@if(!empty($stockEntry->purchased_date)) datetime="{{ $stockEntry->purchased_date }} 23:59:59" @endif></time>
</td>
<td class="d-none">{{ $stockEntry->purchased_date }}</td>
<td>
<span>{{ $stockEntry->row_created_timestamp }}</span>
<time class="timeago timeago-contextual"
datetime="{{ $stockEntry->row_created_timestamp }}"></time>
</td>
@include('components.userfields_thead', [
'userfields' => $userfields,
])
</tr>
</thead>
<tbody class="d-none">
@foreach ($stockEntries as $stockEntry)
<tr id="stock-{{ $stockEntry->id }}-row"
data-due-type="{{ FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->due_type }}"
class="@if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && $stockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime('-1 days')) && $stockEntry->amount > 0) @if (FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->due_type == 1) table-secondary @else table-danger @endif
@elseif(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && $stockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime('+' . $nextXDays . ' days')) && $stockEntry->amount > 0)
table-warning @endif">
<td class="fit-content border-right">
<a class="btn btn-danger btn-sm stock-consume-button" href="#" data-toggle="tooltip"
data-placement="left" title="{{ $__t('Consume this stock entry') }}"
data-product-id="{{ $stockEntry->product_id }}"
data-stock-id="{{ $stockEntry->stock_id }}"
data-stockrow-id="{{ $stockEntry->id }}"
data-location-id="{{ $stockEntry->location_id }}"
data-product-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->name }}"
data-product-qu-name="{{ FindObjectInArrayByPropertyValue($quantityunits,'id',FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->qu_id_stock)->name }}"
data-consume-amount="{{ $stockEntry->amount }}">
<i class="fas fa-utensils"></i>
</a>
@if (GROCY_FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING)
<a class="btn btn-success btn-sm product-open-button @if ($stockEntry->open == 1 || FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->enable_tare_weight_handling == 1) disabled @endif"
href="#" data-toggle="tooltip" data-placement="left"
title="{{ $__t('Mark this stock entry as open') }}"
data-product-id="{{ $stockEntry->product_id }}"
data-product-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->name }}"
data-product-qu-name="{{ FindObjectInArrayByPropertyValue($quantityunits,'id',FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->qu_id_stock)->name }}"
data-stock-id="{{ $stockEntry->stock_id }}"
data-stockrow-id="{{ $stockEntry->id }}">
<i class="fas fa-box-open"></i>
</a>
@endif
<a class="btn btn-info btn-sm show-as-dialog-link"
href="{{ $U('/stockentry/' . $stockEntry->id . '?embedded') }}" data-toggle="tooltip"
data-placement="left" title="{{ $__t('Edit stock entry') }}">
<i class="fas fa-edit"></i>
</a>
<div class="dropdown d-inline-block">
<button class="btn btn-sm btn-light text-secondary" type="button"
data-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="dropdown-menu">
@if (GROCY_FEATURE_FLAG_SHOPPINGLIST)
<a class="dropdown-item show-as-dialog-link" type="button"
href="{{ $U('/shoppinglistitem/new?embedded&updateexistingproduct&product=' . $stockEntry->product_id) }}">
<i class="fas fa-shopping-cart"></i> {{ $__t('Add to shopping list') }}
</a>
<div class="dropdown-divider"></div>
@endif
<a class="dropdown-item show-as-dialog-link" type="button"
href="{{ $U('/purchase?embedded&product=' . $stockEntry->product_id) }}">
<i class="fas fa-cart-plus"></i> {{ $__t('Purchase') }}
</a>
<a class="dropdown-item show-as-dialog-link" type="button"
href="{{ $U('/consume?embedded&product=' .$stockEntry->product_id .'&locationId=' .$stockEntry->location_id .'&stockId=' .$stockEntry->stock_id) }}">
<i class="fas fa-utensils"></i> {{ $__t('Consume') }}
</a>
@if (GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
<a class="dropdown-item show-as-dialog-link" type="button"
href="{{ $U('/transfer?embedded&product=' .$stockEntry->product_id .'&locationId=' .$stockEntry->location_id .'&stockId=' .$stockEntry->stock_id) }}">
<i class="fas fa-exchange-alt"></i> {{ $__t('Transfer') }}
</a>
@endif
<a class="dropdown-item show-as-dialog-link" type="button"
href="{{ $U('/inventory?embedded&product=' . $stockEntry->product_id) }}">
<i class="fas fa-list"></i> {{ $__t('Inventory') }}
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item stock-consume-button stock-consume-button-spoiled"
type="button" href="#" data-product-id="{{ $stockEntry->product_id }}"
data-product-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->name }}"
data-product-qu-name="{{ FindObjectInArrayByPropertyValue($quantityunits,'id',FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->qu_id_stock)->name }}"
data-stock-id="{{ $stockEntry->stock_id }}"
data-stockrow-id="{{ $stockEntry->id }}"
data-location-id="{{ $stockEntry->location_id }}" data-consume-amount="1">
{{ $__t('Consume this stock entry as spoiled','1 ' .FindObjectInArrayByPropertyValue($quantityunits,'id',FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->qu_id_stock)->name,FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->name) }}
</a>
@if (GROCY_FEATURE_FLAG_RECIPES)
<a class="dropdown-item" type="button"
href="{{ $U('/recipes?search=') }}{{ FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->name }}">
{{ $__t('Search for recipes containing this product') }}
</a>
@endif
<div class="dropdown-divider"></div>
<a class="dropdown-item product-name-cell"
data-product-id="{{ $stockEntry->product_id }}" type="button" href="#">
{{ $__t('Product overview') }}
</a>
<a class="dropdown-item show-as-dialog-link" type="button"
href="{{ $U('/stockjournal?embedded&product=') }}{{ $stockEntry->product_id }}">
{{ $__t('Stock journal') }}
</a>
<a class="dropdown-item show-as-dialog-link" type="button"
href="{{ $U('/stockjournal/summary?embedded&product=') }}{{ $stockEntry->product_id }}">
{{ $__t('Stock journal summary') }}
</a>
<a class="dropdown-item link-return" type="button"
data-href="{{ $U('/product/') }}{{ $stockEntry->product_id }}">
{{ $__t('Edit product') }}
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" type="button"
href="{{ $U('/stockentry/' . $stockEntry->id . '/grocycode?download=true') }}">
{!! str_replace('grocycode', '<span class="ls-n1">grocycode</span>', $__t('Download %s grocycode', $__t('Stock entry'))) !!}
</a>
@if (GROCY_FEATURE_FLAG_LABEL_PRINTER)
<a class="dropdown-item stockentry-grocycode-label-print"
data-stock-id="{{ $stockEntry->id }}" type="button" href="#">
{!! str_replace('grocycode', '<span class="ls-n1">grocycode</span>', $__t('Print %s grocycode on label printer', $__t('Stock entry'))) !!}
</a>
@endif
<a class="dropdown-item stockentry-label-link" type="button" target="_blank"
href="{{ $U('/stockentry/' . $stockEntry->id . '/label') }}">
{{ $__t('Open stock entry label in new window') }}
</a>
</div>
</div>
</td>
<td class="d-none" data-product-id="{{ $stockEntry->product_id }}">
{{ $stockEntry->product_id }}
</td>
<td class="product-name-cell cursor-link" data-product-id="{{ $stockEntry->product_id }}">
{{ FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->name }}
</td>
<td data-order="{{ $stockEntry->amount }}">
<span id="stock-{{ $stockEntry->id }}-amount"
class="locale-number locale-number-quantity-amount">{{ $stockEntry->amount }}</span>
<span
id="product-{{ $stockEntry->product_id }}-qu-name">{{ $__n($stockEntry->amount,FindObjectInArrayByPropertyValue($quantityunits,'id',FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->qu_id_stock)->name,FindObjectInArrayByPropertyValue($quantityunits,'id',FindObjectInArrayByPropertyValue($products, 'id', $stockEntry->product_id)->qu_id_stock)->name_plural,true) }}</span>
<span id="stock-{{ $stockEntry->id }}-opened-amount" class="small font-italic">
@if ($stockEntry->open == 1)
{{ $__t('Opened') }}@endif
</span>
</td>
<td class="@if (!GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING) d-none @endif">
<span
id="stock-{{ $stockEntry->id }}-due-date">{{ $stockEntry->best_before_date }}</span>
<time id="stock-{{ $stockEntry->id }}-due-date-timeago"
class="timeago timeago-contextual"
@if ($stockEntry->best_before_date != '') datetime="{{ $stockEntry->best_before_date }} 23:59:59" @endif></time>
</td>
<td id="stock-{{ $stockEntry->id }}-location"
class="@if (!GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) d-none @endif"
data-location-id="{{ $stockEntry->location_id }}">
{{ FindObjectInArrayByPropertyValue($locations, 'id', $stockEntry->location_id)->name }}
</td>
<td id="stock-{{ $stockEntry->id }}-shopping-location"
class="@if (!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif"
data-shopping-location-id="{{ $stockEntry->shopping_location_id }}">
@if (FindObjectInArrayByPropertyValue($shoppinglocations, 'id', $stockEntry->shopping_location_id) !== null)
{{ FindObjectInArrayByPropertyValue($shoppinglocations, 'id', $stockEntry->shopping_location_id)->name }}
@endif
</td>
<td id="stock-{{ $stockEntry->id }}-price"
class="@if (!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif"
class="locale-number locale-number-currency" data-price-id="{{ $stockEntry->price }}">
{{ $stockEntry->price }}
</td>
<td>
<span
id="stock-{{ $stockEntry->id }}-purchased-date">{{ $stockEntry->purchased_date }}</span>
<time id="stock-{{ $stockEntry->id }}-purchased-date-timeago"
class="timeago timeago-contextual"
@if (!empty($stockEntry->purchased_date)) datetime="{{ $stockEntry->purchased_date }} 23:59:59" @endif></time>
</td>
<td class="d-none">{{ $stockEntry->purchased_date }}</td>
<td>
<span>{{ $stockEntry->row_created_timestamp }}</span>
<time class="timeago timeago-contextual"
datetime="{{ $stockEntry->row_created_timestamp }}"></time>
</td>
@include('components.userfields_tbody', array(
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $stockEntry->product_id)
))
@include('components.userfields_tbody', [
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue(
$userfieldValues,
'object_id',
$stockEntry->product_id
),
])
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<div class="modal fade"
id="productcard-modal"
tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-center">
<div class="modal-body">
@include('components.productcard')
</div>
<div class="modal-footer">
<button type="button"
class="btn btn-secondary"
data-dismiss="modal">{{ $__t('Close') }}</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="productcard-modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-center">
<div class="modal-body">
@include('components.productcard')
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ $__t('Close') }}</button>
</div>
</div>
</div>
</div>
@stop

View File

@ -5,198 +5,180 @@
@section('viewJsName', 'stockjournal')
@section('content')
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3 hide-when-embedded"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
id="related-links">
<a class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/stockjournal/summary') }}">
{{ $__t('Journal summary') }}
</a>
</div>
</div>
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button" data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3 hide-when-embedded" type="button"
data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100" id="related-links">
<a class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/stockjournal/summary') }}">
{{ $__t('Journal summary') }}
</a>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row collapse d-md-flex"
id="table-filter-row">
<div class="col-12 col-md-6 col-xl-2">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text"
id="search"
class="form-control"
placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Product') }}</span>
</div>
<select class="custom-control custom-select"
id="product-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach($products as $product)
<option value="{{ $product->id }}">{{ $product->name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Transaction type') }}</span>
</div>
<select class="custom-control custom-select"
id="transaction-type-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach($transactionTypes as $transactionType)
<option value="{{ $transactionType }}">{{ $__t($transactionType) }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Location') }}</span>
</div>
<select class="custom-control custom-select"
id="location-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach($locations as $location)
<option value="{{ $location->id }}">{{ $location->name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-12 col-md-6 col-xl-2 mt-1">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('User') }}</span>
</div>
<select class="custom-control custom-select"
id="user-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach($users as $user)
<option value="{{ $user->id }}">{{ $user->display_name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-12 col-md-6 col-xl-3 mt-1">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-clock"></i>&nbsp;{{ $__t('Date range') }}</span>
</div>
<select class="custom-control custom-select"
id="daterange-filter">
<option value="1">{{ $__n(1, '%s month', '%s months') }}</option>
<option value="6"
selected>{{ $__n(6, '%s month', '%s months') }}</option>
<option value="12">{{ $__n(1, '%s year', '%s years') }}</option>
<option value="24">{{ $__n(2, '%s month', '%s years') }}</option>
<option value="9999">{{ $__t('All') }}</option>
</select>
</div>
</div>
<div class="col">
<div class="float-right mt-1">
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row collapse d-md-flex" id="table-filter-row">
<div class="col-12 col-md-6 col-xl-2">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text" id="search" class="form-control" placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Product') }}</span>
</div>
<select class="select2 custom-control custom-select" id="product-filter">
<option value="all">{{ $__t('All') }}</option>
</select>
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i
class="fas fa-filter"></i>&nbsp;{{ $__t('Transaction type') }}</span>
</div>
{{-- TODO: Select2: static data --}}
<select class="custom-control custom-select" id="transaction-type-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach ($transactionTypes as $transactionType)
<option value="{{ $transactionType }}">{{ $__t($transactionType) }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Location') }}</span>
</div>
{{-- TODO: Select2: dynamic data: locations --}}
<select class="custom-control custom-select" id="location-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach ($locations as $location)
<option value="{{ $location->id }}">{{ $location->name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-12 col-md-6 col-xl-2 mt-1">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('User') }}</span>
</div>
{{-- TODO: Select2: dynamic data: users --}}
<select class="custom-control custom-select" id="user-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach ($users as $user)
<option value="{{ $user->id }}">{{ $user->display_name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-12 col-md-6 col-xl-3 mt-1">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-clock"></i>&nbsp;{{ $__t('Date range') }}</span>
</div>
<select class="custom-control custom-select" id="daterange-filter">
<option value="1">{{ $__n(1, '%s month', '%s months') }}</option>
<option value="6" selected>{{ $__n(6, '%s month', '%s months') }}</option>
<option value="12">{{ $__n(1, '%s year', '%s years') }}</option>
<option value="24">{{ $__n(2, '%s month', '%s years') }}</option>
<option value="9999">{{ $__t('All') }}</option>
</select>
</div>
</div>
<div class="col">
<div class="float-right mt-1">
<a id="clear-filter-button" class="btn btn-sm btn-outline-info" href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col">
<table id="stock-journal-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#stock-journal-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th class="allow-grouping">{{ $__t('Product') }}</th>
<th>{{ $__t('Amount') }}</th>
<th>{{ $__t('Transaction time') }}</th>
<th class="allow-grouping">{{ $__t('Transaction type') }}</th>
<th class="@if(!GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) d-none @endif allow-grouping">{{ $__t('Location') }}</th>
<th class="allow-grouping">{{ $__t('Done by') }}</th>
</tr>
</thead>
<tbody class="d-none">
@foreach($stockLog as $stockLogEntry)
<tr id="stock-booking-{{ $stockLogEntry->id }}-row"
class="@if($stockLogEntry->undone == 1) text-muted @endif stock-booking-correlation-{{ $stockLogEntry->correlation_id }}"
data-correlation-id="{{ $stockLogEntry->correlation_id }}">
<td class="fit-content border-right">
<a class="btn btn-secondary btn-xs undo-stock-booking-button @if($stockLogEntry->undone == 1) disabled @endif"
href="#"
data-booking-id="{{ $stockLogEntry->id }}"
data-toggle="tooltip"
data-placement="left"
title="{{ $__t('Undo transaction') }}">
<i class="fas fa-undo"></i>
</a>
</td>
<td>
<span class="name-anchor @if($stockLogEntry->undone == 1) text-strike-through @endif">{{ $stockLogEntry->product_name }}</span>
@if($stockLogEntry->undone == 1)
<br>
{{ $__t('Undone on') . ' ' . $stockLogEntry->undone_timestamp }}
<time class="timeago timeago-contextual"
datetime="{{ $stockLogEntry->undone_timestamp }}"></time>
@endif
</td>
<td>
<span class="locale-number locale-number-quantity-amount">{{ $stockLogEntry->amount }}</span> {{ $__n($stockLogEntry->amount, $stockLogEntry->qu_name, $stockLogEntry->qu_name_plural, true) }}
</td>
<td>
{{ $stockLogEntry->row_created_timestamp }}
<time class="timeago timeago-contextual"
datetime="{{ $stockLogEntry->row_created_timestamp }}"></time>
</td>
<td>
{{ $__t($stockLogEntry->transaction_type) }}
@if ($stockLogEntry->spoiled == 1)
<span class="font-italic text-muted">{{ $__t('Spoiled') }}</span>
@endif
</td>
<td class="@if(!GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) d-none @endif">
{{ $stockLogEntry->location_name }}
</td>
<td>
{{ $stockLogEntry->user_display_name }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<div class="row mt-2">
<div class="col">
{{-- TODO: DataTables: dynamic data: uihelper_stock_journal --}}
<table id="stock-journal-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#stock-journal-table" href="#"><i class="fas fa-eye"></i></a>
</th>
<th class="allow-grouping">{{ $__t('Product') }}</th>
<th>{{ $__t('Amount') }}</th>
<th>{{ $__t('Transaction time') }}</th>
<th class="allow-grouping">{{ $__t('Transaction type') }}</th>
<th class="@if (!GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) d-none @endif allow-grouping">{{ $__t('Location') }}
</th>
<th class="allow-grouping">{{ $__t('Done by') }}</th>
</tr>
</thead>
<tbody class="d-none">
@foreach ($stockLog as $stockLogEntry)
<tr id="stock-booking-{{ $stockLogEntry->id }}-row"
class="@if ($stockLogEntry->undone == 1) text-muted @endif stock-booking-correlation-{{ $stockLogEntry->correlation_id }}"
data-correlation-id="{{ $stockLogEntry->correlation_id }}">
<td class="fit-content border-right">
<a class="btn btn-secondary btn-xs undo-stock-booking-button @if ($stockLogEntry->undone == 1) disabled @endif"
href="#" data-booking-id="{{ $stockLogEntry->id }}" data-toggle="tooltip"
data-placement="left" title="{{ $__t('Undo transaction') }}">
<i class="fas fa-undo"></i>
</a>
</td>
<td>
<span
class="name-anchor @if ($stockLogEntry->undone == 1) text-strike-through @endif">{{ $stockLogEntry->product_name }}</span>
@if ($stockLogEntry->undone == 1)
<br>
{{ $__t('Undone on') . ' ' . $stockLogEntry->undone_timestamp }}
<time class="timeago timeago-contextual"
datetime="{{ $stockLogEntry->undone_timestamp }}"></time>
@endif
</td>
<td>
<span
class="locale-number locale-number-quantity-amount">{{ $stockLogEntry->amount }}</span>
{{ $__n($stockLogEntry->amount, $stockLogEntry->qu_name, $stockLogEntry->qu_name_plural, true) }}
</td>
<td>
{{ $stockLogEntry->row_created_timestamp }}
<time class="timeago timeago-contextual"
datetime="{{ $stockLogEntry->row_created_timestamp }}"></time>
</td>
<td>
{{ $__t($stockLogEntry->transaction_type) }}
@if ($stockLogEntry->spoiled == 1)
<span class="font-italic text-muted">{{ $__t('Spoiled') }}</span>
@endif
</td>
<td class="@if (!GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) d-none @endif">
{{ $stockLogEntry->location_name }}
</td>
<td>
{{ $stockLogEntry->user_display_name }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@stop

View File

@ -5,127 +5,117 @@
@section('viewJsName', 'stockjournalsummary')
@section('content')
<div class="row">
<div class="col">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
</div>
</div>
</div>
<div class="row">
<div class="col">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button" data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
</div>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row collapse d-md-flex"
id="table-filter-row">
<div class="col-12 col-md-6 col-xl-2">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text"
id="search"
class="form-control"
placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Product') }}</span>
</div>
<select class="custom-control custom-select"
id="product-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach($products as $product)
<option value="{{ $product->id }}">{{ $product->name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Transaction type') }}</span>
</div>
<select class="custom-control custom-select"
id="transaction-type-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach($transactionTypes as $transactionType)
<option value="{{ $transactionType }}">{{ $__t($transactionType) }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('User') }}</span>
</div>
<select class="custom-control custom-select"
id="user-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach($users as $user)
<option value="{{ $user->id }}">{{ $user->display_name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col">
<div class="float-right mt-1">
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row collapse d-md-flex" id="table-filter-row">
<div class="col-12 col-md-6 col-xl-2">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text" id="search" class="form-control" placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Product') }}</span>
</div>
{{-- TODO: Select2: dynamic data: products --}}
<select class="select2 custom-control custom-select" id="product-filter">
<option value="all">{{ $__t('All') }}</option>
</select>
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i
class="fas fa-filter"></i>&nbsp;{{ $__t('Transaction type') }}</span>
</div>
{{-- TODO: Select2: static data --}}
<select class="custom-control custom-select" id="transaction-type-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach ($transactionTypes as $transactionType)
<option value="{{ $transactionType }}">{{ $__t($transactionType) }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('User') }}</span>
</div>
{{-- TODO: Select2: dynamic data: users --}}
<select class="custom-control custom-select" id="user-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach ($users as $user)
<option value="{{ $user->id }}">{{ $user->display_name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col">
<div class="float-right mt-1">
<a id="clear-filter-button" class="btn btn-sm btn-outline-info" href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col">
<table id="stock-journal-summary-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#stock-journal-summary-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th class="allow-grouping">{{ $__t('Product') }}</th>
<th class="allow-grouping">{{ $__t('Transaction type') }}</th>
<th class="allow-grouping">{{ $__t('User') }}</th>
<th>{{ $__t('Amount') }}</th>
</tr>
</thead>
<tbody class="d-none">
@foreach($entries as $journalEntry)
<tr>
<td class="fit-content border-right"></td>
<td>
{{ $journalEntry->product_name }}
</td>
<td>
{{ $__t($journalEntry->transaction_type) }}
</td>
<td>
{{ $journalEntry->user_display_name }}
</td>
<td>
<span class="locale-number locale-number-quantity-amount">{{ $journalEntry->amount }}</span> {{ $__n($journalEntry->amount, $journalEntry->qu_name, $journalEntry->qu_name_plural, true) }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<div class="row mt-2">
<div class="col">
{{-- TODO: DataTables: dynamic data: uihelper_stock_journal_summary --}}
<table id="stock-journal-summary-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#stock-journal-summary-table" href="#"><i
class="fas fa-eye"></i></a>
</th>
<th class="allow-grouping">{{ $__t('Product') }}</th>
<th class="allow-grouping">{{ $__t('Transaction type') }}</th>
<th class="allow-grouping">{{ $__t('User') }}</th>
<th>{{ $__t('Amount') }}</th>
</tr>
</thead>
<tbody class="d-none">
@foreach ($entries as $journalEntry)
<tr>
<td class="fit-content border-right"></td>
<td>
{{ $journalEntry->product_name }}
</td>
<td>
{{ $__t($journalEntry->transaction_type) }}
</td>
<td>
{{ $journalEntry->user_display_name }}
</td>
<td>
<span
class="locale-number locale-number-quantity-amount">{{ $journalEntry->amount }}</span>
{{ $__n($journalEntry->amount, $journalEntry->qu_name, $journalEntry->qu_name_plural, true) }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@stop

View File

@ -5,446 +5,438 @@
@section('viewJsName', 'stockoverview')
@push('pageStyles')
<link href="{{ $U('/node_modules/animate.css/animate.min.css?v=', true) }}{{ $version }}"
rel="stylesheet">
<link href="{{ $U('/node_modules/animate.css/animate.min.css?v=', true) }}{{ $version }}" rel="stylesheet">
@endpush
@push('pageScripts')
<script src="{{ $U('/viewjs/purchase.js?v=', true) }}{{ $version }}"></script>
<script src="{{ $U('/viewjs/purchase.js?v=', true) }}{{ $version }}"></script>
@endpush
@section('content')
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title mr-2 order-0">
@yield('title')
</h2>
<h2 class="mb-0 mr-auto order-3 order-md-1 width-xs-sm-100">
<span id="info-current-stock"
class="text-muted small"></span>
</h2>
<button class="btn btn-outline-dark d-md-none mt-2 float-right order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
id="related-links">
<a class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/stockjournal') }}">
{{ $__t('Journal') }}
</a>
<a class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/stockentries') }}">
{{ $__t('Stock entries') }}
</a>
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
<a class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/locationcontentsheet') }}">
{{ $__t('Location Content Sheet') }}
</a>
@endif
</div>
</div>
<div class="border-top border-bottom my-2 py-1">
@if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
<div id="info-expired-products"
data-status-filter="expired"
class="error-message status-filter-message responsive-button mr-2"></div>
<div id="info-overdue-products"
data-status-filter="overdue"
class="secondary-message status-filter-message responsive-button mr-2"></div>
<div id="info-duesoon-products"
data-next-x-days="{{ $nextXDays }}"
data-status-filter="duesoon"
class="warning-message status-filter-message responsive-button mr-2"></div>
@endif
<div id="info-missing-products"
data-status-filter="belowminstockamount"
class="normal-message status-filter-message responsive-button"></div>
<div class="float-right">
<a class="btn btn-sm btn-outline-info d-md-none mt-1"
data-toggle="collapse"
href="#table-filter-row"
role="button">
<i class="fas fa-filter"></i>
</a>
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info mt-1"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
</div>
<div class="row collapse d-md-flex"
id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text"
id="search"
class="form-control"
placeholder="{{ $__t('Search') }}">
</div>
</div>
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Location') }}</span>
</div>
<select class="custom-control custom-select"
id="location-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach($locations as $location)
<option value="{{ $location->name }}">{{ $location->name }}</option>
@endforeach
</select>
</div>
</div>
@endif
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Product group') }}</span>
</div>
<select class="custom-control custom-select"
id="product-group-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach($productGroups as $productGroup)
<option value="{{ $productGroup->name }}">{{ $productGroup->name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Status') }}</span>
</div>
<select class="custom-control custom-select"
id="status-filter">
<option class="bg-white"
value="all">{{ $__t('All') }}</option>
@if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
<option value="duesoon">{{ $__t('Due soon') }}</option>
<option value="overdue">{{ $__t('Overdue') }}</option>
<option value="expired">{{ $__t('Expired') }}</option>
@endif
<option value="belowminstockamount">{{ $__t('Below min. stock amount') }}</option>
<option value="instockX">{{ $__t('In-stock products') }}</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title mr-2 order-0">
@yield('title')
</h2>
<h2 class="mb-0 mr-auto order-3 order-md-1 width-xs-sm-100">
<span id="info-current-stock" class="text-muted small"></span>
</h2>
<button class="btn btn-outline-dark d-md-none mt-2 float-right order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100" id="related-links">
<a class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/stockjournal') }}">
{{ $__t('Journal') }}
</a>
<a class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/stockentries') }}">
{{ $__t('Stock entries') }}
</a>
@if (GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
<a class="btn btn-outline-dark responsive-button m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/locationcontentsheet') }}">
{{ $__t('Location Content Sheet') }}
</a>
@endif
</div>
</div>
<div class="border-top border-bottom my-2 py-1">
@if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
<div id="info-expired-products" data-status-filter="expired"
class="error-message status-filter-message responsive-button mr-2"></div>
<div id="info-overdue-products" data-status-filter="overdue"
class="secondary-message status-filter-message responsive-button mr-2"></div>
<div id="info-duesoon-products" data-next-x-days="{{ $nextXDays }}" data-status-filter="duesoon"
class="warning-message status-filter-message responsive-button mr-2"></div>
@endif
<div id="info-missing-products" data-status-filter="belowminstockamount"
class="normal-message status-filter-message responsive-button"></div>
<div class="float-right">
<a class="btn btn-sm btn-outline-info d-md-none mt-1" data-toggle="collapse" href="#table-filter-row"
role="button">
<i class="fas fa-filter"></i>
</a>
<a id="clear-filter-button" class="btn btn-sm btn-outline-info mt-1" href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
</div>
<div class="row collapse d-md-flex" id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text" id="search" class="form-control" placeholder="{{ $__t('Search') }}">
</div>
</div>
@if (GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Location') }}</span>
</div>
{{-- TODO: Select2: dynamic data: locations --}}
<select class="custom-control custom-select" id="location-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach ($locations as $location)
<option value="{{ $location->name }}">{{ $location->name }}</option>
@endforeach
</select>
</div>
</div>
@endif
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Product group') }}</span>
</div>
{{-- TODO: Select2: dynamic data: product_groups --}}
<select class="custom-control custom-select" id="product-group-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach ($productGroups as $productGroup)
<option value="{{ $productGroup->name }}">{{ $productGroup->name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Status') }}</span>
</div>
<select class="custom-control custom-select" id="status-filter">
<option class="bg-white" value="all">{{ $__t('All') }}</option>
@if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
<option value="duesoon">{{ $__t('Due soon') }}</option>
<option value="overdue">{{ $__t('Overdue') }}</option>
<option value="expired">{{ $__t('Expired') }}</option>
@endif
<option value="belowminstockamount">{{ $__t('Below min. stock amount') }}</option>
<option value="instockX">{{ $__t('In-stock products') }}</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col">
<table id="stock-overview-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#stock-overview-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Product') }}</th>
<th class="allow-grouping">{{ $__t('Product group') }}</th>
<th>{{ $__t('Amount') }}</th>
<th class="@if(!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif">{{ $__t('Value') }}</th>
<th class="@if(!GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING) d-none @endif allow-grouping">{{ $__t('Next due date') }}</th>
<th class="d-none">Hidden location</th>
<th class="d-none">Hidden status</th>
<th class="d-none">Hidden product group</th>
<th>{{ $__t('Calories') }} ({{ $__t('Per stock quantity unit') }})</th>
<th>{{ $__t('Calories') }}</th>
<th class="allow-grouping">{{ $__t('Last purchased') }}</th>
<th class="@if(!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif">{{ $__t('Last price') }}</th>
<th class="allow-grouping">{{ $__t('Min. stock amount') }}</th>
<th>{{ $__t('Product description') }}</th>
<th class="allow-grouping">{{ $__t('Parent product') }}</th>
<th class="allow-grouping">{{ $__t('Default location') }}</th>
<th>{{ $__t('Product picture') }}</th>
<th>{{ $__t('Average price') }}</th>
<div class="row">
<div class="col">
{{-- TODO: DataTables: dynamic data: uihelper_stock_current_overview --}}
<table id="stock-overview-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#stock-overview-table" href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Product') }}</th>
<th class="allow-grouping">{{ $__t('Product group') }}</th>
<th>{{ $__t('Amount') }}</th>
<th class="@if (!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif">{{ $__t('Value') }}</th>
<th class="@if (!GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING) d-none @endif allow-grouping">
{{ $__t('Next due date') }}</th>
<th class="d-none">Hidden location</th>
<th class="d-none">Hidden status</th>
<th class="d-none">Hidden product group</th>
<th>{{ $__t('Calories') }} ({{ $__t('Per stock quantity unit') }})</th>
<th>{{ $__t('Calories') }}</th>
<th class="allow-grouping">{{ $__t('Last purchased') }}</th>
<th class="@if (!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif">{{ $__t('Last price') }}</th>
<th class="allow-grouping">{{ $__t('Min. stock amount') }}</th>
<th>{{ $__t('Product description') }}</th>
<th class="allow-grouping">{{ $__t('Parent product') }}</th>
<th class="allow-grouping">{{ $__t('Default location') }}</th>
<th>{{ $__t('Product picture') }}</th>
<th>{{ $__t('Average price') }}</th>
@include('components.userfields_thead', array(
'userfields' => $userfields
))
@include('components.userfields_thead', [
'userfields' => $userfields,
])
</tr>
</thead>
<tbody class="d-none">
@foreach($currentStock as $currentStockEntry)
<tr id="product-{{ $currentStockEntry->product_id }}-row"
class="@if(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && $currentStockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime('-1 days')) && $currentStockEntry->amount > 0) @if($currentStockEntry->due_type == 1) table-secondary @else table-danger @endif @elseif(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && $currentStockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime('+' . $nextXDays . ' days')) && $currentStockEntry->amount > 0) table-warning @elseif ($currentStockEntry->product_missing) table-info @endif">
<td class="fit-content border-right">
<a class="permission-STOCK_CONSUME btn btn-success btn-sm product-consume-button @if($currentStockEntry->amount_aggregated < $currentStockEntry->quick_consume_amount || $currentStockEntry->enable_tare_weight_handling == 1) disabled @endif"
href="#"
data-toggle="tooltip"
data-placement="left"
title="{{ $__t('Consume %1$s of %2$s', floatval($currentStockEntry->quick_consume_amount) . ' ' . $currentStockEntry->qu_unit_name, $currentStockEntry->product_name) }}"
data-product-id="{{ $currentStockEntry->product_id }}"
data-product-name="{{ $currentStockEntry->product_name }}"
data-product-qu-name="{{ $currentStockEntry->qu_unit_name }}"
data-consume-amount="{{ $currentStockEntry->quick_consume_amount }}">
<i class="fas fa-utensils"></i> <span class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->quick_consume_amount }}</span>
</a>
<a id="product-{{ $currentStockEntry->product_id }}-consume-all-button"
class="permission-STOCK_CONSUME btn btn-danger btn-sm product-consume-button @if($currentStockEntry->amount_aggregated == 0) disabled @endif"
href="#"
data-toggle="tooltip"
data-placement="right"
title="{{ $__t('Consume all %s which are currently in stock', $currentStockEntry->product_name) }}"
data-product-id="{{ $currentStockEntry->product_id }}"
data-product-name="{{ $currentStockEntry->product_name }}"
data-product-qu-name="{{ $currentStockEntry->qu_unit_name }}"
data-consume-amount="@if($currentStockEntry->enable_tare_weight_handling == 1){{$currentStockEntry->tare_weight}}@else{{$currentStockEntry->amount}}@endif"
data-original-total-stock-amount="{{$currentStockEntry->amount}}">
<i class="fas fa-utensils"></i> {{ $__t('All') }}
</a>
@if(GROCY_FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING)
<a class="btn btn-success btn-sm product-open-button @if($currentStockEntry->amount_aggregated < $currentStockEntry->quick_consume_amount || $currentStockEntry->amount_aggregated == $currentStockEntry->amount_opened_aggregated || $currentStockEntry->enable_tare_weight_handling == 1) disabled @endif"
href="#"
data-toggle="tooltip"
data-placement="left"
title="{{ $__t('Mark %1$s of %2$s as open', floatval($currentStockEntry->quick_consume_amount) . ' ' . $currentStockEntry->qu_unit_name, $currentStockEntry->product_name) }}"
data-product-id="{{ $currentStockEntry->product_id }}"
data-product-name="{{ $currentStockEntry->product_name }}"
data-product-qu-name="{{ $currentStockEntry->qu_unit_name }}"
data-open-amount="{{ $currentStockEntry->quick_consume_amount }}">
<i class="fas fa-box-open"></i> <span class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->quick_consume_amount }}</span>
</a>
@endif
<div class="dropdown d-inline-block">
<button class="btn btn-sm btn-light text-secondary"
type="button"
data-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="table-inline-menu dropdown-menu dropdown-menu-right">
@if(GROCY_FEATURE_FLAG_SHOPPINGLIST)
<a class="dropdown-item show-as-dialog-link permission-SHOPPINGLIST_ITEMS_ADD"
type="button"
href="{{ $U('/shoppinglistitem/new?embedded&updateexistingproduct&product=' . $currentStockEntry->product_id ) }}">
<span class="dropdown-item-icon"><i class="fas fa-shopping-cart"></i></span> <span class="dropdown-item-text">{{ $__t('Add to shopping list') }}</span>
</a>
<div class="dropdown-divider"></div>
@endif
<a class="dropdown-item show-as-dialog-link permission-STOCK_PURCHASE"
type="button"
href="{{ $U('/purchase?embedded&product=' . $currentStockEntry->product_id ) }}">
<span class="dropdown-item-icon"><i class="fas fa-cart-plus"></i></span> <span class="dropdown-item-text">{{ $__t('Purchase') }}</span>
</a>
<a class="dropdown-item show-as-dialog-link permission-STOCK_CONSUME @if($currentStockEntry->amount_aggregated <= 0) disabled @endif"
type="button"
href="{{ $U('/consume?embedded&product=' . $currentStockEntry->product_id ) }}">
<span class="dropdown-item-icon"><i class="fas fa-utensils"></i></span> <span class="dropdown-item-text">{{ $__t('Consume') }}</span>
</a>
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
<a class="dropdown-item show-as-dialog-link permission-STOCK_TRANSFER @if($currentStockEntry->amount <= 0) disabled @endif"
type="button"
href="{{ $U('/transfer?embedded&product=' . $currentStockEntry->product_id) }}">
<span class="dropdown-item-icon"><i class="fas fa-exchange-alt"></i></span> <span class="dropdown-item-text">{{ $__t('Transfer') }}</span>
</a>
@endif
<a class="dropdown-item show-as-dialog-link permission-STOCK_INVENTORY"
type="button"
href="{{ $U('/inventory?embedded&product=' . $currentStockEntry->product_id ) }}">
<span class="dropdown-item-icon"><i class="fas fa-list"></i></span> <span class="dropdown-item-text">{{ $__t('Inventory') }}</span>
</a>
@if(GROCY_FEATURE_FLAG_RECIPES)
<a class="dropdown-item"
type="button"
href="{{ $U('/recipes?search=') }}{{ $currentStockEntry->product_name }}">
<span class="dropdown-item-text">{{ $__t('Search for recipes containing this product') }}</span>
</a>
@endif
<div class="dropdown-divider"></div>
<a class="dropdown-item product-name-cell"
data-product-id="{{ $currentStockEntry->product_id }}"
type="button"
href="#">
<span class="dropdown-item-text">{{ $__t('Product overview') }}</span>
</a>
<a class="dropdown-item show-as-dialog-link"
type="button"
href="{{ $U('/stockentries?embedded&product=') }}{{ $currentStockEntry->product_id }}"
data-product-id="{{ $currentStockEntry->product_id }}">
<span class="dropdown-item-text">{{ $__t('Stock entries') }}</span>
</a>
<a class="dropdown-item show-as-dialog-link"
type="button"
href="{{ $U('/stockjournal?embedded&product=') }}{{ $currentStockEntry->product_id }}">
<span class="dropdown-item-text">{{ $__t('Stock journal') }}</span>
</a>
<a class="dropdown-item show-as-dialog-link"
type="button"
href="{{ $U('/stockjournal/summary?embedded&product_id=') }}{{ $currentStockEntry->product_id }}">
<span class="dropdown-item-text">{{ $__t('Stock journal summary') }}</span>
</a>
<a class="dropdown-item permission-MASTER_DATA_EDIT link-return"
type="button"
data-href="{{ $U('/product/') }}{{ $currentStockEntry->product_id }}">
<span class="dropdown-item-text">{{ $__t('Edit product') }}</span>
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item"
type="button"
href="{{ $U('/product/' . $currentStockEntry->product_id . '/grocycode?download=true') }}">
{!! str_replace('grocycode', '<span class="ls-n1">grocycode</span>', $__t('Download %s grocycode', $__t('Product'))) !!}
</a>
@if(GROCY_FEATURE_FLAG_LABEL_PRINTER)
<a class="dropdown-item product-grocycode-label-print"
data-product-id="{{ $currentStockEntry->product_id }}"
type="button"
href="#">
{!! str_replace('grocycode', '<span class="ls-n1">grocycode</span>', $__t('Print %s grocycode on label printer', $__t('Product'))) !!}
</a>
@endif
</div>
</div>
</td>
<td class="product-name-cell cursor-link"
data-product-id="{{ $currentStockEntry->product_id }}">
{{ $currentStockEntry->product_name }}
<span class="d-none">{{ $currentStockEntry->product_barcodes }}</span>
</td>
<td>
@if($currentStockEntry->product_group_name !== null){{ $currentStockEntry->product_group_name }}@endif
</td>
<td data-order="{{ $currentStockEntry->amount }}">
<span id="product-{{ $currentStockEntry->product_id }}-amount"
class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->amount }}</span> <span id="product-{{ $currentStockEntry->product_id }}-qu-name">{{ $__n($currentStockEntry->amount, $currentStockEntry->qu_unit_name, $currentStockEntry->qu_unit_name_plural) }}</span>
<span id="product-{{ $currentStockEntry->product_id }}-opened-amount"
class="small font-italic">@if($currentStockEntry->amount_opened > 0){{ $__t('%s opened', $currentStockEntry->amount_opened) }}@endif</span>
@if($currentStockEntry->is_aggregated_amount == 1)
<span class="pl-1 text-secondary">
<i class="fas fa-custom-sigma-sign"></i> <span id="product-{{ $currentStockEntry->product_id }}-amount-aggregated"
class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->amount_aggregated }}</span> {{ $__n($currentStockEntry->amount_aggregated, $currentStockEntry->qu_unit_name, $currentStockEntry->qu_unit_name_plural, true) }}
@if($currentStockEntry->amount_opened_aggregated > 0)<span id="product-{{ $currentStockEntry->product_id }}-opened-amount-aggregated"
class="small font-italic">{{ $__t('%s opened', $currentStockEntry->amount_opened_aggregated) }}</span>@endif
</span>
@endif
@if(boolval($userSettings['show_icon_on_stock_overview_page_when_product_is_on_shopping_list']))
@if($currentStockEntry->on_shopping_list)
<span class="text-muted cursor-normal"
data-toggle="tooltip"
title="{{ $__t('This product is currently on a shopping list') }}">
<i class="fas fa-shopping-cart"></i>
</span>
@endif
@endif
</td>
<td>
<span id="product-{{ $currentStockEntry->product_id }}-value"
class="locale-number locale-number-currency">{{ $currentStockEntry->value }}</span>
</td>
<td class="@if(!GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING) d-none @endif">
<span id="product-{{ $currentStockEntry->product_id }}-next-due-date">{{ $currentStockEntry->best_before_date }}</span>
<time id="product-{{ $currentStockEntry->product_id }}-next-due-date-timeago"
class="timeago timeago-contextual"
@if(!empty($currentStockEntry->best_before_date)) datetime="{{ $currentStockEntry->best_before_date }} 23:59:59" @endif></time>
</td>
<td class="d-none">
@foreach(FindAllObjectsInArrayByPropertyValue($currentStockLocations, 'product_id', $currentStockEntry->product_id) as $locationsForProduct)
xx{{ FindObjectInArrayByPropertyValue($locations, 'id', $locationsForProduct->location_id)->name }}xx
@endforeach
</td>
<td class="d-none">
@if($currentStockEntry->best_before_date < date('Y-m-d
</tr>
</thead>
<tbody class="d-none">
@foreach ($currentStock as $currentStockEntry)
<tr id="product-{{ $currentStockEntry->product_id }}-row"
class="@if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && $currentStockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime('-1 days')) && $currentStockEntry->amount > 0) @if ($currentStockEntry->due_type == 1) table-secondary @else table-danger @endif
@elseif(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && $currentStockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime('+' . $nextXDays . ' days')) && $currentStockEntry->amount > 0)
table-warning
@elseif ($currentStockEntry->product_missing)
table-info @endif">
<td class="fit-content border-right">
<a class="permission-STOCK_CONSUME btn btn-success btn-sm product-consume-button @if ($currentStockEntry->amount_aggregated < $currentStockEntry->quick_consume_amount || $currentStockEntry->enable_tare_weight_handling == 1) disabled @endif"
href="#" data-toggle="tooltip" data-placement="left"
title="{{ $__t('Consume %1$s of %2$s',floatval($currentStockEntry->quick_consume_amount) . ' ' . $currentStockEntry->qu_unit_name,$currentStockEntry->product_name) }}"
data-product-id="{{ $currentStockEntry->product_id }}"
data-product-name="{{ $currentStockEntry->product_name }}"
data-product-qu-name="{{ $currentStockEntry->qu_unit_name }}"
data-consume-amount="{{ $currentStockEntry->quick_consume_amount }}">
<i class="fas fa-utensils"></i> <span
class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->quick_consume_amount }}</span>
</a>
<a id="product-{{ $currentStockEntry->product_id }}-consume-all-button"
class="permission-STOCK_CONSUME btn btn-danger btn-sm product-consume-button @if ($currentStockEntry->amount_aggregated == 0) disabled @endif"
href="#" data-toggle="tooltip" data-placement="right"
title="{{ $__t('Consume all %s which are currently in stock', $currentStockEntry->product_name) }}"
data-product-id="{{ $currentStockEntry->product_id }}"
data-product-name="{{ $currentStockEntry->product_name }}"
data-product-qu-name="{{ $currentStockEntry->qu_unit_name }}"
data-consume-amount="@if ($currentStockEntry->enable_tare_weight_handling == 1) {{ $currentStockEntry->tare_weight }}@else{{ $currentStockEntry->amount }} @endif"
data-original-total-stock-amount="{{ $currentStockEntry->amount }}">
<i class="fas fa-utensils"></i> {{ $__t('All') }}
</a>
@if (GROCY_FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING)
<a class="btn btn-success btn-sm product-open-button @if ($currentStockEntry->amount_aggregated < $currentStockEntry->quick_consume_amount || $currentStockEntry->amount_aggregated == $currentStockEntry->amount_opened_aggregated || $currentStockEntry->enable_tare_weight_handling == 1) disabled @endif"
href="#" data-toggle="tooltip" data-placement="left"
title="{{ $__t('Mark %1$s of %2$s as open',floatval($currentStockEntry->quick_consume_amount) . ' ' . $currentStockEntry->qu_unit_name,$currentStockEntry->product_name) }}"
data-product-id="{{ $currentStockEntry->product_id }}"
data-product-name="{{ $currentStockEntry->product_name }}"
data-product-qu-name="{{ $currentStockEntry->qu_unit_name }}"
data-open-amount="{{ $currentStockEntry->quick_consume_amount }}">
<i class="fas fa-box-open"></i> <span
class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->quick_consume_amount }}</span>
</a>
@endif
<div class="dropdown d-inline-block">
<button class="btn btn-sm btn-light text-secondary" type="button"
data-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="table-inline-menu dropdown-menu dropdown-menu-right">
@if (GROCY_FEATURE_FLAG_SHOPPINGLIST)
<a class="dropdown-item show-as-dialog-link permission-SHOPPINGLIST_ITEMS_ADD"
type="button"
href="{{ $U('/shoppinglistitem/new?embedded&updateexistingproduct&product=' . $currentStockEntry->product_id) }}">
<span class="dropdown-item-icon"><i class="fas fa-shopping-cart"></i></span>
<span
class="dropdown-item-text">{{ $__t('Add to shopping list') }}</span>
</a>
<div class="dropdown-divider"></div>
@endif
<a class="dropdown-item show-as-dialog-link permission-STOCK_PURCHASE" type="button"
href="{{ $U('/purchase?embedded&product=' . $currentStockEntry->product_id) }}">
<span class="dropdown-item-icon"><i class="fas fa-cart-plus"></i></span> <span
class="dropdown-item-text">{{ $__t('Purchase') }}</span>
</a>
<a class="dropdown-item show-as-dialog-link permission-STOCK_CONSUME @if ($currentStockEntry->amount_aggregated <= 0) disabled @endif"
type="button"
href="{{ $U('/consume?embedded&product=' . $currentStockEntry->product_id) }}">
<span class="dropdown-item-icon"><i class="fas fa-utensils"></i></span> <span
class="dropdown-item-text">{{ $__t('Consume') }}</span>
</a>
@if (GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
<a class="dropdown-item show-as-dialog-link permission-STOCK_TRANSFER @if ($currentStockEntry->amount <= 0) disabled @endif"
type="button"
href="{{ $U('/transfer?embedded&product=' . $currentStockEntry->product_id) }}">
<span class="dropdown-item-icon"><i class="fas fa-exchange-alt"></i></span>
<span class="dropdown-item-text">{{ $__t('Transfer') }}</span>
</a>
@endif
<a class="dropdown-item show-as-dialog-link permission-STOCK_INVENTORY"
type="button"
href="{{ $U('/inventory?embedded&product=' . $currentStockEntry->product_id) }}">
<span class="dropdown-item-icon"><i class="fas fa-list"></i></span> <span
class="dropdown-item-text">{{ $__t('Inventory') }}</span>
</a>
@if (GROCY_FEATURE_FLAG_RECIPES)
<a class="dropdown-item" type="button"
href="{{ $U('/recipes?search=') }}{{ $currentStockEntry->product_name }}">
<span
class="dropdown-item-text">{{ $__t('Search for recipes containing this product') }}</span>
</a>
@endif
<div class="dropdown-divider"></div>
<a class="dropdown-item product-name-cell"
data-product-id="{{ $currentStockEntry->product_id }}" type="button"
href="#">
<span class="dropdown-item-text">{{ $__t('Product overview') }}</span>
</a>
<a class="dropdown-item show-as-dialog-link" type="button"
href="{{ $U('/stockentries?embedded&product=') }}{{ $currentStockEntry->product_id }}"
data-product-id="{{ $currentStockEntry->product_id }}">
<span class="dropdown-item-text">{{ $__t('Stock entries') }}</span>
</a>
<a class="dropdown-item show-as-dialog-link" type="button"
href="{{ $U('/stockjournal?embedded&product=') }}{{ $currentStockEntry->product_id }}">
<span class="dropdown-item-text">{{ $__t('Stock journal') }}</span>
</a>
<a class="dropdown-item show-as-dialog-link" type="button"
href="{{ $U('/stockjournal/summary?embedded&product_id=') }}{{ $currentStockEntry->product_id }}">
<span class="dropdown-item-text">{{ $__t('Stock journal summary') }}</span>
</a>
<a class="dropdown-item permission-MASTER_DATA_EDIT link-return" type="button"
data-href="{{ $U('/product/') }}{{ $currentStockEntry->product_id }}">
<span class="dropdown-item-text">{{ $__t('Edit product') }}</span>
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" type="button"
href="{{ $U('/product/' . $currentStockEntry->product_id . '/grocycode?download=true') }}">
{!! str_replace('grocycode', '<span class="ls-n1">grocycode</span>', $__t('Download %s grocycode', $__t('Product'))) !!}
</a>
@if (GROCY_FEATURE_FLAG_LABEL_PRINTER)
<a class="dropdown-item product-grocycode-label-print"
data-product-id="{{ $currentStockEntry->product_id }}" type="button"
href="#">
{!! str_replace('grocycode', '<span class="ls-n1">grocycode</span>', $__t('Print %s grocycode on label printer', $__t('Product'))) !!}
</a>
@endif
</div>
</div>
</td>
<td class="product-name-cell cursor-link"
data-product-id="{{ $currentStockEntry->product_id }}">
{{ $currentStockEntry->product_name }}
<span class="d-none">{{ $currentStockEntry->product_barcodes }}</span>
</td>
<td>
@if ($currentStockEntry->product_group_name !== null)
{{ $currentStockEntry->product_group_name }}@endif
</td>
<td data-order="{{ $currentStockEntry->amount }}">
<span id="product-{{ $currentStockEntry->product_id }}-amount"
class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->amount }}</span>
<span
id="product-{{ $currentStockEntry->product_id }}-qu-name">{{ $__n($currentStockEntry->amount, $currentStockEntry->qu_unit_name, $currentStockEntry->qu_unit_name_plural) }}</span>
<span id="product-{{ $currentStockEntry->product_id }}-opened-amount"
class="small font-italic">
@if ($currentStockEntry->amount_opened > 0)
{{ $__t('%s opened', $currentStockEntry->amount_opened) }}@endif
</span>
@if ($currentStockEntry->is_aggregated_amount == 1)
<span class="pl-1 text-secondary">
<i class="fas fa-custom-sigma-sign"></i> <span
id="product-{{ $currentStockEntry->product_id }}-amount-aggregated"
class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->amount_aggregated }}</span>
{{ $__n($currentStockEntry->amount_aggregated,$currentStockEntry->qu_unit_name,$currentStockEntry->qu_unit_name_plural,true) }}
@if ($currentStockEntry->amount_opened_aggregated > 0)<span
id="product-{{ $currentStockEntry->product_id }}-opened-amount-aggregated"
class="small font-italic">{{ $__t('%s opened', $currentStockEntry->amount_opened_aggregated) }}</span>
@endif
</span>
@endif
@if (boolval($userSettings['show_icon_on_stock_overview_page_when_product_is_on_shopping_list']))
@if ($currentStockEntry->on_shopping_list)
<span class="text-muted cursor-normal" data-toggle="tooltip"
title="{{ $__t('This product is currently on a shopping list') }}">
<i class="fas fa-shopping-cart"></i>
</span>
@endif
@endif
</td>
<td>
<span id="product-{{ $currentStockEntry->product_id }}-value"
class="locale-number locale-number-currency">{{ $currentStockEntry->value }}</span>
</td>
<td class="@if (!GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING) d-none @endif">
<span
id="product-{{ $currentStockEntry->product_id }}-next-due-date">{{ $currentStockEntry->best_before_date }}</span>
<time id="product-{{ $currentStockEntry->product_id }}-next-due-date-timeago"
class="timeago timeago-contextual"
@if (!empty($currentStockEntry->best_before_date)) datetime="{{ $currentStockEntry->best_before_date }} 23:59:59" @endif></time>
</td>
<td class="d-none">
@foreach (FindAllObjectsInArrayByPropertyValue($currentStockLocations, 'product_id', $currentStockEntry->product_id) as $locationsForProduct)
xx{{ FindObjectInArrayByPropertyValue($locations, 'id', $locationsForProduct->location_id)->name }}xx
@endforeach
</td>
<td class="d-none">
@if ($currentStockEntry->best_before_date <
date(
'Y-m-d
23:59:59',
strtotime('-'
. '1'
. ' days'
))
&&
$currentStockEntry->amount > 0) @if($currentStockEntry->due_type == 1) overdue @else expired @endif @elseif($currentStockEntry->best_before_date < date('Y-m-d
strtotime('-' . '1' . ' days'),
) && $currentStockEntry->amount > 0)
@if ($currentStockEntry->due_type == 1) overdue
@else
expired @endif
@elseif($currentStockEntry->best_before_date <
date(
'Y-m-d
23:59:59',
strtotime('+'
.
$nextXDays
. ' days'
))
&&
$currentStockEntry->amount > 0) duesoon @endif
@if($currentStockEntry->amount_aggregated > 0) instockX @endif
@if ($currentStockEntry->product_missing) belowminstockamount @endif
</td>
<td class="d-none">
xx{{ $currentStockEntry->product_group_name }}xx
</td>
<td>
<span class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->product_calories }}</span>
</td>
<td>
<span class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->calories }}</span>
</td>
<td>
{{ $currentStockEntry->last_purchased }}
<time class="timeago timeago-contextual"
datetime="{{ $currentStockEntry->last_purchased }}"></time>
</td>
<td class="@if(!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif">
<span class="locale-number locale-number-currency">{{ $currentStockEntry->last_price }}</span>
</td>
<td>
<span class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->min_stock_amount }}</span>
</td>
<td>
{!! $currentStockEntry->product_description !!}
</td>
<td class="product-name-cell cursor-link"
data-product-id="{{ $currentStockEntry->parent_product_id }}">
{{ $currentStockEntry->parent_product_name }}
</td>
<td>
{{ $currentStockEntry->product_default_location_name }}
</td>
<td>
@if(!empty($currentStockEntry->product_picture_file_name))
<img data-src="{{ $U('/api/files/productpictures/' . base64_encode($currentStockEntry->product_picture_file_name) . '?force_serve_as=picture&best_fit_width=64&best_fit_height=64') }}"
class="lazy">
@endif
</td>
<td class="@if(!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif">
<span class="locale-number locale-number-currency">{{ $currentStockEntry->average_price }}</span>
</td>
strtotime('+' . $nextXDays . ' days'),
) && $currentStockEntry->amount > 0)
duesoon
@endif
@if ($currentStockEntry->amount_aggregated > 0) instockX @endif
@if ($currentStockEntry->product_missing) belowminstockamount
@endif
</td>
<td class="d-none">
xx{{ $currentStockEntry->product_group_name }}xx
</td>
<td>
<span
class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->product_calories }}</span>
</td>
<td>
<span
class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->calories }}</span>
</td>
<td>
{{ $currentStockEntry->last_purchased }}
<time class="timeago timeago-contextual"
datetime="{{ $currentStockEntry->last_purchased }}"></time>
</td>
<td class="@if (!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif">
<span
class="locale-number locale-number-currency">{{ $currentStockEntry->last_price }}</span>
</td>
<td>
<span
class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->min_stock_amount }}</span>
</td>
<td>
{!! $currentStockEntry->product_description !!}
</td>
<td class="product-name-cell cursor-link"
data-product-id="{{ $currentStockEntry->parent_product_id }}">
{{ $currentStockEntry->parent_product_name }}
</td>
<td>
{{ $currentStockEntry->product_default_location_name }}
</td>
<td>
@if (!empty($currentStockEntry->product_picture_file_name))
<img data-src="{{ $U('/api/files/productpictures/' .base64_encode($currentStockEntry->product_picture_file_name) .'?force_serve_as=picture&best_fit_width=64&best_fit_height=64') }}"
class="lazy">
@endif
</td>
<td class="@if (!GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) d-none @endif">
<span
class="locale-number locale-number-currency">{{ $currentStockEntry->average_price }}</span>
</td>
@include('components.userfields_tbody', array(
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $currentStockEntry->product_id)
))
@include('components.userfields_tbody', [
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue(
$userfieldValues,
'object_id',
$currentStockEntry->product_id
),
])
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<div class="modal fade"
id="stockoverview-productcard-modal"
tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-center">
<div class="modal-body">
@include('components.productcard')
</div>
<div class="modal-footer">
<button type="button"
class="btn btn-secondary"
data-dismiss="modal">{{ $__t('Close') }}</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="stockoverview-productcard-modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-center">
<div class="modal-body">
@include('components.productcard')
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ $__t('Close') }}</button>
</div>
</div>
</div>
</div>
@stop

View File

@ -5,202 +5,189 @@
@section('viewJsName', 'stocksettings')
@section('content')
<div class="row">
<div class="col">
<h2 class="title">@yield('title')</h2>
</div>
</div>
<div class="row">
<div class="col">
<h2 class="title">@yield('title')</h2>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row">
<div class="col-lg-6 col-12">
<div id="productpresets">
<h4>{{ $__t('Presets for new products') }}</h4>
<div class="row">
<div class="col-lg-6 col-12">
<div id="productpresets">
<h4>{{ $__t('Presets for new products') }}</h4>
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
<div class="form-group">
<label for="product_presets_location_id">{{ $__t('Location') }}</label>
<select class="custom-control custom-select user-setting-control"
id="product_presets_location_id"
data-setting-key="product_presets_location_id">
<option value="-1"></option>
@foreach($locations as $location)
<option value="{{ $location->id }}">{{ $location->name }}</option>
@endforeach
</select>
</div>
@endif
@if (GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
<div class="form-group">
<label for="product_presets_location_id">{{ $__t('Location') }}</label>
{{-- TODO: Select2: dynamic data: locations --}}
<select class="custom-control custom-select user-setting-control" id="product_presets_location_id"
data-setting-key="product_presets_location_id">
<option value="-1"></option>
@foreach ($locations as $location)
<option value="{{ $location->id }}">{{ $location->name }}</option>
@endforeach
</select>
</div>
@endif
<div class="form-group">
<label for="product_presets_product_group_id">{{ $__t('Product group') }}</label>
<select class="custom-control custom-select user-setting-control"
id="product_presets_product_group_id"
data-setting-key="product_presets_product_group_id">
<option value="-1"></option>
@foreach($productGroups as $productGroup)
<option value="{{ $productGroup->id }}">{{ $productGroup->name }}</option>
@endforeach
</select>
</div>
<div class="form-group">
<label for="product_presets_product_group_id">{{ $__t('Product group') }}</label>
{{-- TODO: Select2: dynamic data: product_groups --}}
<select class="custom-control custom-select user-setting-control" id="product_presets_product_group_id"
data-setting-key="product_presets_product_group_id">
<option value="-1"></option>
@foreach ($productGroups as $productGroup)
<option value="{{ $productGroup->id }}">{{ $productGroup->name }}</option>
@endforeach
</select>
</div>
<div class="form-group">
<label for="product_presets_qu_id">{{ $__t('Quantity unit') }}</label>
<select class="custom-control custom-select user-setting-control"
id="product_presets_qu_id"
data-setting-key="product_presets_qu_id">
<option value="-1"></option>
@foreach($quantityunits as $quantityunit)
<option value="{{ $quantityunit->id }}">{{ $quantityunit->name }}</option>
@endforeach
</select>
</div>
<div class="form-group">
<label for="product_presets_qu_id">{{ $__t('Quantity unit') }}</label>
{{-- TODO: Select2: dynamic data: quantity_units --}}
<select class="custom-control custom-select user-setting-control" id="product_presets_qu_id"
data-setting-key="product_presets_qu_id">
<option value="-1"></option>
@foreach ($quantityunits as $quantityunit)
<option value="{{ $quantityunit->id }}">{{ $quantityunit->name }}</option>
@endforeach
</select>
</div>
@include('components.numberpicker', array(
'id' => 'product_presets_default_due_days',
'additionalAttributes' => 'data-setting-key="product_presets_default_due_days"',
'label' => 'Default due days',
'min' => -1,
'additionalCssClasses' => 'user-setting-control'
))
@include('components.numberpicker', [
'id' => 'product_presets_default_due_days',
'additionalAttributes' => 'data-setting-key="product_presets_default_due_days"',
'label' => 'Default due days',
'min' => -1,
'additionalCssClasses' => 'user-setting-control',
])
@if(GROCY_FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING)
<div class="form-group">
<div class="custom-control custom-checkbox">
<input type="checkbox"
class="form-check-input custom-control-input user-setting-control"
id="product_presets_treat_opened_as_out_of_stock"
data-setting-key="product_presets_treat_opened_as_out_of_stock">
<label class="form-check-label custom-control-label"
for="product_presets_treat_opened_as_out_of_stock">
{{ $__t('Treat opened as out of stock') }}
</label>
</div>
</div>
@endif
</div>
@if (GROCY_FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING)
<div class="form-group">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="form-check-input custom-control-input user-setting-control"
id="product_presets_treat_opened_as_out_of_stock"
data-setting-key="product_presets_treat_opened_as_out_of_stock">
<label class="form-check-label custom-control-label"
for="product_presets_treat_opened_as_out_of_stock">
{{ $__t('Treat opened as out of stock') }}
</label>
</div>
</div>
@endif
</div>
<h4 class="mt-2">{{ $__t('Stock overview') }}</h4>
@include('components.numberpicker', array(
'id' => 'stock_due_soon_days',
'additionalAttributes' => 'data-setting-key="stock_due_soon_days"',
'label' => 'Due soon days',
'min' => 1,
'additionalCssClasses' => 'user-setting-control'
))
<h4 class="mt-2">{{ $__t('Stock overview') }}</h4>
@include('components.numberpicker', [
'id' => 'stock_due_soon_days',
'additionalAttributes' => 'data-setting-key="stock_due_soon_days"',
'label' => 'Due soon days',
'min' => 1,
'additionalCssClasses' => 'user-setting-control',
])
<div class="form-group">
<div class="custom-control custom-checkbox">
<input type="checkbox"
class="form-check-input custom-control-input user-setting-control"
id="show_icon_on_stock_overview_page_when_product_is_on_shopping_list"
data-setting-key="show_icon_on_stock_overview_page_when_product_is_on_shopping_list">
<label class="form-check-label custom-control-label"
for="show_icon_on_stock_overview_page_when_product_is_on_shopping_list">
{{ $__t('Show an icon if the product is already on the shopping list') }}
</label>
</div>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="form-check-input custom-control-input user-setting-control"
id="show_icon_on_stock_overview_page_when_product_is_on_shopping_list"
data-setting-key="show_icon_on_stock_overview_page_when_product_is_on_shopping_list">
<label class="form-check-label custom-control-label"
for="show_icon_on_stock_overview_page_when_product_is_on_shopping_list">
{{ $__t('Show an icon if the product is already on the shopping list') }}
</label>
</div>
</div>
<h4 class="mt-2">{{ $__t('Purchase') }}</h4>
@include('components.numberpicker', array(
'id' => 'stock_default_purchase_amount',
'additionalAttributes' => 'data-setting-key="stock_default_purchase_amount"',
'label' => 'Default amount for purchase',
'min' => '0.',
'decimals' => $userSettings['stock_decimal_places_amounts'],
'additionalCssClasses' => 'user-setting-control locale-number-input locale-number-quantity-amount',
))
<h4 class="mt-2">{{ $__t('Purchase') }}</h4>
@include('components.numberpicker', [
'id' => 'stock_default_purchase_amount',
'additionalAttributes' => 'data-setting-key="stock_default_purchase_amount"',
'label' => 'Default amount for purchase',
'min' => '0.',
'decimals' => $userSettings['stock_decimal_places_amounts'],
'additionalCssClasses' => 'user-setting-control locale-number-input locale-number-quantity-amount',
])
<div class="form-group">
<div class="custom-control custom-checkbox">
<input type="checkbox"
class="form-check-input custom-control-input user-setting-control"
id="show_purchased_date_on_purchase"
data-setting-key="show_purchased_date_on_purchase">
<label class="form-check-label custom-control-label"
for="show_purchased_date_on_purchase">
{{ $__t('Show purchased date on purchase and inventory page (otherwise the purchased date defaults to today)') }}
</label>
</div>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="form-check-input custom-control-input user-setting-control"
id="show_purchased_date_on_purchase" data-setting-key="show_purchased_date_on_purchase">
<label class="form-check-label custom-control-label" for="show_purchased_date_on_purchase">
{{ $__t('Show purchased date on purchase and inventory page (otherwise the purchased date defaults to today)') }}
</label>
</div>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input type="checkbox"
class="form-check-input custom-control-input user-setting-control"
id="show_warning_on_purchase_when_due_date_is_earlier_than_next"
data-setting-key="show_warning_on_purchase_when_due_date_is_earlier_than_next">
<label class="form-check-label custom-control-label"
for="show_warning_on_purchase_when_due_date_is_earlier_than_next">
{{ $__t('Show a warning when the due date of the purchased product is earlier than the next due date in stock') }}
</label>
</div>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="form-check-input custom-control-input user-setting-control"
id="show_warning_on_purchase_when_due_date_is_earlier_than_next"
data-setting-key="show_warning_on_purchase_when_due_date_is_earlier_than_next">
<label class="form-check-label custom-control-label"
for="show_warning_on_purchase_when_due_date_is_earlier_than_next">
{{ $__t('Show a warning when the due date of the purchased product is earlier than the next due date in stock') }}
</label>
</div>
</div>
<h4 class="mt-2">{{ $__t('Consume') }}</h4>
@include('components.numberpicker', array(
'id' => 'stock_default_consume_amount',
'additionalAttributes' => 'data-setting-key="stock_default_consume_amount"',
'label' => 'Default amount for consume',
'min' => 0,
'decimals' => $userSettings['stock_decimal_places_amounts'],
'additionalCssClasses' => 'user-setting-control locale-number-input locale-number-quantity-amount',
'additionalGroupCssClasses' => 'mb-0'
))
<h4 class="mt-2">{{ $__t('Consume') }}</h4>
@include('components.numberpicker', [
'id' => 'stock_default_consume_amount',
'additionalAttributes' => 'data-setting-key="stock_default_consume_amount"',
'label' => 'Default amount for consume',
'min' => 0,
'decimals' => $userSettings['stock_decimal_places_amounts'],
'additionalCssClasses' => 'user-setting-control locale-number-input locale-number-quantity-amount',
'additionalGroupCssClasses' => 'mb-0',
])
<div class="form-group">
<div class="custom-control custom-checkbox">
<input type="checkbox"
class="form-check-input custom-control-input user-setting-control"
id="stock_default_consume_amount_use_quick_consume_amount"
data-setting-key="stock_default_consume_amount_use_quick_consume_amount">
<label class="form-check-label custom-control-label"
for="stock_default_consume_amount_use_quick_consume_amount">
{{ $__t('Use the products "Quick consume amount"') }}
</label>
</div>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="form-check-input custom-control-input user-setting-control"
id="stock_default_consume_amount_use_quick_consume_amount"
data-setting-key="stock_default_consume_amount_use_quick_consume_amount">
<label class="form-check-label custom-control-label"
for="stock_default_consume_amount_use_quick_consume_amount">
{{ $__t('Use the products "Quick consume amount"') }}
</label>
</div>
</div>
<h4 class="mt-2">{{ $__t('Common') }}</h4>
<h4 class="mt-2">{{ $__t('Common') }}</h4>
@include('components.numberpicker', array(
'id' => 'stock_decimal_places_amounts',
'additionalAttributes' => 'data-setting-key="stock_decimal_places_amounts"',
'label' => 'Decimal places allowed for amounts',
'min' => 0,
'additionalCssClasses' => 'user-setting-control'
))
@include('components.numberpicker', [
'id' => 'stock_decimal_places_amounts',
'additionalAttributes' => 'data-setting-key="stock_decimal_places_amounts"',
'label' => 'Decimal places allowed for amounts',
'min' => 0,
'additionalCssClasses' => 'user-setting-control',
])
@if(GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
@include('components.numberpicker', array(
'id' => 'stock_decimal_places_prices',
'additionalAttributes' => 'data-setting-key="stock_decimal_places_prices"',
'label' => 'Decimal places allowed for prices',
'min' => 0,
'additionalCssClasses' => 'user-setting-control'
))
@if (GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
@include('components.numberpicker', [
'id' => 'stock_decimal_places_prices',
'additionalAttributes' => 'data-setting-key="stock_decimal_places_prices"',
'label' => 'Decimal places allowed for prices',
'min' => 0,
'additionalCssClasses' => 'user-setting-control',
])
<div class="form-group mt-n3">
<div class="custom-control custom-checkbox">
<input type="checkbox"
class="form-check-input custom-control-input user-setting-control"
id="stock_auto_decimal_separator_prices"
data-setting-key="stock_auto_decimal_separator_prices">
<label class="form-check-label custom-control-label"
for="stock_auto_decimal_separator_prices">
{{ $__t('Add decimal separator automatically for price inputs') }}
<i class="fas fa-question-circle text-muted"
data-toggle="tooltip"
data-trigger="hover click"
title="{{ $__t('When enabled, you always have to enter the value including decimal places, the decimal separator will be automatically added based on the amount of allowed decimal places') }}"></i>
</label>
</div>
</div>
@endif
<div class="form-group mt-n3">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="form-check-input custom-control-input user-setting-control"
id="stock_auto_decimal_separator_prices" data-setting-key="stock_auto_decimal_separator_prices">
<label class="form-check-label custom-control-label" for="stock_auto_decimal_separator_prices">
{{ $__t('Add decimal separator automatically for price inputs') }}
<i class="fas fa-question-circle text-muted" data-toggle="tooltip" data-trigger="hover click"
title="{{ $__t('When enabled, you always have to enter the value including decimal places, the decimal separator will be automatically added based on the amount of allowed decimal places') }}"></i>
</label>
</div>
</div>
@endif
<a href="{{ $U('/stockoverview') }}"
class="btn btn-success">{{ $__t('OK') }}</a>
</div>
</div>
<a href="{{ $U('/stockoverview') }}" class="btn btn-success">{{ $__t('OK') }}</a>
</div>
</div>
@stop

View File

@ -5,122 +5,108 @@
@section('viewJsName', 'taskcategories')
@section('content')
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100"
id="related-links">
<a class="btn btn-primary responsive-button m-1 mt-md-0 mb-md-0 float-right show-as-dialog-link"
href="{{ $U('/taskcategory/new?embedded') }}">
{{ $__t('Add') }}
</a>
<a class="btn btn-outline-secondary"
href="{{ $U('/userfields?entity=task_categories') }}">
{{ $__t('Configure userfields') }}
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100" id="related-links">
<a class="btn btn-primary responsive-button m-1 mt-md-0 mb-md-0 float-right show-as-dialog-link"
href="{{ $U('/taskcategory/new?embedded') }}">
{{ $__t('Add') }}
</a>
<a class="btn btn-outline-secondary" href="{{ $U('/userfields?entity=task_categories') }}">
{{ $__t('Configure userfields') }}
</a>
</div>
</div>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row collapse d-md-flex"
id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text"
id="search"
class="form-control"
placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row collapse d-md-flex" id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text" id="search" class="form-control" placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button" class="btn btn-sm btn-outline-info" href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row">
<div class="col">
<table id="taskcategories-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#taskcategories-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Name') }}</th>
<th>{{ $__t('Description') }}</th>
<div class="row">
<div class="col">
{{-- TODO: DataTables: dynamic data: task_categories --}}
<table id="taskcategories-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#taskcategories-table" href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Name') }}</th>
<th>{{ $__t('Description') }}</th>
@include('components.userfields_thead', array(
'userfields' => $userfields
))
@include('components.userfields_thead', [
'userfields' => $userfields,
])
</tr>
</thead>
<tbody class="d-none">
@foreach($taskCategories as $taskCategory)
<tr>
<td class="fit-content border-right">
<a class="btn btn-info btn-sm show-as-dialog-link"
href="{{ $U('/taskcategory/') }}{{ $taskCategory->id }}?embedded"
data-toggle="tooltip"
title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger btn-sm task-category-delete-button"
href="#"
data-category-id="{{ $taskCategory->id }}"
data-category-name="{{ $taskCategory->name }}"
data-toggle="tooltip"
title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
{{ $taskCategory->name }}
</td>
<td>
{{ $taskCategory->description }}
</td>
</tr>
</thead>
<tbody class="d-none">
@foreach ($taskCategories as $taskCategory)
<tr>
<td class="fit-content border-right">
<a class="btn btn-info btn-sm show-as-dialog-link"
href="{{ $U('/taskcategory/') }}{{ $taskCategory->id }}?embedded"
data-toggle="tooltip" title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger btn-sm task-category-delete-button" href="#"
data-category-id="{{ $taskCategory->id }}"
data-category-name="{{ $taskCategory->name }}" data-toggle="tooltip"
title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
{{ $taskCategory->name }}
</td>
<td>
{{ $taskCategory->description }}
</td>
@include('components.userfields_tbody', array(
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $taskCategory->id)
))
@include('components.userfields_tbody', [
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue(
$userfieldValues,
'object_id',
$taskCategory->id
),
])
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@stop

View File

@ -1,116 +1,109 @@
@extends('layout.default')
@if($mode == 'edit')
@section('title', $__t('Edit task'))
@if ($mode == 'edit')
@section('title', $__t('Edit task'))
@else
@section('title', $__t('Create task'))
@section('title', $__t('Create task'))
@endif
@section('viewJsName', 'taskform')
@section('content')
<div class="row">
<div class="col">
<h2 class="title">@yield('title')</h2>
</div>
</div>
<div class="row">
<div class="col">
<h2 class="title">@yield('title')</h2>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row">
<div class="col-lg-6 col-12">
<script>
Grocy.EditMode = '{{ $mode }}';
</script>
<div class="row">
<div class="col-lg-6 col-12">
<script>
Grocy.EditMode = '{{ $mode }}';
</script>
@if($mode == 'edit')
<script>
Grocy.EditObjectId = {{ $task->id }};
</script>
@endif
@if ($mode == 'edit')
<script>
Grocy.EditObjectId = {{ $task->id }};
</script>
@endif
<form id="task-form"
novalidate>
<form id="task-form" novalidate>
<div class="form-group">
<label for="name">{{ $__t('Name') }}</label>
<input type="text"
class="form-control"
required
id="name"
name="name"
value="@if($mode == 'edit'){{ $task->name }}@endif">
<div class="invalid-feedback">{{ $__t('A name is required') }}</div>
</div>
<div class="form-group">
<label for="name">{{ $__t('Name') }}</label>
<input type="text" class="form-control" required id="name" name="name"
value="@if ($mode == 'edit') {{ $task->name }} @endif">
<div class="invalid-feedback">{{ $__t('A name is required') }}</div>
</div>
<div class="form-group">
<label for="description">{{ $__t('Description') }}</label>
<textarea class="form-control"
rows="4"
id="description"
name="description">@if($mode == 'edit'){{ $task->description }}@endif</textarea>
</div>
<div class="form-group">
<label for="description">{{ $__t('Description') }}</label>
<textarea class="form-control" rows="4" id="description" name="description">
@if ($mode == 'edit')
{{ $task->description }}
@endif
</textarea>
</div>
@php
$initialDueDate = null;
if ($mode == 'edit' && !empty($task->due_date))
{
$initialDueDate = date('Y-m-d', strtotime($task->due_date));
}
@endphp
@include('components.datetimepicker', array(
'id' => 'due_date',
'label' => 'Due',
'format' => 'YYYY-MM-DD',
'initWithNow' => false,
'initialValue' => $initialDueDate,
'limitEndToNow' => false,
'limitStartToNow' => false,
'invalidFeedback' => $__t('A due date is required'),
'nextInputSelector' => 'category_id',
'additionalGroupCssClasses' => 'date-only-datetimepicker',
'isRequired' => false
))
@php
$initialDueDate = null;
if ($mode == 'edit' && !empty($task->due_date)) {
$initialDueDate = date('Y-m-d', strtotime($task->due_date));
}
@endphp
@include('components.datetimepicker', [
'id' => 'due_date',
'label' => 'Due',
'format' => 'YYYY-MM-DD',
'initWithNow' => false,
'initialValue' => $initialDueDate,
'limitEndToNow' => false,
'limitStartToNow' => false,
'invalidFeedback' => $__t('A due date is required'),
'nextInputSelector' => 'category_id',
'additionalGroupCssClasses' => 'date-only-datetimepicker',
'isRequired' => false,
])
<div class="form-group">
<label for="category_id">{{ $__t('Category') }}</label>
<select class="custom-control custom-select"
id="category_id"
name="category_id">
<option></option>
@foreach($taskCategories as $taskCategory)
<option @if($mode=='edit'
&&
$taskCategory->id == $task->category_id) selected="selected" @endif value="{{ $taskCategory->id }}">{{ $taskCategory->name }}</option>
@endforeach
</select>
</div>
<div class="form-group">
<label for="category_id">{{ $__t('Category') }}</label>
{{-- TODO: Select2: dynamic data: task_categories --}}
<select class="custom-control custom-select" id="category_id" name="category_id">
<option></option>
@foreach ($taskCategories as $taskCategory)
<option @if ($mode == 'edit' && $taskCategory->id == $task->category_id) selected="selected" @endif
value="{{ $taskCategory->id }}">{{ $taskCategory->name }}</option>
@endforeach
</select>
</div>
@php
$initUserId = GROCY_USER_ID;
if ($mode == 'edit')
{
$initUserId = $task->assigned_to_user_id;
}
@endphp
@include('components.userpicker', array(
'label' => 'Assigned to',
'users' => $users,
'prefillByUserId' => $initUserId
))
@php
$initUserId = GROCY_USER_ID;
if ($mode == 'edit') {
$initUserId = $task->assigned_to_user_id;
}
@endphp
@include('components.userpicker', [
'label' => 'Assigned to',
'users' => $users,
'prefillByUserId' => $initUserId,
])
@include('components.userfieldsform', array(
'userfields' => $userfields,
'entity' => 'tasks'
))
@include('components.userfieldsform', [
'userfields' => $userfields,
'entity' => 'tasks',
])
@if($mode == 'edit')
<button class="btn btn-success save-task-button">{{ $__t('Save') }}</button>
@else
<button class="btn btn-success save-task-button">{{ $__t('Save & close') }}</button>
<button class="btn btn-primary save-task-button add-another">{{ $__t('Save & add another task') }}</button>
@endif
</form>
</div>
</div>
@if ($mode == 'edit')
<button class="btn btn-success save-task-button">{{ $__t('Save') }}</button>
@else
<button class="btn btn-success save-task-button">{{ $__t('Save & close') }}</button>
<button
class="btn btn-primary save-task-button add-another">{{ $__t('Save & add another task') }}</button>
@endif
</form>
</div>
</div>
@stop

View File

@ -5,200 +5,178 @@
@section('viewJsName', 'tasks')
@push('pageStyles')
<link href="{{ $U('/node_modules/animate.css/animate.min.css?v=', true) }}{{ $version }}"
rel="stylesheet">
<link href="{{ $U('/node_modules/animate.css/animate.min.css?v=', true) }}{{ $version }}" rel="stylesheet">
@endpush
@section('content')
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<button class="btn btn-outline-dark d-md-none mt-2 float-right order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100 m-1 mt-md-0 mb-md-0 float-right"
id="related-links">
<a class="btn btn-primary responsive-button show-as-dialog-link"
href="{{ $U('/task/new?embedded') }}">
{{ $__t('Add') }}
</a>
</div>
</div>
<div class="border-top border-bottom my-2 py-1">
<div id="info-overdue-tasks"
data-status-filter="overdue"
class="error-message status-filter-message responsive-button mr-2"></div>
<div id="info-due-today-tasks"
data-status-filter="duetoday"
class="normal-message status-filter-message responsive-button mr-2"></div>
<div id="info-due-soon-tasks"
data-status-filter="duesoon"
data-next-x-days="{{ $nextXDays }}"
class="warning-message status-filter-message responsive-button @if($nextXDays == 0) d-none @endif"></div>
<div class="float-right">
<a class="btn btn-sm btn-outline-info d-md-none mt-1"
data-toggle="collapse"
href="#table-filter-row"
role="button">
<i class="fas fa-filter"></i>
</a>
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info mt-1"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<button class="btn btn-outline-dark d-md-none mt-2 float-right order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100 m-1 mt-md-0 mb-md-0 float-right"
id="related-links">
<a class="btn btn-primary responsive-button show-as-dialog-link" href="{{ $U('/task/new?embedded') }}">
{{ $__t('Add') }}
</a>
</div>
</div>
<div class="border-top border-bottom my-2 py-1">
<div id="info-overdue-tasks" data-status-filter="overdue"
class="error-message status-filter-message responsive-button mr-2"></div>
<div id="info-due-today-tasks" data-status-filter="duetoday"
class="normal-message status-filter-message responsive-button mr-2"></div>
<div id="info-due-soon-tasks" data-status-filter="duesoon" data-next-x-days="{{ $nextXDays }}"
class="warning-message status-filter-message responsive-button @if ($nextXDays == 0) d-none @endif">
</div>
<div class="float-right">
<a class="btn btn-sm btn-outline-info d-md-none mt-1" data-toggle="collapse" href="#table-filter-row"
role="button">
<i class="fas fa-filter"></i>
</a>
<a id="clear-filter-button" class="btn btn-sm btn-outline-info mt-1" href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
</div>
<div class="row collapse d-md-flex"
id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text"
id="search"
class="form-control"
placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Status') }}</span>
</div>
<select class="custom-control custom-select"
id="status-filter">
<option value="all">{{ $__t('All') }}</option>
<option value="overdue">{{ $__t('Overdue') }}</option>
<option value="duetoday">{{ $__t('Due today') }}</option>
@if($nextXDays > 0)
<option value="duesoon">{{ $__t('Due soon') }}</option>
@endif
</select>
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="form-check custom-control custom-checkbox">
<input class="form-check-input custom-control-input"
type="checkbox"
id="show-done-tasks">
<label class="form-check-label custom-control-label"
for="show-done-tasks">
{{ $__t('Show done tasks') }}
</label>
</div>
</div>
</div>
<div class="row collapse d-md-flex" id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text" id="search" class="form-control" placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Status') }}</span>
</div>
<select class="custom-control custom-select" id="status-filter">
<option value="all">{{ $__t('All') }}</option>
<option value="overdue">{{ $__t('Overdue') }}</option>
<option value="duetoday">{{ $__t('Due today') }}</option>
@if ($nextXDays > 0)
<option value="duesoon">{{ $__t('Due soon') }}</option>
@endif
</select>
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="form-check custom-control custom-checkbox">
<input class="form-check-input custom-control-input" type="checkbox" id="show-done-tasks">
<label class="form-check-label custom-control-label" for="show-done-tasks">
{{ $__t('Show done tasks') }}
</label>
</div>
</div>
</div>
<div class="row">
<div class="col">
<table id="tasks-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#tasks-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Task') }}</th>
<th class="allow-grouping">{{ $__t('Due') }}</th>
<th class="allow-grouping"
data-shadow-rowgroup-column="6">{{ $__t('Category') }}</th>
<th class="allow-grouping">{{ $__t('Assigned to') }}</th>
<th class="d-none">Hidden status</th>
<th class="d-none">Hidden category_id</th>
<div class="row">
<div class="col">
{{-- TODO: DataTables: dynamic data: tasks --}}
<table id="tasks-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#tasks-table" href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Task') }}</th>
<th class="allow-grouping">{{ $__t('Due') }}</th>
<th class="allow-grouping" data-shadow-rowgroup-column="6">{{ $__t('Category') }}</th>
<th class="allow-grouping">{{ $__t('Assigned to') }}</th>
<th class="d-none">Hidden status</th>
<th class="d-none">Hidden category_id</th>
@include('components.userfields_thead', array(
'userfields' => $userfields
))
@include('components.userfields_thead', [
'userfields' => $userfields,
])
</tr>
</thead>
<tbody class="d-none">
@foreach($tasks as $task)
<tr id="task-{{ $task->id }}-row"
class="@if($task->due_type == 'overdue') table-danger @elseif($task->due_type == 'duetoday') table-info @elseif($task->due_type == 'duesoon') table-warning @endif">
<td class="fit-content border-right">
@if($task->done == 0)
<a class="btn btn-success btn-sm do-task-button"
href="#"
data-toggle="tooltip"
data-placement="left"
title="{{ $__t('Mark task as completed') }}"
data-task-id="{{ $task->id }}"
data-task-name="{{ $task->name }}">
<i class="fas fa-check"></i>
</a>
@else
<a class="btn btn-secondary btn-sm undo-task-button"
href="#"
data-toggle="tooltip"
data-placement="left"
title="{{ $__t('Undo task', $task->name) }}"
data-task-id="{{ $task->id }}"
data-task-name="{{ $task->name }}">
<i class="fas fa-undo"></i>
</a>
@endif
<a class="btn btn-info btn-sm show-as-dialog-link"
href="{{ $U('/task/') }}{{ $task->id }}?embedded"
data-toggle="tooltip"
title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-sm btn-danger delete-task-button"
href="#"
data-task-id="{{ $task->id }}"
data-task-name="{{ $task->name }}"
data-toggle="tooltip"
title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td id="task-{{ $task->id }}-name"
class="@if($task->done == 1) text-strike-through @endif">
{{ $task->name }}
</td>
<td>
<span>{{ $task->due_date }}</span>
<time class="timeago timeago-contextual"
datetime="{{ $task->due_date }}"></time>
</td>
<td>
@if($task->category_id != null) <span>{{ FindObjectInArrayByPropertyValue($taskCategories, 'id', $task->category_id)->name }}</span> @else <span class="font-italic font-weight-light">{{ $__t('Uncategorized') }}</span>@endif
</td>
<td>
@if($task->assigned_to_user_id != null) <span>{{ GetUserDisplayName(FindObjectInArrayByPropertyValue($users, 'id', $task->assigned_to_user_id)) }}</span> @endif
</td>
<td class="d-none">
{{ $task->due_type }}
@if($task->due_type == 'duetoday')
duesoon
@endif
</td>
<td class="d-none">
@if($task->category_id != null) {{ FindObjectInArrayByPropertyValue($taskCategories, 'id', $task->category_id)->name }} @else {{ $__t('Uncategorized') }} @endif
</td>
@include('components.userfields_tbody',
array( 'userfields'=> $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $task->id)
))
</tr>
</thead>
<tbody class="d-none">
@foreach ($tasks as $task)
<tr id="task-{{ $task->id }}-row"
class="@if ($task->due_type == 'overdue') table-danger @elseif($task->due_type == 'duetoday') table-info @elseif($task->due_type == 'duesoon') table-warning @endif">
<td class="fit-content border-right">
@if ($task->done == 0)
<a class="btn btn-success btn-sm do-task-button" href="#" data-toggle="tooltip"
data-placement="left" title="{{ $__t('Mark task as completed') }}"
data-task-id="{{ $task->id }}" data-task-name="{{ $task->name }}">
<i class="fas fa-check"></i>
</a>
@else
<a class="btn btn-secondary btn-sm undo-task-button" href="#" data-toggle="tooltip"
data-placement="left" title="{{ $__t('Undo task', $task->name) }}"
data-task-id="{{ $task->id }}" data-task-name="{{ $task->name }}">
<i class="fas fa-undo"></i>
</a>
@endif
<a class="btn btn-info btn-sm show-as-dialog-link"
href="{{ $U('/task/') }}{{ $task->id }}?embedded" data-toggle="tooltip"
title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-sm btn-danger delete-task-button" href="#"
data-task-id="{{ $task->id }}" data-task-name="{{ $task->name }}"
data-toggle="tooltip" title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td id="task-{{ $task->id }}-name"
class="@if ($task->done == 1) text-strike-through @endif">
{{ $task->name }}
</td>
<td>
<span>{{ $task->due_date }}</span>
<time class="timeago timeago-contextual" datetime="{{ $task->due_date }}"></time>
</td>
<td>
@if ($task->category_id != null)
<span>{{ FindObjectInArrayByPropertyValue($taskCategories, 'id', $task->category_id)->name }}</span>
@else
<span class="font-italic font-weight-light">{{ $__t('Uncategorized') }}</span>
@endif
</td>
<td>
@if ($task->assigned_to_user_id != null)
<span>{{ GetUserDisplayName(FindObjectInArrayByPropertyValue($users, 'id', $task->assigned_to_user_id)) }}</span>
@endif
</td>
<td class="d-none">
{{ $task->due_type }}
@if ($task->due_type == 'duetoday')
duesoon
@endif
</td>
<td class="d-none">
@if ($task->category_id != null)
{{ FindObjectInArrayByPropertyValue($taskCategories, 'id', $task->category_id)->name }}
@else
{{ $__t('Uncategorized') }}
@endif
</td>
@include('components.userfields_tbody', [
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue(
$userfieldValues,
'object_id',
$task->id
),
])
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@stop

View File

@ -5,94 +5,88 @@
@section('viewJsName', 'transfer')
@section('content')
<script>
Grocy.QuantityUnits = {!! json_encode($quantityUnits) !!};
Grocy.QuantityUnitConversionsResolved = {!! json_encode($quantityUnitConversionsResolved) !!};
</script>
<script>
Grocy.QuantityUnits = {!! json_encode($quantityUnits) !!};
Grocy.QuantityUnitConversionsResolved = {!! json_encode($quantityUnitConversionsResolved) !!};
</script>
<div class="row">
<div class="col-12 col-md-6 col-xl-4 pb-3">
<h2 class="title">@yield('title')</h2>
<div class="row">
<div class="col-12 col-md-6 col-xl-4 pb-3">
<h2 class="title">@yield('title')</h2>
<hr class="my-2">
<hr class="my-2">
<form id="transfer-form"
novalidate>
<form id="transfer-form" novalidate>
@include('components.productpicker', array(
'products' => $products,
'barcodes' => $barcodes,
'nextInputSelector' => '#location_id_from',
'disallowAddProductWorkflows' => true
))
@include('components.productpicker', [
'productsQuery' => 'query%5B%5D=active%3D1&only_in_stock=1&order=name%3Acollate%20nocase',
'nextInputSelector' => '#location_id_from',
'disallowAddProductWorkflows' => true,
])
<div class="form-group">
<label for="location_id_from">{{ $__t('From location') }}</label>
<select required
class="custom-control custom-select location-combobox"
id="location_id_from"
name="location_id_from">
<option></option>
@foreach($locations as $location)
<option value="{{ $location->id }}"
data-is-freezer="{{ $location->is_freezer }}">{{ $location->name }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('A location is required') }}</div>
</div>
<div class="form-group">
<label for="location_id_from">{{ $__t('From location') }}</label>
{{-- TODO: Select2: dynamic data: locations --}}
<select required class="custom-control custom-select location-combobox" id="location_id_from"
name="location_id_from">
<option></option>
@foreach ($locations as $location)
<option value="{{ $location->id }}" data-is-freezer="{{ $location->is_freezer }}">
{{ $location->name }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('A location is required') }}</div>
</div>
@include('components.productamountpicker', array(
'value' => 1,
'additionalHtmlContextHelp' => '<div id="tare-weight-handling-info"
class="text-info font-italic d-none">' . $__t('Tare weight handling enabled - please weigh the whole container, the amount to be posted will be automatically calculcated') . '</div>'
))
@include('components.productamountpicker', [
'value' => 1,
'additionalHtmlContextHelp' =>
'<div id="tare-weight-handling-info"
class="text-info font-italic d-none">' .
$__t(
'Tare weight handling enabled - please weigh the whole container, the amount to be posted will be automatically calculcated'
) .
'</div>',
])
<div class="form-group">
<label for="location_id_to">{{ $__t('To location') }}</label>
<select required
class="custom-control custom-select location-combobox"
id="location_id_to"
name="location_id_to">
<option></option>
@foreach($locations as $location)
<option value="{{ $location->id }}"
data-is-freezer="{{ $location->is_freezer }}">{{ $location->name }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('A location is required') }}</div>
</div>
<div class="form-group">
<label for="location_id_to">{{ $__t('To location') }}</label>
{{-- TODO: Select2: dynamic data: locations --}}
<select required class="custom-control custom-select location-combobox" id="location_id_to"
name="location_id_to">
<option></option>
@foreach ($locations as $location)
<option value="{{ $location->id }}" data-is-freezer="{{ $location->is_freezer }}">
{{ $location->name }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('A location is required') }}</div>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input class="form-check-input custom-control-input"
type="checkbox"
id="use_specific_stock_entry"
name="use_specific_stock_entry"
value="1">
<label class="form-check-label custom-control-label"
for="use_specific_stock_entry">{{ $__t('Use a specific stock item') }}
&nbsp;<i class="fas fa-question-circle text-muted"
data-toggle="tooltip"
data-trigger="hover click"
title="{{ $__t('The first item in this list would be picked by the default rule which is "Opened first, then first due first, then first in first out"') }}"></i>
</label>
</div>
<select disabled
class="custom-control custom-select mt-2"
id="specific_stock_entry"
name="specific_stock_entry">
<option></option>
</select>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input class="form-check-input custom-control-input" type="checkbox" id="use_specific_stock_entry"
name="use_specific_stock_entry" value="1">
<label class="form-check-label custom-control-label"
for="use_specific_stock_entry">{{ $__t('Use a specific stock item') }}
&nbsp;<i class="fas fa-question-circle text-muted" data-toggle="tooltip"
data-trigger="hover click"
title="{{ $__t('The first item in this list would be picked by the default rule which is "Opened first, then first due first, then first in first out"') }}"></i>
</label>
</div>
<select disabled class="custom-control custom-select mt-2" id="specific_stock_entry"
name="specific_stock_entry">
<option></option>
</select>
</div>
<button id="save-transfer-button"
class="btn btn-success">{{ $__t('OK') }}</button>
<button id="save-transfer-button" class="btn btn-success">{{ $__t('OK') }}</button>
</form>
</div>
</form>
</div>
<div class="col-12 col-md-6 col-xl-4 hide-when-embedded">
@include('components.productcard')
</div>
</div>
<div class="col-12 col-md-6 col-xl-4 hide-when-embedded">
@include('components.productcard')
</div>
</div>
@stop

View File

@ -5,111 +5,95 @@
@section('viewJsName', 'userentities')
@section('content')
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100 m-1 mt-md-0 mb-md-0 float-right"
id="related-links">
<a class="btn btn-primary responsive-button show-as-dialog-link"
href="{{ $U('/userentity/new?embedded') }}">
{{ $__t('Add') }}
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100 m-1 mt-md-0 mb-md-0 float-right"
id="related-links">
<a class="btn btn-primary responsive-button show-as-dialog-link"
href="{{ $U('/userentity/new?embedded') }}">
{{ $__t('Add') }}
</a>
</div>
</div>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row collapse d-md-flex"
id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text"
id="search"
class="form-control"
placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row collapse d-md-flex" id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text" id="search" class="form-control" placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button" class="btn btn-sm btn-outline-info" href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row">
<div class="col">
<table id="userentities-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#userentities-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Name') }}</th>
<th>{{ $__t('Caption') }}</th>
</tr>
</thead>
<tbody class="d-none">
@foreach($userentities as $userentity)
<tr>
<td class="fit-content border-right">
<a class="btn btn-info btn-sm show-as-dialog-link"
href="{{ $U('/userentity/') }}{{ $userentity->id }}?embedded"
data-toggle="tooltip"
title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger btn-sm userentity-delete-button"
href="#"
data-userentity-id="{{ $userentity->id }}"
data-userentity-name="{{ $userentity->name }}"
data-toggle="tooltip"
title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
<a class="btn btn-secondary btn-sm"
href="{{ $U('/userfields?entity=userentity-') }}{{ $userentity->name }}">
<i class="fas fa-th-list"></i> {{ $__t('Configure fields') }}
</a>
</td>
<td>
{{ $userentity->name }}
</td>
<td>
{{ $userentity->caption }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="col">
{{-- TODO: DataTables: dynamic data: userentities --}}
<table id="userentities-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#userentities-table" href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Name') }}</th>
<th>{{ $__t('Caption') }}</th>
</tr>
</thead>
<tbody class="d-none">
@foreach ($userentities as $userentity)
<tr>
<td class="fit-content border-right">
<a class="btn btn-info btn-sm show-as-dialog-link"
href="{{ $U('/userentity/') }}{{ $userentity->id }}?embedded" data-toggle="tooltip"
title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger btn-sm userentity-delete-button" href="#"
data-userentity-id="{{ $userentity->id }}"
data-userentity-name="{{ $userentity->name }}" data-toggle="tooltip"
title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
<a class="btn btn-secondary btn-sm"
href="{{ $U('/userfields?entity=userentity-') }}{{ $userentity->name }}">
<i class="fas fa-th-list"></i> {{ $__t('Configure fields') }}
</a>
</td>
<td>
{{ $userentity->name }}
</td>
<td>
{{ $userentity->caption }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@stop

View File

@ -1,153 +1,138 @@
@extends('layout.default')
@if($mode == 'edit')
@section('title', $__t('Edit userfield'))
@if ($mode == 'edit')
@section('title', $__t('Edit userfield'))
@else
@section('title', $__t('Create userfield'))
@section('title', $__t('Create userfield'))
@endif
@section('viewJsName', 'userfieldform')
@section('content')
<div class="row">
<div class="col">
<h2 class="title">@yield('title')</h2>
</div>
</div>
<div class="row">
<div class="col">
<h2 class="title">@yield('title')</h2>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row">
<div class="col-lg-6 col-12">
<script>
Grocy.EditMode = '{{ $mode }}';
</script>
<div class="row">
<div class="col-lg-6 col-12">
<script>
Grocy.EditMode = '{{ $mode }}';
</script>
@if($mode == 'edit')
<script>
Grocy.EditObjectId = {{ $userfield->id }};
</script>
@endif
@if ($mode == 'edit')
<script>
Grocy.EditObjectId = {{ $userfield->id }};
</script>
@endif
<form id="userfield-form"
novalidate>
<form id="userfield-form" novalidate>
<div class="form-group">
<label for="entity">{{ $__t('Entity') }}</label>
<select required
class="custom-control custom-select"
id="entity"
name="entity">
<option></option>
@foreach($entities as $entity)
<option @if($mode=='edit'
&&
$userfield->entity == $entity) selected="selected" @endif value="{{ $entity }}">{{ $entity }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('A entity is required') }}</div>
</div>
<div class="form-group">
<label for="entity">{{ $__t('Entity') }}</label>
{{-- TODO: Select2: dynamic data: userentities --}}
<select required class="custom-control custom-select" id="entity" name="entity">
<option></option>
@foreach ($entities as $entity)
<option @if ($mode == 'edit' && $userfield->entity == $entity) selected="selected" @endif
value="{{ $entity }}">{{ $entity }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('A entity is required') }}</div>
</div>
<div class="form-group">
<label for="name">
{{ $__t('Name') }}
<i class="fas fa-question-circle text-muted"
data-toggle="tooltip"
data-trigger="hover click"
title="{{ $__t('This is the internal field name, e. g. for the API') }}"></i>
</label>
<input type="text"
class="form-control"
required
pattern="^[a-zA-Z0-9]*$"
id="name"
name="name"
value="@if($mode == 'edit'){{ $userfield->name }}@endif">
<div class="invalid-feedback">{{ $__t('This is required and can only contain letters and numbers') }}</div>
</div>
<div class="form-group">
<label for="name">
{{ $__t('Name') }}
<i class="fas fa-question-circle text-muted" data-toggle="tooltip" data-trigger="hover click"
title="{{ $__t('This is the internal field name, e. g. for the API') }}"></i>
</label>
<input type="text" class="form-control" required pattern="^[a-zA-Z0-9]*$" id="name" name="name"
value="@if ($mode == 'edit') {{ $userfield->name }} @endif">
<div class="invalid-feedback">{{ $__t('This is required and can only contain letters and numbers') }}
</div>
</div>
<div class="form-group">
<label for="name">
{{ $__t('Caption') }}
<i class="fas fa-question-circle text-muted"
data-toggle="tooltip"
data-trigger="hover click"
title="{{ $__t('This is used to display the field on the frontend') }}"></i>
</label>
<input type="text"
class="form-control"
required
id="caption"
name="caption"
value="@if($mode == 'edit'){{ $userfield->caption }}@endif">
<div class="invalid-feedback">{{ $__t('A caption is required') }}</div>
</div>
<div class="form-group">
<label for="name">
{{ $__t('Caption') }}
<i class="fas fa-question-circle text-muted" data-toggle="tooltip" data-trigger="hover click"
title="{{ $__t('This is used to display the field on the frontend') }}"></i>
</label>
<input type="text" class="form-control" required id="caption" name="caption"
value="@if ($mode == 'edit') {{ $userfield->caption }} @endif">
<div class="invalid-feedback">{{ $__t('A caption is required') }}</div>
</div>
@php if($mode == 'edit' && !empty($userfield->sort_number)) { $value = $userfield->sort_number; } else { $value = ''; } @endphp
@include('components.numberpicker', array(
'id' => 'sort_number',
'label' => 'Sort number',
'min' => 0,
'value' => $value,
'isRequired' => false,
'hint' => $__t('Multiple Userfields will be ordered by that number on the input form')
))
@php
if ($mode == 'edit' && !empty($userfield->sort_number)) {
$value = $userfield->sort_number;
} else {
$value = '';
}
@endphp
@include('components.numberpicker', [
'id' => 'sort_number',
'label' => 'Sort number',
'min' => 0,
'value' => $value,
'isRequired' => false,
'hint' => $__t('Multiple Userfields will be ordered by that number on the input form'),
])
<div class="form-group">
<label for="type">{{ $__t('Type') }}</label>
<select required
class="custom-control custom-select"
id="type"
name="type">
<option></option>
@foreach($userfieldTypes as $userfieldType)
<option @if($mode=='edit'
&&
$userfield->type == $userfieldType) selected="selected" @endif value="{{ $userfieldType }}">{{ $__t($userfieldType) }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('A type is required') }}</div>
</div>
<div class="form-group">
<label for="type">{{ $__t('Type') }}</label>
{{-- TODO: Select2: static data --}}
<select required class="custom-control custom-select" id="type" name="type">
<option></option>
@foreach ($userfieldTypes as $userfieldType)
<option @if ($mode == 'edit' && $userfield->type == $userfieldType) selected="selected" @endif
value="{{ $userfieldType }}">{{ $__t($userfieldType) }}</option>
@endforeach
</select>
<div class="invalid-feedback">{{ $__t('A type is required') }}</div>
</div>
<div class="form-group d-none">
<label for="config">{{ $__t('Configuration') }} <span id="config-hint"
class="small text-muted"></span></label>
<textarea class="form-control"
rows="10"
id="config"
name="config">@if($mode == 'edit'){{ $userfield->config }}@endif</textarea>
</div>
<div class="form-group d-none">
<label for="config">{{ $__t('Configuration') }} <span id="config-hint"
class="small text-muted"></span></label>
<textarea class="form-control" rows="10" id="config" name="config">
@if ($mode == 'edit')
{{ $userfield->config }}
@endif
</textarea>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input @if($mode=='edit'
&&
$userfield->show_as_column_in_tables == 1) checked @endif class="form-check-input custom-control-input" type="checkbox" id="show_as_column_in_tables" name="show_as_column_in_tables" value="1">
<label class="form-check-label custom-control-label"
for="show_as_column_in_tables">{{ $__t('Show as column in tables') }}</label>
</div>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input @if ($mode == 'edit' && $userfield->show_as_column_in_tables == 1) checked @endif
class="form-check-input custom-control-input" type="checkbox" id="show_as_column_in_tables"
name="show_as_column_in_tables" value="1">
<label class="form-check-label custom-control-label"
for="show_as_column_in_tables">{{ $__t('Show as column in tables') }}</label>
</div>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input @if($mode=='edit'
&&
$userfield->input_required == 1) checked @endif class="form-check-input custom-control-input" type="checkbox" id="input_required" name="input_required" value="1">
<label class="form-check-label custom-control-label"
for="input_required">
{{ $__t('Mandatory') }}
&nbsp;<i class="fas fa-question-circle text-muted"
data-toggle="tooltip"
data-trigger="hover click"
title="{{ $__t('When enabled, then this field must be filled on the destination form') }}"></i>
</label>
</div>
</div>
<div class="form-group">
<div class="custom-control custom-checkbox">
<input @if ($mode == 'edit' && $userfield->input_required == 1) checked @endif
class="form-check-input custom-control-input" type="checkbox" id="input_required"
name="input_required" value="1">
<label class="form-check-label custom-control-label" for="input_required">
{{ $__t('Mandatory') }}
&nbsp;<i class="fas fa-question-circle text-muted" data-toggle="tooltip"
data-trigger="hover click"
title="{{ $__t('When enabled, then this field must be filled on the destination form') }}"></i>
</label>
</div>
</div>
<button id="save-userfield-button"
class="btn btn-success">{{ $__t('Save') }}</button>
<button id="save-userfield-button" class="btn btn-success">{{ $__t('Save') }}</button>
</form>
</div>
</div>
</form>
</div>
</div>
@stop

View File

@ -5,134 +5,117 @@
@section('viewJsName', 'userfields')
@section('content')
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100 m-1 mt-md-0 mb-md-0 float-right"
id="related-links">
<a id="new-userfield-button"
class="btn btn-primary responsive-button show-as-dialog-link"
href="{{ $U('/userfield/new?embedded') }}">
{{ $__t('Add') }}
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100 m-1 mt-md-0 mb-md-0 float-right"
id="related-links">
<a id="new-userfield-button" class="btn btn-primary responsive-button show-as-dialog-link"
href="{{ $U('/userfield/new?embedded') }}">
{{ $__t('Add') }}
</a>
</div>
</div>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row collapse d-md-flex"
id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text"
id="search"
class="form-control"
placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Entity') }}</span>
</div>
<select class="custom-control custom-select"
id="entity-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach($entities as $entity)
<option value="{{ $entity }}">{{ $entity }}</option>
@endforeach
</select>
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row collapse d-md-flex" id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text" id="search" class="form-control" placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-filter"></i>&nbsp;{{ $__t('Entity') }}</span>
</div>
{{-- TODO: Select2: dynamic data: userfields --}}
<select class="custom-control custom-select" id="entity-filter">
<option value="all">{{ $__t('All') }}</option>
@foreach ($entities as $entity)
<option value="{{ $entity }}">{{ $entity }}</option>
@endforeach
</select>
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button" class="btn btn-sm btn-outline-info" href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row">
<div class="col">
<table id="userfields-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#userfields-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th class="allow-grouping">{{ $__t('Entity') }}</th>
<th>{{ $__t('Name') }}</th>
<th>{{ $__t('Caption') }}</th>
<th class="allow-grouping">{{ $__t('Type') }}</th>
<th>{{ $__t('Sort number') }}</th>
</tr>
</thead>
<tbody class="d-none">
@foreach($userfields as $userfield)
<tr>
<td class="fit-content border-right">
<a class="btn btn-info btn-sm show-as-dialog-link"
href="{{ $U('/userfield/') }}{{ $userfield->id }}?embedded"
data-toggle="tooltip"
title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger btn-sm userfield-delete-button"
href="#"
data-userfield-id="{{ $userfield->id }}"
data-userfield-name="{{ $userfield->name }}"
data-toggle="tooltip"
title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
{{ $userfield->entity }}
</td>
<td>
{{ $userfield->name }}
</td>
<td>
{{ $userfield->caption }}
</td>
<td>
{{ $__t($userfield->type) }}
</td>
<td>
{{ $userfield->sort_number }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="col">
{{-- TODO: DataTables: dynamic data: userfields --}}
<table id="userfields-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#userfields-table" href="#"><i class="fas fa-eye"></i></a>
</th>
<th class="allow-grouping">{{ $__t('Entity') }}</th>
<th>{{ $__t('Name') }}</th>
<th>{{ $__t('Caption') }}</th>
<th class="allow-grouping">{{ $__t('Type') }}</th>
<th>{{ $__t('Sort number') }}</th>
</tr>
</thead>
<tbody class="d-none">
@foreach ($userfields as $userfield)
<tr>
<td class="fit-content border-right">
<a class="btn btn-info btn-sm show-as-dialog-link"
href="{{ $U('/userfield/') }}{{ $userfield->id }}?embedded" data-toggle="tooltip"
title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger btn-sm userfield-delete-button" href="#"
data-userfield-id="{{ $userfield->id }}"
data-userfield-name="{{ $userfield->name }}" data-toggle="tooltip"
title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
{{ $userfield->entity }}
</td>
<td>
{{ $userfield->name }}
</td>
<td>
{{ $userfield->caption }}
</td>
<td>
{{ $__t($userfield->type) }}
</td>
<td>
{{ $userfield->sort_number }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@stop

View File

@ -5,112 +5,103 @@
@section('viewJsName', 'userobjects')
@section('content')
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title mr-2 order-0">
@yield('title')
</h2>
<h2 class="mb-0 mr-auto order-3 order-md-1 width-xs-sm-100">
<span class="text-muted small">{{ $userentity->description }}</span>
</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100 m-1 mt-md-0 mb-md-0 float-right"
id="related-links">
<a class="btn btn-primary responsive-button mr-1 show-as-dialog-link"
href="{{ $U('/userobject/' . $userentity->name . '/new?embedded') }}">
{{ $__t('Add') }}
</a>
<a class="btn btn-outline-secondary d-print-none"
href="{{ $U('/userfields?entity=' . 'userentity-' . $userentity->name) }}">
{{ $__t('Configure fields') }}
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title mr-2 order-0">
@yield('title')
</h2>
<h2 class="mb-0 mr-auto order-3 order-md-1 width-xs-sm-100">
<span class="text-muted small">{{ $userentity->description }}</span>
</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100 m-1 mt-md-0 mb-md-0 float-right"
id="related-links">
<a class="btn btn-primary responsive-button mr-1 show-as-dialog-link"
href="{{ $U('/userobject/' . $userentity->name . '/new?embedded') }}">
{{ $__t('Add') }}
</a>
<a class="btn btn-outline-secondary d-print-none"
href="{{ $U('/userfields?entity=' . 'userentity-' . $userentity->name) }}">
{{ $__t('Configure fields') }}
</a>
</div>
</div>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row collapse d-md-flex"
id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text"
id="search"
class="form-control"
placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row collapse d-md-flex" id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text" id="search" class="form-control" placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button" class="btn btn-sm btn-outline-info" href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row">
<div class="col">
<table id="userobjects-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right d-print-none"></th>
<div class="row">
<div class="col">
{{-- TODO: DataTables: dynamic data: userobjects --}}
<table id="userobjects-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right d-print-none"></th>
@include('components.userfields_thead', array(
'userfields' => $userfields
))
@include('components.userfields_thead', [
'userfields' => $userfields,
])
</tr>
</thead>
<tbody class="d-none">
@foreach($userobjects as $userobject)
<tr>
<td class="fit-content border-right d-print-none">
<a class="btn btn-info btn-sm show-as-dialog-link"
href="{{ $U('/userobject/' . $userentity->name . '/') }}{{ $userobject->id }}?embedded"
data-toggle="tooltip"
title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger btn-sm userobject-delete-button"
href="#"
data-userobject-id="{{ $userobject->id }}"
data-toggle="tooltip"
title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
</td>
</tr>
</thead>
<tbody class="d-none">
@foreach ($userobjects as $userobject)
<tr>
<td class="fit-content border-right d-print-none">
<a class="btn btn-info btn-sm show-as-dialog-link"
href="{{ $U('/userobject/' . $userentity->name . '/') }}{{ $userobject->id }}?embedded"
data-toggle="tooltip" title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-danger btn-sm userobject-delete-button" href="#"
data-userobject-id="{{ $userobject->id }}" data-toggle="tooltip"
title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
</td>
@include('components.userfields_tbody', array(
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $userobject->id)
))
@include('components.userfields_tbody', [
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue(
$userfieldValues,
'object_id',
$userobject->id
),
])
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@stop

View File

@ -5,130 +5,114 @@
@section('viewJsName', 'users')
@section('content')
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3"
type="button"
data-toggle="collapse"
data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100 m-1 mt-md-0 mb-md-0 float-right"
id="related-links">
<a class="btn btn-primary responsive-button"
href="{{ $U('/user/new') }}">
{{ $__t('Add') }}
</a>
<a class="btn btn-outline-secondary m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/userfields?entity=users') }}">
{{ $__t('Configure userfields') }}
</a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="title-related-links">
<h2 class="title">@yield('title')</h2>
<div class="float-right">
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#table-filter-row">
<i class="fas fa-filter"></i>
</button>
<button class="btn btn-outline-dark d-md-none mt-2 order-1 order-md-3" type="button"
data-toggle="collapse" data-target="#related-links">
<i class="fas fa-ellipsis-v"></i>
</button>
</div>
<div class="related-links collapse d-md-flex order-2 width-xs-sm-100 m-1 mt-md-0 mb-md-0 float-right"
id="related-links">
<a class="btn btn-primary responsive-button" href="{{ $U('/user/new') }}">
{{ $__t('Add') }}
</a>
<a class="btn btn-outline-secondary m-1 mt-md-0 mb-md-0 float-right"
href="{{ $U('/userfields?entity=users') }}">
{{ $__t('Configure userfields') }}
</a>
</div>
</div>
</div>
</div>
<hr class="my-2">
<hr class="my-2">
<div class="row collapse d-md-flex"
id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text"
id="search"
class="form-control"
placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button"
class="btn btn-sm btn-outline-info"
href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row collapse d-md-flex" id="table-filter-row">
<div class="col-12 col-md-6 col-xl-3">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-search"></i></span>
</div>
<input type="text" id="search" class="form-control" placeholder="{{ $__t('Search') }}">
</div>
</div>
<div class="col">
<div class="float-right">
<a id="clear-filter-button" class="btn btn-sm btn-outline-info" href="#">
{{ $__t('Clear filter') }}
</a>
</div>
</div>
</div>
<div class="row">
<div class="col">
<table id="users-table"
class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip"
data-toggle="tooltip"
title="{{ $__t('Table options') }}"
data-table-selector="#users-table"
href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Username') }}</th>
<th>{{ $__t('First name') }}</th>
<th>{{ $__t('Last name') }}</th>
<div class="row">
<div class="col">
{{-- TODO: DataTables: dynamic data: users --}}
<table id="users-table" class="table table-sm table-striped nowrap w-100">
<thead>
<tr>
<th class="border-right"><a class="text-muted change-table-columns-visibility-button"
data-toggle="tooltip" data-toggle="tooltip" title="{{ $__t('Table options') }}"
data-table-selector="#users-table" href="#"><i class="fas fa-eye"></i></a>
</th>
<th>{{ $__t('Username') }}</th>
<th>{{ $__t('First name') }}</th>
<th>{{ $__t('Last name') }}</th>
@include('components.userfields_thead', array(
'userfields' => $userfields
))
</tr>
</thead>
<tbody class="d-none">
@foreach($users as $user)
<tr>
<td class="fit-content border-right">
<a class="btn btn-info btn-sm"
href="{{ $U('/user/') }}{{ $user->id }}"
data-toggle="tooltip"
title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-info btn-sm"
href="{{ $U('/user/' . $user->id . '/permissions') }}"
data-toggle="tooltip"
title="{{ $__t('Configure user permissions') }}">
<i class="fas fa-lock"></i>
</a>
<a class="btn btn-danger btn-sm user-delete-button @if($user->id == GROCY_USER_ID) disabled @endif"
href="#"
data-user-id="{{ $user->id }}"
data-user-username="{{ $user->username }}"
data-toggle="tooltip"
title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
{{ $user->username }}
</td>
<td>
{{ $user->first_name }}
</td>
<td>
{{ $user->last_name }}
</td>
@include('components.userfields_thead', [
'userfields' => $userfields,
])
</tr>
</thead>
<tbody class="d-none">
@foreach ($users as $user)
<tr>
<td class="fit-content border-right">
<a class="btn btn-info btn-sm" href="{{ $U('/user/') }}{{ $user->id }}"
data-toggle="tooltip" title="{{ $__t('Edit this item') }}">
<i class="fas fa-edit"></i>
</a>
<a class="btn btn-info btn-sm" href="{{ $U('/user/' . $user->id . '/permissions') }}"
data-toggle="tooltip" title="{{ $__t('Configure user permissions') }}">
<i class="fas fa-lock"></i>
</a>
<a class="btn btn-danger btn-sm user-delete-button @if ($user->id == GROCY_USER_ID) disabled @endif"
href="#" data-user-id="{{ $user->id }}"
data-user-username="{{ $user->username }}" data-toggle="tooltip"
title="{{ $__t('Delete this item') }}">
<i class="fas fa-trash"></i>
</a>
</td>
<td>
{{ $user->username }}
</td>
<td>
{{ $user->first_name }}
</td>
<td>
{{ $user->last_name }}
</td>
@include('components.userfields_tbody', array(
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue($userfieldValues, 'object_id', $user->id)
))
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@include('components.userfields_tbody', [
'userfields' => $userfields,
'userfieldValues' => FindAllObjectsInArrayByPropertyValue(
$userfieldValues,
'object_id',
$user->id
),
])
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@stop

View File

@ -238,7 +238,7 @@ datatables.net-bs4@1.10.16:
datatables.net "1.10.16"
jquery ">=1.7"
datatables.net-bs4@>=1.11.3, datatables.net-bs4@^1.10.22:
datatables.net-bs4@>=1.11.3:
version "1.11.4"
resolved "https://registry.yarnpkg.com/datatables.net-bs4/-/datatables.net-bs4-1.11.4.tgz#caa82ab1a989bf1462f075b91213df865060d1ec"
integrity sha512-4V2uSxFloX1jRIsy4eAt1INyp5M5Pq5SV017/naq3zpVKraaFwqFjLhtkx64UHGXqcPj7egvj27dVcdnNIKnNA==
@ -246,7 +246,15 @@ datatables.net-bs4@>=1.11.3, datatables.net-bs4@^1.10.22:
datatables.net ">=1.11.3"
jquery ">=1.7"
datatables.net-colreorder-bs4@^1.5.2:
datatables.net-bs4@^1.11.5:
version "1.11.5"
resolved "https://registry.yarnpkg.com/datatables.net-bs4/-/datatables.net-bs4-1.11.5.tgz#7767a27991c75716ce7fef1aa6a41d01bfeaefbd"
integrity sha512-tYE9MnyWIW4P8nm41yQnCqW4dUPK3M7qsuxQY5rViFFKDEU0bUUNAnE9+69P5/4Exe4uyXaY05xFkD00niPZUA==
dependencies:
datatables.net ">=1.11.3"
jquery ">=1.7"
datatables.net-colreorder-bs4@^1.5.5:
version "1.5.5"
resolved "https://registry.yarnpkg.com/datatables.net-colreorder-bs4/-/datatables.net-colreorder-bs4-1.5.5.tgz#52ca2f95148572583ad619d6c9cdb44f1952d355"
integrity sha512-MGAJ/k/FeSK2Kccio5k0oBRacBpmaIKP2wXYfC64ONZFjOFxKBSmFevfg7yPdipYdYDwoQqDnmw0MpdIL7UUug==
@ -255,7 +263,7 @@ datatables.net-colreorder-bs4@^1.5.2:
datatables.net-colreorder ">=1.5.4"
jquery ">=1.7"
datatables.net-colreorder@>=1.5.4, datatables.net-colreorder@^1.5.2:
datatables.net-colreorder@>=1.5.4, datatables.net-colreorder@^1.5.5:
version "1.5.5"
resolved "https://registry.yarnpkg.com/datatables.net-colreorder/-/datatables.net-colreorder-1.5.5.tgz#0de93e460cba5eb0167c0c491a2da0c76a2e3b12"
integrity sha512-AUwv5A/87I4hg7GY/WbhRrDhqng9b019jLvvKutHibSPCEtMDWqyNtuP0q8zYoquqU9UQ1/nqXLW/ld8TzIDYQ==
@ -263,12 +271,12 @@ datatables.net-colreorder@>=1.5.4, datatables.net-colreorder@^1.5.2:
datatables.net ">=1.11.3"
jquery ">=1.7"
datatables.net-plugins@^1.10.20:
version "1.11.4"
resolved "https://registry.yarnpkg.com/datatables.net-plugins/-/datatables.net-plugins-1.11.4.tgz#8eb7915cd9f43ba8ba256b89e06917aa24d1ac41"
integrity sha512-39yyyoCCavagE0mO1BFsrRPeak5BwOlbtSACdGpPNf3jG5Lm7D6vEUPPcpGy6eLxjCiG/orMxlAqb8E5lSZtoA==
datatables.net-plugins@^1.11.5:
version "1.11.5"
resolved "https://registry.yarnpkg.com/datatables.net-plugins/-/datatables.net-plugins-1.11.5.tgz#c53ebbd0ab3473a08c6ae36eb1990a66de599b89"
integrity sha512-+Rsf/fyLG8GyFqp7Bvd1ElqWGQO3NPsx2VADn9X8QaZbctshGVW0sqvR5V7iHHgY6OY1LR0+t6qIMhan9BM4gA==
datatables.net-rowgroup-bs4@^1.1.2:
datatables.net-rowgroup-bs4@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/datatables.net-rowgroup-bs4/-/datatables.net-rowgroup-bs4-1.1.4.tgz#dd4fad888edea895acd06fe8cf66816809d78eec"
integrity sha512-D0+LxraRjvV1RpPNtXJuW9Z4Jn90Sykb7ytJC/5eJsEYc9WnLUvxWEok7fqPpl3dWphQKg5ZbWpKG55Gd1IIXA==
@ -277,7 +285,7 @@ datatables.net-rowgroup-bs4@^1.1.2:
datatables.net-rowgroup ">=1.1.3"
jquery ">=1.7"
datatables.net-rowgroup@>=1.1.3, datatables.net-rowgroup@^1.1.2:
datatables.net-rowgroup@>=1.1.3, datatables.net-rowgroup@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/datatables.net-rowgroup/-/datatables.net-rowgroup-1.1.4.tgz#3eea91951d46f6c207d2e0c03cb3d635b7b09689"
integrity sha512-Oe9mL3X8RXLOQZblJVWTYD0melyw3xoPeQ3T2x1k2guTFxob8/2caKuzn95oFJau6tvbhsvY/QneTaCzHRKnnQ==
@ -285,7 +293,7 @@ datatables.net-rowgroup@>=1.1.3, datatables.net-rowgroup@^1.1.2:
datatables.net ">=1.11.3"
jquery ">=1.7"
datatables.net-select-bs4@^1.3.1:
datatables.net-select-bs4@^1.3.4:
version "1.3.4"
resolved "https://registry.yarnpkg.com/datatables.net-select-bs4/-/datatables.net-select-bs4-1.3.4.tgz#07336260be0aa7741a61b82188350f4d1e422a02"
integrity sha512-hXxxTwR9Mx8xwD55g8hNiLt035afguKZ9Ejsdm5/mo3wmm9ml7gs8QG5fJuMRwtrdP9EKcnjc54+zVoHymEcgw==
@ -294,7 +302,7 @@ datatables.net-select-bs4@^1.3.1:
datatables.net-select ">=1.3.3"
jquery ">=1.7"
datatables.net-select@>=1.3.3, datatables.net-select@^1.3.1:
datatables.net-select@>=1.3.3, datatables.net-select@^1.3.4:
version "1.3.4"
resolved "https://registry.yarnpkg.com/datatables.net-select/-/datatables.net-select-1.3.4.tgz#7970587a8d8db8ba70a4cccb89b8519bb518116d"
integrity sha512-iQ/dBHIWkhfCBxzNdtef79seCNO1ZsA5zU0Uiw3R2mlwmjcJM1xn6pFNajke6SX7VnlzndGDHGqzzEljSqz4pA==
@ -309,13 +317,20 @@ datatables.net@1.10.16:
dependencies:
jquery ">=1.7"
datatables.net@>=1.11.3, datatables.net@^1.10.22:
datatables.net@>=1.11.3:
version "1.11.4"
resolved "https://registry.yarnpkg.com/datatables.net/-/datatables.net-1.11.4.tgz#5f3e1ec134fa532e794fbd47c13f8333d7a5c455"
integrity sha512-z9LG4O0VYOYzp+rnArLExvnUWV8ikyWBcHYZEKDfVuz7BKxQdEq4a/tpO0Trbm+FL1+RY7UEIh+UcYNY/hwGxA==
dependencies:
jquery ">=1.7"
datatables.net@^1.11.5:
version "1.11.5"
resolved "https://registry.yarnpkg.com/datatables.net/-/datatables.net-1.11.5.tgz#858a69953a01e1d5b18786769802117b04b8e3c9"
integrity sha512-nlFst2xfwSWaQgaOg5sXVG3cxYC0tH8E8d65289w9ROgF2TmLULOOpcdMpyxxUim/qEwVSEem42RjkTWEpr3eA==
dependencies:
jquery ">=1.7"
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
@ -686,6 +701,16 @@ safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
select2-theme-bootstrap4@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/select2-theme-bootstrap4/-/select2-theme-bootstrap4-1.0.1.tgz#aa476930988cb61b05c77d1173a937688d2bb4be"
integrity sha512-gn9zN+ZppzsjRCQzHMfhDyizcCNlzbsB059qAXjyEaFYx7OA3677LYEIbYiGmliuF6y9Dbuae8X8Rk/Z4SBleQ==
select2@^4.0.13:
version "4.0.13"
resolved "https://registry.yarnpkg.com/select2/-/select2-4.0.13.tgz#0dbe377df3f96167c4c1626033e924372d8ef44d"
integrity sha512-1JeB87s6oN/TDxQQYCvS5EFoQyvV6eYMZZ0AeA4tdFDYWN3BAGZ8npr17UBFddU0lgAt3H0yjX3X6/ekOj1yjw==
sprintf-js@^1.0.3, sprintf-js@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673"