From: Roland Häder <roland@mxchange.org>
Date: Tue, 31 Oct 2017 18:44:33 +0000 (+0100)
Subject: Maybe cherry-pick:
X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=113cd842d0c5b969d5a906f7b95581d8b4f1fff8;p=jfinancials-war.git

Maybe cherry-pick:
- added mini-links JSF tags for product categories and generic products
- added form templates for above again
- added views for listing both again
- added navigation rules to faces-config.xml
- rewrote converter to CDI "lookup" (programatic) which finally let the
  converter work without any EJB calls
- added filtered list for categories and products
- added product unit amount and type, like 1 liter
- categories and products have now both i18n keys and not direct and
  untranslateable titles
- rewrote + sorted renderGenericProduct() as products now have i18n keys
- added menu entries

Signed-off-by: Roland Häder <roland@mxchange.org>
---

diff --git a/src/java/org/mxchange/jfinancials/beans/category/FinancialAdminCategoryWebRequestBean.java b/src/java/org/mxchange/jfinancials/beans/category/FinancialAdminCategoryWebRequestBean.java
index 3a746f2c..1764c64f 100644
--- a/src/java/org/mxchange/jfinancials/beans/category/FinancialAdminCategoryWebRequestBean.java
+++ b/src/java/org/mxchange/jfinancials/beans/category/FinancialAdminCategoryWebRequestBean.java
@@ -64,9 +64,9 @@ public class FinancialAdminCategoryWebRequestBean extends BaseFinancialsBean imp
 	private Boolean categoryShownInStatistics;
 
 	/**
-	 * Category categoryTitle
+	 * Category categoryI18nKey
 	 */
-	private String categoryTitle;
+	private String categoryI18nKey;
 
 	/**
 	 * Parent category
@@ -128,21 +128,21 @@ public class FinancialAdminCategoryWebRequestBean extends BaseFinancialsBean imp
 	}
 
 	/**
-	 * Getter for category title
+	 * Getter for category i18n key
 	 * <p>
-	 * @return the title
+	 * @return Category i18n key
 	 */
-	public String getCategoryTitle () {
-		return this.categoryTitle;
+	public String getCategoryI18nKey () {
+		return this.categoryI18nKey;
 	}
 
 	/**
-	 * Setter for category title
+	 * Setter for category i18n key
 	 * <p>
-	 * @param categoryTitle the title to set
+	 * @param categoryI18nKey Category i18n key
 	 */
-	public void setCategoryTitle (final String categoryTitle) {
-		this.categoryTitle = categoryTitle;
+	public void setCategoryI18nKey (final String categoryI18nKey) {
+		this.categoryI18nKey = categoryI18nKey;
 	}
 
 	/**
@@ -168,7 +168,7 @@ public class FinancialAdminCategoryWebRequestBean extends BaseFinancialsBean imp
 	 */
 	private void clear () {
 		// Clear all fields
-		this.setCategoryTitle(null);
+		this.setCategoryI18nKey(null);
 		this.setParentCategory(null);
 	}
 
@@ -179,7 +179,7 @@ public class FinancialAdminCategoryWebRequestBean extends BaseFinancialsBean imp
 	 */
 	private Category createCategoryInstance () {
 		// Create category
-		final Category category = new ProductCategory(this.getCategoryTitle(), this.getParentCategory(), this.getCategoryShownInStatistics());
+		final Category category = new ProductCategory(this.getCategoryI18nKey(), this.getParentCategory(), this.getCategoryShownInStatistics());
 
 		// Return it
 		return category;
diff --git a/src/java/org/mxchange/jfinancials/beans/category/FinancialCategoryWebRequestBean.java b/src/java/org/mxchange/jfinancials/beans/category/FinancialCategoryWebRequestBean.java
index d9d588c9..e45879a9 100644
--- a/src/java/org/mxchange/jfinancials/beans/category/FinancialCategoryWebRequestBean.java
+++ b/src/java/org/mxchange/jfinancials/beans/category/FinancialCategoryWebRequestBean.java
@@ -52,7 +52,7 @@ public class FinancialCategoryWebRequestBean extends BaseFinancialsBean implemen
 	/**
 	 * List of all categories
 	 */
-	private List<Category> allCategories;
+	private final List<Category> allCategories;
 
 	/**
 	 * EJB for general category stuff
@@ -67,6 +67,11 @@ public class FinancialCategoryWebRequestBean extends BaseFinancialsBean implemen
 	@NamedCache (cacheName = "categoryCache")
 	private Cache<Long, Category> categoryCache;
 
+	/**
+	 * A list of filtered categories
+	 */
+	private List<Category> filteredCategories;
+
 	/**
 	 * Default constructor
 	 */
@@ -135,6 +140,26 @@ public class FinancialCategoryWebRequestBean extends BaseFinancialsBean implemen
 		return category;
 	}
 
+	/**
+	 * Getter for filtered category list
+	 * <p>
+	 * @return Filtered category list
+	 */
+	@SuppressWarnings ("ReturnOfCollectionOrArrayField")
+	public List<Category> getFilteredCategories () {
+		return this.filteredCategories;
+	}
+
+	/**
+	 * Setter for filtered category list
+	 * <p>
+	 * @param filteredCategories Filtered category list
+	 */
+	@SuppressWarnings ("AssignmentToCollectionOrArrayFieldFromParameter")
+	public void setFilteredCategories (final List<Category> filteredCategories) {
+		this.filteredCategories = filteredCategories;
+	}
+
 	/**
 	 * Initialization of this bean
 	 */
diff --git a/src/java/org/mxchange/jfinancials/beans/helper/FinancialsWebRequestHelperBean.java b/src/java/org/mxchange/jfinancials/beans/helper/FinancialsWebRequestHelperBean.java
index e07d84d9..5592f369 100644
--- a/src/java/org/mxchange/jfinancials/beans/helper/FinancialsWebRequestHelperBean.java
+++ b/src/java/org/mxchange/jfinancials/beans/helper/FinancialsWebRequestHelperBean.java
@@ -42,6 +42,7 @@ import org.mxchange.jphone.events.mobile.created.ObservableCreatedMobileNumberEv
 import org.mxchange.jphone.model.phonenumbers.fax.DialableFaxNumber;
 import org.mxchange.jphone.model.phonenumbers.landline.DialableLandLineNumber;
 import org.mxchange.jphone.model.phonenumbers.mobile.DialableMobileNumber;
+import org.mxchange.jproduct.model.category.Category;
 import org.mxchange.jproduct.model.product.Product;
 import org.mxchange.jusercore.events.user.created.CreatedUserEvent;
 import org.mxchange.jusercore.events.user.created.ObservableCreatedUserEvent;
@@ -566,6 +567,31 @@ public class FinancialsWebRequestHelperBean extends BaseFinancialsBean implement
 		return sb.toString();
 	}
 
+	/**
+	 * Returns the product name and price. If null is provided, an empty string
+	 * is returned.
+	 * <p>
+	 * @param product Product instance
+	 * <p>
+	 * @return Product name
+	 */
+	public String renderGenericProduct (final Product product) {
+		// Default is empty string, so let's get started
+		final StringBuilder sb = new StringBuilder(10);
+
+		// Is a product set?
+		if (product instanceof Product) {
+			// Add name and price
+			sb.append(product.getProductI18nKey());
+			sb.append(" ("); //NOI18N
+			sb.append(this.localizationController.formatCurrency(product.getProductGrossPrice()));
+			sb.append(")"); //NOI18N
+		}
+
+		// Return it
+		return sb.toString();
+	}
+
 	/**
 	 * Returns the headquarters address. If null is provided, an empty string is
 	 * returned.
@@ -612,24 +638,29 @@ public class FinancialsWebRequestHelperBean extends BaseFinancialsBean implement
 	}
 
 	/**
-	 * Returns the product name and price. If null is provided, an empty string
-	 * is returned.
+	 * Returns the category's i18n string translated. If null is provided, an
+	 * empty string is returned.
 	 * <p>
-	 * @param product Product instance
+	 * @param category Product category instance
 	 * <p>
-	 * @return Product name
+	 * @return Category's i18n string translation
 	 */
-	public String renderProduct (final Product product) {
+	public String renderProductCategory (final Category category) {
 		// Default is empty string, so let's get started
 		final StringBuilder sb = new StringBuilder(10);
 
-		// Is a product set?
-		if (product instanceof Product) {
-			// Add name and price
-			sb.append(product.getProductTitle());
-			sb.append(" ("); //NOI18N
-			sb.append(this.localizationController.formatCurrency(product.getProductGrossPrice()));
-			sb.append(")"); //NOI18N
+		// Is a category set?
+		if (category instanceof Category) {
+			// Add title
+			sb.append(this.getMessageFromBundle(category.getCategoryI18nKey()));
+
+			// Is a parent category set?
+			if (category.getParentCategory() instanceof Category) {
+				// Then add it in braces, too
+				sb.append(" ("); //NOI18N
+				sb.append(this.getMessageFromBundle(category.getParentCategory().getCategoryI18nKey()));
+				sb.append(")"); //NOI18N
+			}
 		}
 
 		// Return it
diff --git a/src/java/org/mxchange/jfinancials/beans/product/FinancialAdminProductWebRequestBean.java b/src/java/org/mxchange/jfinancials/beans/product/FinancialAdminProductWebRequestBean.java
index db8058be..842d3b1f 100644
--- a/src/java/org/mxchange/jfinancials/beans/product/FinancialAdminProductWebRequestBean.java
+++ b/src/java/org/mxchange/jfinancials/beans/product/FinancialAdminProductWebRequestBean.java
@@ -81,8 +81,8 @@ public class FinancialAdminProductWebRequestBean extends BaseFinancialsBean impl
 	/**
 	 * Remote bean for products
 	 */
-	@EJB (lookup = "java:global/jfinancial-ejb/adminProduct!org.mxchange.jproduct.model.product.AdminProductSessionBeanRemote")
-	private AdminProductSessionBeanRemote productRemoteBean;
+	@EJB (lookup = "java:global/jfinancials-ejb/adminProduct!org.mxchange.jproduct.model.product.AdminProductSessionBeanRemote")
+	private AdminProductSessionBeanRemote adminProductBean;
 
 	/**
 	 * Product's tax rate
@@ -90,9 +90,19 @@ public class FinancialAdminProductWebRequestBean extends BaseFinancialsBean impl
 	private Float productTaxRate;
 
 	/**
-	 * Property productTitle
+	 * I18n key of product
 	 */
-	private String productTitle;
+	private String productI18nKey;
+
+	/**
+	 * Product's unit amount
+	 */
+	private Float productUnitAmount;
+
+	/**
+	 * Product's unit type
+	 */
+	private String productUnitType;
 
 	/**
 	 * Default constructor
@@ -116,7 +126,7 @@ public class FinancialAdminProductWebRequestBean extends BaseFinancialsBean impl
 
 		try {
 			// Call bean
-			updatedProduct = this.productRemoteBean.addGenericProduct(product);
+			updatedProduct = this.adminProductBean.addGenericProduct(product);
 		} catch (final ProductAlreadyAddedException ex) {
 			// Continue to throw
 			throw new FaceletException(ex);
@@ -130,18 +140,18 @@ public class FinancialAdminProductWebRequestBean extends BaseFinancialsBean impl
 	}
 
 	/**
-	 * Getter for product's available property
+	 * Getter for product's available
 	 * <p>
-	 * @return Product's available property
+	 * @return Product's available
 	 */
 	public Boolean getProductAvailability () {
 		return this.productAvailability;
 	}
 
 	/**
-	 * Setter for product's available property
+	 * Setter for product's available
 	 * <p>
-	 * @param productAvailability Product's available property
+	 * @param productAvailability Product's available
 	 */
 	public void setProductAvailability (final Boolean productAvailability) {
 		this.productAvailability = productAvailability;
@@ -238,21 +248,57 @@ public class FinancialAdminProductWebRequestBean extends BaseFinancialsBean impl
 	}
 
 	/**
-	 * Getter for product's title property
+	 * Getter for product's i18n key
+	 * <p>
+	 * @return Product's i18n key
+	 */
+	public String getProductI18nKey () {
+		return this.productI18nKey;
+	}
+
+	/**
+	 * Setter for product's i18n key
+	 * <p>
+	 * @param productI18nKey Product's i18n key
+	 */
+	public void setProductI18nKey (final String productI18nKey) {
+		this.productI18nKey = productI18nKey;
+	}
+
+	/**
+	 * Getter for product's unit amount
+	 * <p>
+	 * @return Product's unit amount
+	 */
+	public Float getProductUnitAmount () {
+		return this.productUnitAmount;
+	}
+
+	/**
+	 * Setter for product's unit amount
+	 * <p>
+	 * @param productUnitAmount Product's unit amount
+	 */
+	public void setProductUnitAmount (final Float productUnitAmount) {
+		this.productUnitAmount = productUnitAmount;
+	}
+
+	/**
+	 * Getter for product's unit type
 	 * <p>
-	 * @return Product's title
+	 * @return Product's unit type
 	 */
-	public String getProductTitle () {
-		return this.productTitle;
+	public String getProductUnitType () {
+		return this.productUnitType;
 	}
 
 	/**
-	 * Setter for product's title property
+	 * Setter for product's unit type
 	 * <p>
-	 * @param productTitle Product's title
+	 * @param productUnitType Product's unit type
 	 */
-	public void setProductTitle (final String productTitle) {
-		this.productTitle = productTitle;
+	public void setProductUnitType (final String productUnitType) {
+		this.productUnitType = productUnitType;
 	}
 
 	/**
@@ -264,7 +310,9 @@ public class FinancialAdminProductWebRequestBean extends BaseFinancialsBean impl
 		this.setProductNetPrice(null);
 		this.setProductTaxRate(null);
 		this.setProductGrossPrice(null);
-		this.setProductTitle(null);
+		this.setProductI18nKey(null);
+		this.setProductUnitAmount(null);
+		this.setProductUnitType(null);
 	}
 
 	/**
@@ -274,11 +322,13 @@ public class FinancialAdminProductWebRequestBean extends BaseFinancialsBean impl
 	 */
 	private Product createProductInstance () {
 		// Create product instance
-		final Product product = new GenericProduct(this.getProductTitle(), this.getProductGrossPrice(), this.getProductCurrencyCode(), this.getProductCategory(), this.getProductAvailability());
+		final Product product = new GenericProduct(this.getProductI18nKey(), this.getProductGrossPrice(), this.getProductCurrencyCode(), this.getProductCategory(), this.getProductAvailability());
 
 		// Set all optional fields
 		product.setProductNetPrice(this.getProductNetPrice());
 		product.setProductTaxRate(this.getProductTaxRate());
+		product.setProductUnitAmount(this.getProductUnitAmount());
+		product.setProductUnitType(this.getProductUnitType());
 
 		// Return it
 		return product;
diff --git a/src/java/org/mxchange/jfinancials/beans/product/FinancialProductWebRequestBean.java b/src/java/org/mxchange/jfinancials/beans/product/FinancialProductWebRequestBean.java
index 88da637e..635cbe82 100644
--- a/src/java/org/mxchange/jfinancials/beans/product/FinancialProductWebRequestBean.java
+++ b/src/java/org/mxchange/jfinancials/beans/product/FinancialProductWebRequestBean.java
@@ -51,10 +51,15 @@ public class FinancialProductWebRequestBean extends BaseFinancialsBean implement
 	private static final long serialVersionUID = 58_137_539_530_279L;
 
 	/**
-	 * List for all available products
+	 * List for all products
 	 */
 	private List<Product> allProducts;
 
+	/**
+	 * List for filtered products
+	 */
+	private List<Product> filteredProducts;
+
 	/**
 	 * EJB for general product purposes
 	 */
@@ -137,6 +142,26 @@ public class FinancialProductWebRequestBean extends BaseFinancialsBean implement
 		return product;
 	}
 
+	/**
+	 * Getter for filtered product list
+	 * <p>
+	 * @return Filtered product list
+	 */
+	@SuppressWarnings ("ReturnOfCollectionOrArrayField")
+	public List<Product> getFilteredProducts () {
+		return this.filteredProducts;
+	}
+
+	/**
+	 * Setter for filtered product list
+	 * <p>
+	 * @param filteredProducts Filtered product list
+	 */
+	@SuppressWarnings ("AssignmentToCollectionOrArrayFieldFromParameter")
+	public void setFilteredProducts (final List<Product> filteredProducts) {
+		this.filteredProducts = filteredProducts;
+	}
+
 	/**
 	 * Initialization of this bean
 	 */
diff --git a/src/java/org/mxchange/jfinancials/converter/generic_product/FinancialsGenericProductConverter.java b/src/java/org/mxchange/jfinancials/converter/generic_product/FinancialsGenericProductConverter.java
index 7d03212d..5af8d5fd 100644
--- a/src/java/org/mxchange/jfinancials/converter/generic_product/FinancialsGenericProductConverter.java
+++ b/src/java/org/mxchange/jfinancials/converter/generic_product/FinancialsGenericProductConverter.java
@@ -16,15 +16,13 @@
  */
 package org.mxchange.jfinancials.converter.generic_product;
 
-import javax.faces.application.FacesMessage;
+import javax.enterprise.inject.spi.CDI;
 import javax.faces.component.UIComponent;
 import javax.faces.context.FacesContext;
 import javax.faces.convert.Converter;
 import javax.faces.convert.ConverterException;
 import javax.faces.convert.FacesConverter;
-import javax.naming.Context;
-import javax.naming.InitialContext;
-import javax.naming.NamingException;
+import org.mxchange.jfinancials.beans.product.FinancialProductWebRequestBean;
 import org.mxchange.jfinancials.beans.product.FinancialProductWebRequestController;
 import org.mxchange.jproduct.exceptions.product.ProductNotFoundException;
 import org.mxchange.jproduct.model.product.Product;
@@ -46,16 +44,8 @@ public class FinancialsGenericProductConverter implements Converter<Product> {
 	public Product getAsObject (final FacesContext context, final UIComponent component, final String submittedValue) {
 		// Is the instance there?
 		if (PRODUCT_CONTROLLER == null) {
-			try {
-				// Not yet, attempt lookup
-				final Context initial = new InitialContext();
-
-				// Lookup EJB
-				PRODUCT_CONTROLLER = (FinancialProductWebRequestController) initial.lookup("java:global/jfinancials-ejb/productController!org.mxchange.jfinancials.beans.product.FinancialProductWebRequestController");
-			} catch (final NamingException ex) {
-				// Throw it again
-				throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Cannot lookup backing bean", ex.getMessage()), ex);
-			}
+			// Get bean from CDI directly
+			PRODUCT_CONTROLLER = CDI.current().select(FinancialProductWebRequestBean.class).get();
 		}
 
 		// Is the value null or empty?
diff --git a/src/java/org/mxchange/jfinancials/converter/product_category/FinancialsProductCategoryConverter.java b/src/java/org/mxchange/jfinancials/converter/product_category/FinancialsProductCategoryConverter.java
index a1753d0b..983e8b3f 100644
--- a/src/java/org/mxchange/jfinancials/converter/product_category/FinancialsProductCategoryConverter.java
+++ b/src/java/org/mxchange/jfinancials/converter/product_category/FinancialsProductCategoryConverter.java
@@ -16,15 +16,12 @@
  */
 package org.mxchange.jfinancials.converter.product_category;
 
-import javax.faces.application.FacesMessage;
+import javax.enterprise.inject.spi.CDI;
 import javax.faces.component.UIComponent;
 import javax.faces.context.FacesContext;
 import javax.faces.convert.Converter;
 import javax.faces.convert.ConverterException;
 import javax.faces.convert.FacesConverter;
-import javax.naming.Context;
-import javax.naming.InitialContext;
-import javax.naming.NamingException;
 import org.mxchange.jfinancials.beans.category.FinancialCategoryWebRequestBean;
 import org.mxchange.jfinancials.beans.category.FinancialCategoryWebRequestController;
 import org.mxchange.jproduct.exceptions.category.CategoryNotFoundException;
@@ -47,16 +44,8 @@ public class FinancialsProductCategoryConverter implements Converter<Category> {
 	public Category getAsObject (final FacesContext context, final UIComponent component, final String submittedValue) {
 		// Is the instance there?
 		if (CATEGORY_CONTROLLER == null) {
-			try {
-				// Not yet, attempt lookup
-				final Context initial = new InitialContext();
-
-				// Lookup EJB
-				CATEGORY_CONTROLLER = (FinancialCategoryWebRequestController) initial.lookup(String.format("java:module/%s", FinancialCategoryWebRequestBean.class.getSimpleName()));
-			} catch (final NamingException ex) {
-				// Throw it again
-				throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "Cannot lookup backing bean", ex.getMessage()), ex);
-			}
+			// Get bean from CDI directly
+			CATEGORY_CONTROLLER = CDI.current().select(FinancialCategoryWebRequestBean.class).get();
 		}
 
 		// Is the value null or empty?
diff --git a/web/WEB-INF/faces-config.xml b/web/WEB-INF/faces-config.xml
index cab11327..b6471e27 100644
--- a/web/WEB-INF/faces-config.xml
+++ b/web/WEB-INF/faces-config.xml
@@ -128,6 +128,14 @@
 			<from-outcome>admin_list_company_employee</from-outcome>
 			<to-view-id>/admin/employee/admin_employee_list.xhtml</to-view-id>
 		</navigation-case>
+		<navigation-case>
+			<from-outcome>admin_list_category</from-outcome>
+			<to-view-id>/admin/category/admin_product_category_list.xhtml</to-view-id>
+		</navigation-case>
+		<navigation-case>
+			<from-outcome>admin_list_product</from-outcome>
+			<to-view-id>/admin/product/admin_generic_product_list.xhtml</to-view-id>
+		</navigation-case>
 		<navigation-case>
 			<from-outcome>admin_list_user</from-outcome>
 			<to-view-id>/admin/user/admin_user_list.xhtml</to-view-id>
@@ -873,6 +881,40 @@
 			<to-view-id>/admin/opening_time/admin_opening_time_delete.xhtml</to-view-id>
 		</navigation-case>
 	</navigation-rule>
+	<navigation-rule>
+		<from-view-id>/admin/category/admin_product_category_list.xhtml</from-view-id>
+		<navigation-case>
+			<from-outcome>admin_show_product_category</from-outcome>
+			<to-view-id>/admin/category/admin_product_category_show.xhtml</to-view-id>
+		</navigation-case>
+		<navigation-case>
+			<from-outcome>admin_edit_product_category</from-outcome>
+			<to-view-id>/admin/category/admin_product_category_edit.xhtml</to-view-id>
+		</navigation-case>
+		<navigation-case>
+			<from-outcome>admin_delete_product_category</from-outcome>
+			<to-view-id>/admin/category/admin_product_category_delete.xhtml</to-view-id>
+		</navigation-case>
+		<navigation-case>
+			<from-outcome>admin_assign_parent_category</from-outcome>
+			<to-view-id>/admin/category/admin_product_category_assign_parent.xhtml</to-view-id>
+		</navigation-case>
+	</navigation-rule>
+	<navigation-rule>
+		<from-view-id>/admin/product/admin_generic_product_list.xhtml</from-view-id>
+		<navigation-case>
+			<from-outcome>admin_show_generic_product</from-outcome>
+			<to-view-id>/admin/product/admin_generic_product_show.xhtml</to-view-id>
+		</navigation-case>
+		<navigation-case>
+			<from-outcome>admin_edit_generic_product</from-outcome>
+			<to-view-id>/admin/product/admin_generic_product_edit.xhtml</to-view-id>
+		</navigation-case>
+		<navigation-case>
+			<from-outcome>admin_delete_generic_product</from-outcome>
+			<to-view-id>/admin/product/admin_generic_product_delete.xhtml</to-view-id>
+		</navigation-case>
+	</navigation-rule>
 	<!--
 	<factory>
 		<exception-handler-factory>
diff --git a/web/WEB-INF/product-links.jsf.taglib.xml b/web/WEB-INF/product-links.jsf.taglib.xml
new file mode 100644
index 00000000..637991ce
--- /dev/null
+++ b/web/WEB-INF/product-links.jsf.taglib.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2017 Roland Häder
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+-->
+<facelet-taglib version="2.2"
+				xmlns="http://xmlns.jcp.org/xml/ns/javaee"
+				xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+				xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facelettaglibrary_2_2.xsd">
+	<namespace>http://mxchange.org/jsf/jproduct/links</namespace>
+	<tag>
+		<tag-name>outputProductCategoryAdminMiniLinks</tag-name>
+		<description>This tag renders administrative "mini-links" for given category instance.</description>
+		<source>resources/tags/admin/links/mini/category/admin_product_category_links.tpl</source>
+		<attribute>
+			<name>category</name>
+			<description>The product category instance that provides the data for this tag.</description>
+			<required>true</required>
+			<type>org.mxchange.jproduct.model.category.Category</type>
+		</attribute>
+		<attribute>
+			<name>renderShowLink</name>
+			<description>Whether to render (default: true) "show product" link.</description>
+			<required>false</required>
+			<type>java.langBoolean</type>
+		</attribute>
+		<attribute>
+			<name>rendered</name>
+			<description>Whether this tag is being rendered by JSF engine (default: true).</description>
+			<required>false</required>
+			<type>java.lang.Boolean</type>
+		</attribute>
+	</tag>
+	<tag>
+		<tag-name>outputGenericProductAdminMiniLinks</tag-name>
+		<description>This tag renders administrative "mini-links" for given product instance.</description>
+		<source>resources/tags/admin/links/mini/product/admin_generic_product_links.tpl</source>
+		<attribute>
+			<name>product</name>
+			<description>The generic product instance that provides the data for this tag.</description>
+			<required>true</required>
+			<type>org.mxchange.jproduct.model.product.Product</type>
+		</attribute>
+		<attribute>
+			<name>renderShowLink</name>
+			<description>Whether to render (default: true) "show product" link.</description>
+			<required>false</required>
+			<type>java.langBoolean</type>
+		</attribute>
+		<attribute>
+			<name>rendered</name>
+			<description>Whether this tag is being rendered by JSF engine (default: true).</description>
+			<required>false</required>
+			<type>java.lang.Boolean</type>
+		</attribute>
+	</tag>
+</facelet-taglib>
diff --git a/web/WEB-INF/resources/tags/admin/links/mini/category/admin_product_category_links.tpl b/web/WEB-INF/resources/tags/admin/links/mini/category/admin_product_category_links.tpl
new file mode 100644
index 00000000..b988c21c
--- /dev/null
+++ b/web/WEB-INF/resources/tags/admin/links/mini/category/admin_product_category_links.tpl
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<ui:composition
+	xmlns="http://www.w3.org/1999/xhtml"
+	xmlns:f="http://java.sun.com/jsf/core"
+	xmlns:h="http://java.sun.com/jsf/html"
+	xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
+	xmlns:p="http://primefaces.org/ui">
+
+	<ui:fragment rendered="#{empty rendered or rendered}">
+		<ul class="navbar-mini">
+			<ui:fragment rendered="#{empty renderShowLink or renderShowLink}">
+				<li class="navlink-mini">
+					<p:link outcome="admin_show_product_category" value="#{msg.ADMIN_LINK_SHOW_SHORT}" title="#{msg.ADMIN_LINK_SHOW_PRODUCT_CATEGORY_TITLE}">
+						<f:param name="categoryId" value="#{category.categoryId}" />
+					</p:link>
+				</li>
+			</ui:fragment>
+
+			<li class="navlink-mini">
+				<p:link outcome="admin_edit_product_category" value="#{msg.ADMIN_LINK_EDIT_SHORT}" title="#{msg.ADMIN_LINK_EDIT_PRODUCT_CATEGORY_TITLE}">
+					<f:param name="categoryId" value="#{category.categoryId}" />
+				</p:link>
+			</li>
+
+			<li class="navlink-mini">
+				<p:link outcome="admin_delete_product_category">
+					<h:outputText styleClass="link-danger" value="#{msg.ADMIN_LINK_DELETE_SHORT}" title="#{msg.ADMIN_LINK_DELETE_PRODUCT_CATEGORY_TITLE}" />
+					<f:param name="categoryId" value="#{category.categoryId}" />
+				</p:link>
+			</li>
+		</ul>
+	</ui:fragment>
+</ui:composition>
diff --git a/web/WEB-INF/resources/tags/admin/links/mini/product/admin_generic_product_links.tpl b/web/WEB-INF/resources/tags/admin/links/mini/product/admin_generic_product_links.tpl
new file mode 100644
index 00000000..8d748ae2
--- /dev/null
+++ b/web/WEB-INF/resources/tags/admin/links/mini/product/admin_generic_product_links.tpl
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<ui:composition
+	xmlns="http://www.w3.org/1999/xhtml"
+	xmlns:f="http://java.sun.com/jsf/core"
+	xmlns:h="http://java.sun.com/jsf/html"
+	xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
+	xmlns:p="http://primefaces.org/ui">
+
+	<ui:fragment rendered="#{empty rendered or rendered}">
+		<ul class="navbar-mini">
+			<ui:fragment rendered="#{empty renderShowLink or renderShowLink}">
+				<li class="navlink-mini">
+					<p:link outcome="admin_show_product" value="#{msg.ADMIN_LINK_SHOW_SHORT}" title="#{msg.ADMIN_LINK_SHOW_GENERIC_PRODUCT_TITLE}">
+						<f:param name="productId" value="#{product.productId}" />
+					</p:link>
+				</li>
+			</ui:fragment>
+
+			<li class="navlink-mini">
+				<p:link outcome="admin_edit_product" value="#{msg.ADMIN_LINK_EDIT_SHORT}" title="#{msg.ADMIN_LINK_EDIT_GENERIC_PRODUCT_TITLE}">
+					<f:param name="productId" value="#{product.productId}" />
+				</p:link>
+			</li>
+
+			<li class="navlink-mini">
+				<p:link outcome="admin_delete_product">
+					<h:outputText styleClass="link-danger" value="#{msg.ADMIN_LINK_DELETE_SHORT}" title="#{msg.ADMIN_LINK_DELETE_GENERIC_PRODUCT_TITLE}" />
+					<f:param name="productId" value="#{product.productId}" />
+				</p:link>
+			</li>
+		</ul>
+	</ui:fragment>
+</ui:composition>
diff --git a/web/WEB-INF/templates/admin/category/admin_form_category_data.tpl b/web/WEB-INF/templates/admin/category/admin_form_category_data.tpl
new file mode 100644
index 00000000..c4d265cd
--- /dev/null
+++ b/web/WEB-INF/templates/admin/category/admin_form_category_data.tpl
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+@TODO: title="#{project.ADMIN_PRODUCT_CATEGORY_DATA_LEGEND_TITLE}"
+-->
+<ui:composition
+	xmlns="http://www.w3.org/1999/xhtml"
+	xmlns:f="http://xmlns.jcp.org/jsf/core"
+	xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
+	xmlns:p="http://primefaces.org/ui">
+
+	<p:fieldset legend="#{project.ADMIN_PRODUCT_CATEGORY_DATA_LEGEND}">
+		<p:panelGrid layout="grid" columns="2" columnClasses="ui-grid-col-4,ui-grid-col-8" styleClass="table table-full ui-noborder">
+			<p:outputLabel for="parentCategory" value="#{project.ADMIN_ASSIGN_PARENT_CATEGORY}" />
+			<p:selectOneMenu
+				id="parentCategory"
+				value="#{adminCategoryController.parentCategory}"
+				filter="true"
+				filterMatchMode="contains"
+				title="#{project.ADMIN_ASSIGN_PARENT_CATEGORY_TITLE}"
+				>
+				<f:converter converterId="ProductCategoryConverter" />
+				<f:selectItem itemValue="#{null}" itemLabel="#{msg.NONE_SELECTED}" />
+				<f:selectItems value="#{categoryController.allCategories()}" var="category" itemValue="#{category}" itemLabel="#{beanHelper.renderProductCategory(category)}" />
+			</p:selectOneMenu>
+
+			<p:outputLabel for="categoryI18nKey" value="#{project.ADMIN_ENTER_CATEGORY_I18N_KEY}" />
+			<p:inputText
+				id="categoryI18nKey"
+				value="#{adminCategoryController.categoryI18nKey}"
+				size="10"
+				maxlength="255"
+				required="true"
+				requiredMessage="#{project.ADMIN_CATEGORY_I18N_KEY_REQUIRED}"
+				title="#{project.ADMIN_ENTER_CATEGORY_I18N_KEY_TITLE}"
+				/>
+
+			<p:outputLabel for="categoryShownInStatistics" value="#{project.ADMIN_ENABLE_CATEGORY_IN_STATISTICS}" />
+			<p:selectBooleanCheckbox
+				id="categoryShownInStatistics"
+				value="#{adminCategoryController.categoryShownInStatistics}"
+				required="true"
+				requiredMessage="#{project.ADMIN_ENABLE_CATEGORY_IN_STATISTICS_REQUIRED}"
+				title="#{project.ADMIN_ENABLE_CATEGORY_IN_STATISTICS_TITLE}"
+				/>
+		</p:panelGrid>
+	</p:fieldset>
+</ui:composition>
diff --git a/web/WEB-INF/templates/admin/menu/project.tpl b/web/WEB-INF/templates/admin/menu/project.tpl
index a5f68a54..887a46b7 100644
--- a/web/WEB-INF/templates/admin/menu/project.tpl
+++ b/web/WEB-INF/templates/admin/menu/project.tpl
@@ -1,8 +1,6 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <ui:composition
 	xmlns="http://www.w3.org/1999/xhtml"
-	xmlns:f="http://xmlns.jcp.org/jsf/core"
-	xmlns:h="http://xmlns.jcp.org/jsf/html"
 	xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
 	xmlns:p="http://primefaces.org/ui">
 
@@ -10,4 +8,9 @@
 		<p:menuitem title="#{project.ADMIN_LINK_LIST_RECEIPTS_TITLE}" outcome="admin_list_receipts" value="#{project.ADMIN_LINK_LIST_RECEIPTS}" />
 		<p:menuitem title="#{project.ADMIN_LINK_LIST_RECEIPT_ITEMS_TITLE}" outcome="admin_list_receipt_items" value="#{project.ADMIN_LINK_LIST_RECEIPT_ITEMS}" />
 	</p:submenu>
+
+	<p:submenu label="#{project.ADMIN_MENU_PRODUCTS_CATEGORIES_TITLE}">
+		<p:menuitem title="#{project.ADMIN_LINK_LIST_GENERIC_PRODUCTS_TITLE}" outcome="admin_list_product" value="#{project.ADMIN_LINK_LIST_GENERIC_PRODUCTS}" />
+		<p:menuitem title="#{project.ADMIN_LINK_LIST_CATEGORIES_TITLE}" outcome="admin_list_category" value="#{project.ADMIN_LINK_LIST_CATEGORIES}" />
+	</p:submenu>
 </ui:composition>
diff --git a/web/WEB-INF/templates/admin/product/admin_form_product_data.tpl b/web/WEB-INF/templates/admin/product/admin_form_product_data.tpl
new file mode 100644
index 00000000..2587060e
--- /dev/null
+++ b/web/WEB-INF/templates/admin/product/admin_form_product_data.tpl
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+@TODO: title="#{project.ADMIN_GENERIC_PRODUCT_DATA_LEGEND_TITLE}"
+-->
+<ui:composition
+	xmlns="http://www.w3.org/1999/xhtml"
+	xmlns:product="http://mxchange.org/jsf/jproduct/widgets"
+	xmlns:f="http://xmlns.jcp.org/jsf/core"
+	xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
+	xmlns:p="http://primefaces.org/ui">
+
+	<p:fieldset legend="#{project.ADMIN_GENERIC_PRODUCT_DATA_LEGEND}">
+		<p:panelGrid layout="grid" columns="2" columnClasses="ui-grid-col-4,ui-grid-col-8" styleClass="table table-full ui-noborder">
+			<p:outputLabel for="productCategory" value="#{project.ADMIN_ASSIGN_PRODUCT_CATEGORY}" />
+			<p:selectOneMenu
+				id="productCategory"
+				value="#{adminProductController.productCategory}"
+				filter="true"
+				filterMatchMode="contains"
+				required="true"
+				requiredMessage="#{project.ADMIN_PRODUCT_CATEGORY_REQUIRED}"
+				title="#{project.ADMIN_ASSIGN_PRODUCT_CATEGORY_TITLE}"
+				>
+				<f:converter converterId="ProductCategoryConverter" />
+				<f:selectItem itemValue="#{null}" itemLabel="#{msg.PLEASE_SELECT}" noSelectionOption="true" itemDisabled="true" />
+				<f:selectItems value="#{categoryController.allCategories()}" var="category" itemValue="#{category}" itemLabel="#{beanHelper.renderProductCategory(category)}" />
+			</p:selectOneMenu>
+
+			<p:outputLabel for="productI18nKey" value="#{project.ADMIN_ENTER_PRODUCT_I18N_KEY}" />
+			<p:inputText
+				id="productI18nKey"
+				value="#{adminProductController.productI18nKey}"
+				size="10"
+				maxlength="255"
+				required="true"
+				requiredMessage="#{project.ADMIN_PRODUCT_I18N_KEY_REQUIRED}"
+				title="#{project.ADMIN_ENTER_PRODUCT_I18N_KEY_TITLE}"
+				/>
+
+			<p:outputLabel for="productAvailability" value="#{project.ADMIN_ENABLE_PRODUCT_AVAILABILITY}" />
+			<p:selectBooleanCheckbox
+				id="productAvailability"
+				value="#{adminProductController.productAvailability}"
+				required="true"
+				requiredMessage="#{project.ADMIN_GENERIC_PROJECT_AVAILABILITY_REQUIRED}"
+				title="#{project.ADMIN_ENABLE_PRODUCT_AVAILABILITY_TITLE}"
+				/>
+
+			<p:outputLabel value="#{project.ADMIN_ENTER_PRODUCT_PRICE}" />
+			<product:inputProductPricePanelGrid targetController="#{adminProductController}" />
+
+			<p:outputLabel for="productCurrencyCode" value="#{project.ADMIN_ENTER_PRODUCT_CURRENCY_CODE}" />
+			<p:inputText
+				id="productCurrencyCode"
+				value="#{adminProductController.productCurrencyCode}"
+				size="3"
+				maxlength="3"
+				title="#{project.ADMIN_ENTER_PRODUCT_CURRENCY_CODE_TITLE}"
+				/>
+
+			<p:outputLabel for="productUnitAmount" value="#{project.ADMIN_ENTER_PRODUCT_UNIT_AMOUNT}" />
+			<p:inputNumber
+				id="productUnitAmount"
+				value="#{adminProductController.productUnitAmount}"
+				decimalSeparator=","
+				thousandSeparator="."
+				title="#{project.ADMIN_ENTER_PRODUCT_UNIT_AMOUNT_TITLE}"
+				/>
+
+			<p:outputLabel for="productUnitType" value="#{project.ADMIN_ENTER_PRODUCT_UNIT_TYPE}" />
+			<p:inputText
+				id="productUnitType"
+				value="#{adminProductController.productUnitType}"
+				size="10"
+				maxlength="255"
+				title="#{project.ADMIN_ENTER_PRODUCT_UNIT_TYPE_TITLE}"
+				/>
+		</p:panelGrid>
+	</p:fieldset>
+</ui:composition>
diff --git a/web/WEB-INF/web.xml b/web/WEB-INF/web.xml
index dbce861c..3e85fbc2 100644
--- a/web/WEB-INF/web.xml
+++ b/web/WEB-INF/web.xml
@@ -30,7 +30,7 @@
     <context-param>
         <description>Generic custom JSF tags library</description>
         <param-name>javax.faces.FACELETS_LIBRARIES</param-name>
-        <param-value>/WEB-INF/widgets.jsf.taglib.xml;/WEB-INF/links.jsf.taglib.xml;/WEB-INF/project-links.jsf.taglib.xml;/WEB-INF/product.jsf.taglib.xml</param-value>
+        <param-value>/WEB-INF/widgets.jsf.taglib.xml;/WEB-INF/links.jsf.taglib.xml;/WEB-INF/project-links.jsf.taglib.xml;/WEB-INF/product.jsf.taglib.xml;/WEB-INF/product-links.jsf.taglib.xml</param-value>
     </context-param>
     <context-param>
         <description>Project stage</description>
diff --git a/web/admin/category/admin_product_category_list.xhtml b/web/admin/category/admin_product_category_list.xhtml
new file mode 100644
index 00000000..1796183a
--- /dev/null
+++ b/web/admin/category/admin_product_category_list.xhtml
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<ui:composition template="/WEB-INF/templates/admin/admin_base.tpl"
+				xmlns="http://www.w3.org/1999/xhtml"
+				xmlns:product-links="http://mxchange.org/jsf/jproduct/links"
+				xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
+				xmlns:h="http://xmlns.jcp.org/jsf/html"
+				xmlns:f="http://xmlns.jcp.org/jsf/core"
+				xmlns:p="http://primefaces.org/ui">
+
+	<ui:define name="document_admin_title">
+		<h:outputText value="#{project.PAGE_TITLE_ADMIN_LIST_PRODUCT_CATEGORY}" />
+	</ui:define>
+
+	<ui:define name="content_header">
+		<h:outputText value="#{project.CONTENT_TITLE_ADMIN_LIST_PRODUCT_CATEGORY}" />
+	</ui:define>
+
+	<ui:define name="content">
+		<h:form id="form-list-categories">
+			<p:dataTable
+				id="table-list-categories"
+				var="category"
+				value="#{categoryController.allCategories()}"
+				tableStyleClass="table table-full"
+				paginator="true"
+				paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
+				filteredValue="#{categoryController.filteredCategories}"
+				rows="10"
+				reflow="true"
+				resizableColumns="true"
+				rowsPerPageTemplate="5,10,20,50,100"
+				sortMode="multiple"
+				summary="#{project.TABLE_SUMMARY_ADMIN_LIST_PRODUCT_CATEGORIES}"
+				emptyMessage="#{project.ADMIN_EMPTY_LIST_PRODUCT_CATEGORIES}"
+				widgetVar="categoryList"
+				>
+
+				<f:facet name="header">
+					<p:panelGrid columns="2" columnClasses="ui-grid-col-10,ui-grid-col-2" layout="grid" styleClass="ui-noborder ui-transparent-widget">
+						<h:outputText value="#{project.ADMIN_LIST_PRODUCT_CATEGORIES_HEADER}" />
+
+						<h:panelGroup>
+							<p:commandButton id="toggler" type="button" value="#{msg.SELECT_SHOWN_COLUMNS}" styleClass="column-selector" />
+							<p:columnToggler datasource="table-list-categories" trigger="toggler" />
+						</h:panelGroup>
+					</p:panelGrid>
+				</f:facet>
+
+				<p:column headerText="#{msg.ADMIN_HEADER_ID_NUMBER}" sortBy="#{category.categoryId}" filterable="false">
+					<p:link outcome="admin_show_product_category" title="#{project.ADMIN_LINK_SHOW_PRODUCT_CATEGORY_TITLE}" value="#{category.categoryId}">
+						<f:param name="categoryId" value="#{category.categoryId}" />
+					</p:link>
+				</p:column>
+
+				<p:column headerText="#{msg.ADMIN_HEADER_I18N_KEY}" sortBy="#{category.categoryI18nKey}" filterBy="#{category.categoryI18nKey}" filterMatchMode="contains">
+					<h:outputText value="#{local[category.categoryI18nKey]}" />
+				</p:column>
+
+				<p:column headerText="#{project.ADMIN_HEADER_ASSIGNED_PARENT_CATEGORY}" sortBy="#{category.parentCategory.categoryI18nKey}" filterBy="#{category.parentCategory}" filterMatchMode="in">
+					<f:facet name="filter">
+						<p:selectCheckboxMenu
+							filter="true"
+							filterMatchMode="contains"
+							label="#{project.LABEL_PRODUCT_CATEGORIES}"
+							onchange="PF('categoryList').filter()"
+							updateLabel="true"
+							title="#{project.FILTER_BY_MULTIPLE_PRODUCT_CATEGORIES_TITLE}"
+							>
+							<f:converter converterId="ProductCategoryConverter" />
+							<f:selectItem itemValue="#{null}" itemLabel="#{msg.NONE_SELECTED}" />
+							<f:selectItems value="#{categoryController.allCategories()}" var="category" itemValue="#{category}" itemLabel="#{beanHelper.renderProductCategory(category)}" />
+						</p:selectCheckboxMenu>
+					</f:facet>
+
+					<p:link outcome="admin_show_product_category" title="#{project.ADMIN_LINK_SHOW_PRODUCT_CATEGORY_TITLE}" value="#{category.parentCategory.categoryId}" rendered="#{not empty category.parentCategory}">
+						<f:param name="categoryId" value="#{category.parentCategory.categoryId}" />
+					</p:link>
+
+					<p:link outcome="admin_assign_parent_category" title="#{project.ADMIN_LINK_ASSIGN_PARENT_CATEGORY_TITLE}" value="#{msg.ADMIN_NOT_ASSIGNED}" rendered="#{empty category.parentCategory}">
+						<f:param name="categoryId" value="#{category.categoryId}" />
+					</p:link>
+				</p:column>
+
+				<p:column headerText="#{msg.ADMIN_HEADER_ENTRY_CREATED}" sortBy="#{category.categoryCreated}" filterable="false">
+					<h:outputText id="categoryCreated" value="#{category.categoryCreated.time}">
+						<f:convertDateTime for="categoryCreated" type="both" timeStyle="short" dateStyle="short" />
+					</h:outputText>
+				</p:column>
+
+				<p:column headerText="#{msg.ADMIN_HEADER_ACTION_LINKS}" sortable="false" filterable="false">
+					<product-links:outputProductCategoryAdminMiniLinks category="#{category}" />
+				</p:column>
+			</p:dataTable>
+		</h:form>
+
+		<h:form>
+			<p:panelGrid columns="1" styleClass="table table-full" layout="grid">
+				<f:facet name="header">
+					<h:outputText value="#{project.ADMIN_ADD_PRODUCT_CATEGORY_TITLE}" />
+				</f:facet>
+
+				<h:panelGroup styleClass="para" layout="block">
+					<h:outputText value="#{project.ADMIN_ADD_PRODUCT_CATEGORY_MINIMUM_DATA}" />
+				</h:panelGroup>
+
+				<ui:include src="/WEB-INF/templates/admin/category/admin_form_category_data.tpl" />
+
+				<f:facet name="footer">
+					<p:panelGrid columns="2" layout="grid">
+						<p:commandButton
+							styleClass="reset"
+							type="reset"
+							value="#{msg.BUTTON_RESET_FORM}"
+							/>
+
+						<p:commandButton
+							styleClass="submit"
+							type="submit"
+							value="#{project.BUTTON_ADMIN_ADD_PRODUCT_CATEGORY}"
+							action="#{adminCategoryController.addCategory()}"
+							update=":master:form-list-categories:table-list-categories"
+							/>
+					</p:panelGrid>
+				</f:facet>
+			</p:panelGrid>
+		</h:form>
+	</ui:define>
+</ui:composition>
diff --git a/web/admin/product/admin_generic_product_list.xhtml b/web/admin/product/admin_generic_product_list.xhtml
new file mode 100644
index 00000000..bcaa8190
--- /dev/null
+++ b/web/admin/product/admin_generic_product_list.xhtml
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<ui:composition template="/WEB-INF/templates/admin/admin_base.tpl"
+				xmlns="http://www.w3.org/1999/xhtml"
+				xmlns:product-links="http://mxchange.org/jsf/jproduct/links"
+				xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
+				xmlns:h="http://xmlns.jcp.org/jsf/html"
+				xmlns:f="http://xmlns.jcp.org/jsf/core"
+				xmlns:p="http://primefaces.org/ui">
+
+	<ui:define name="document_admin_title">
+		<h:outputText value="#{project.PAGE_TITLE_ADMIN_LIST_GENERIC_PRODUCT}" />
+	</ui:define>
+
+	<ui:define name="content_header">
+		<h:outputText value="#{project.CONTENT_TITLE_ADMIN_LIST_GENERIC_PRODUCT}" />
+	</ui:define>
+
+	<ui:define name="content">
+		<h:form id="form-list-products">
+			<p:dataTable
+				id="table-list-products"
+				var="product"
+				value="#{productController.allProducts()}"
+				tableStyleClass="table table-full"
+				paginator="true"
+				paginatorTemplate="{CurrentPageReport} {FirstPageLink} {PreviousPageLink} {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
+				filteredValue="#{productController.filteredProducts}"
+				rows="10"
+				reflow="true"
+				resizableColumns="true"
+				rowsPerPageTemplate="5,10,20,50,100"
+				sortMode="multiple"
+				summary="#{project.TABLE_SUMMARY_ADMIN_LIST_GENERIC_PRODUCTS}"
+				emptyMessage="#{project.ADMIN_EMPTY_LIST_GENERIC_PRODUCTS}"
+				widgetVar="productList"
+				>
+
+				<f:facet name="header">
+					<p:panelGrid columns="2" columnClasses="ui-grid-col-10,ui-grid-col-2" layout="grid" styleClass="ui-noborder ui-transparent-widget">
+						<h:outputText value="#{project.ADMIN_LIST_GENERIC_PRODUCTS_HEADER}" />
+
+						<h:panelGroup>
+							<p:commandButton id="toggler" type="button" value="#{msg.SELECT_SHOWN_COLUMNS}" styleClass="column-selector" />
+							<p:columnToggler datasource="table-list-products" trigger="toggler" />
+						</h:panelGroup>
+					</p:panelGrid>
+				</f:facet>
+
+				<p:column headerText="#{msg.ADMIN_HEADER_ID_NUMBER}" sortBy="#{product.productId}" filterable="false">
+					<p:link outcome="admin_show_generic_product" title="#{project.ADMIN_LINK_SHOW_GENERIC_PRODUCT_TITLE}" value="#{product.productId}">
+						<f:param name="productId" value="#{product.productId}" />
+					</p:link>
+				</p:column>
+
+				<p:column headerText="#{project.ADMIN_HEADER_I18N_KEY}" sortBy="#{product.productI18nKey}" filterBy="#{product.productI18nKey}" filterMatchMode="contains">
+					<h:outputText value="#{product.productI18nKey}" />
+				</p:column>
+
+				<p:column headerText="#{project.ADMIN_HEADER_ASSIGNED_PRODUCT_CATEGORY}" sortBy="#{product.productCategory.categoryI18nKey}" filterBy="#{product.productCategory}" filterMatchMode="in">
+					<f:facet name="filter">
+						<p:selectCheckboxMenu
+							filter="true"
+							filterMatchMode="contains"
+							label="#{project.LABEL_PRODUCT_CATEGORIES}"
+							onchange="PF('productList').filter()"
+							updateLabel="true"
+							title="#{project.FILTER_BY_MULTIPLE_PRODUCT_CATEGORIES_TITLE}"
+							>
+							<f:converter converterId="ProductCategoryConverter" />
+							<f:selectItem itemValue="#{null}" itemLabel="#{msg.NONE_SELECTED}" />
+							<f:selectItems value="#{categoryController.allCategories()}" var="category" itemValue="#{category}" itemLabel="#{beanHelper.renderProductCategory(category)}" />
+						</p:selectCheckboxMenu>
+					</f:facet>
+
+					<p:link outcome="admin_show_product_category" title="#{project.ADMIN_LINK_SHOW_GENERIC_PRODUCT_CATEGORY_TITLE}" value="#{product.productCategory.categoryId}">
+						<f:param name="categoryId" value="#{product.productCategory.categoryId}" />
+					</p:link>
+				</p:column>
+
+				<p:column headerText="#{project.ADMIN_HEADER_PRODUCT_GROSS_PRICE}" sortBy="#{product.productAvailability}" filterBy="#{product.productGrossPrice}" filterMatchMode="choose">
+					<h:outputText value="#{product.productGrossPrice}">
+						<!-- @TODO Hard-coded EUR again -->
+						<f:convertNumber type="currency" currencyCode="EUR" />
+					</h:outputText>
+				</p:column>
+
+				<p:column headerText="#{project.ADMIN_HEADER_PRODUCT_AVAILABILITY}" sortBy="#{product.productAvailability}" filterBy="#{product.productAvailability}" filterMatchMode="">
+					<h:outputText value="#{product.productAvailability ? msg.CHOICE_YES : msg.CHOICE_NO}" />
+				</p:column>
+
+				<p:column headerText="#{msg.ADMIN_HEADER_ENTRY_CREATED}" sortBy="#{product.productCreated}" filterable="false">
+					<h:outputText id="productCreated" value="#{product.productCreated.time}">
+						<f:convertDateTime for="productCreated" type="both" timeStyle="short" dateStyle="short" />
+					</h:outputText>
+				</p:column>
+
+				<p:column headerText="#{msg.ADMIN_HEADER_ACTION_LINKS}" sortable="false" filterable="false">
+					<product-links:outputGenericProductAdminMiniLinks product="#{product}" />
+				</p:column>
+			</p:dataTable>
+		</h:form>
+
+		<h:form>
+			<p:panelGrid columns="1" styleClass="table table-full" layout="grid">
+				<f:facet name="header">
+					<h:outputText value="#{project.ADMIN_ADD_GENERIC_PRODUCT_TITLE}" />
+				</f:facet>
+
+				<h:panelGroup styleClass="para" layout="block">
+					<h:outputText value="#{project.ADMIN_ADD_GENERIC_PRODUCT_MINIMUM_DATA}" />
+				</h:panelGroup>
+
+				<ui:include src="/WEB-INF/templates/admin/product/admin_form_product_data.tpl" />
+
+				<f:facet name="footer">
+					<p:panelGrid columns="2" layout="grid">
+						<p:commandButton
+							styleClass="reset"
+							type="reset"
+							value="#{msg.BUTTON_RESET_FORM}"
+							/>
+
+						<p:commandButton
+							styleClass="submit"
+							type="submit"
+							value="#{project.BUTTON_ADMIN_ADD_GENERIC_PRODUCT}"
+							action="#{adminProductController.addProduct()}"
+							update=":master:form-list-products:table-list-products"
+							/>
+					</p:panelGrid>
+				</f:facet>
+			</p:panelGrid>
+		</h:form>
+	</ui:define>
+</ui:composition>