Editable shopping categories
This commit is contained in:
parent
349fec17ac
commit
3fd7bdb842
121
recipes.sql
Normal file
121
recipes.sql
Normal file
|
@ -0,0 +1,121 @@
|
|||
CREATE TABLE keylookup (
|
||||
id INTEGER NOT NULL,
|
||||
word TEXT,
|
||||
item TEXT,
|
||||
ingkey TEXT,
|
||||
count INTEGER,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
CREATE TABLE info (
|
||||
version_super INTEGER,
|
||||
version_major INTEGER,
|
||||
version_minor INTEGER,
|
||||
last_access INTEGER,
|
||||
rowid INTEGER NOT NULL,
|
||||
PRIMARY KEY (rowid)
|
||||
);
|
||||
CREATE TABLE recipe (
|
||||
id INTEGER NOT NULL,
|
||||
title TEXT,
|
||||
instructions TEXT,
|
||||
modifications TEXT,
|
||||
cuisine TEXT,
|
||||
rating INTEGER,
|
||||
description TEXT,
|
||||
source TEXT,
|
||||
preptime INTEGER,
|
||||
cooktime INTEGER,
|
||||
servings FLOAT,
|
||||
yields FLOAT,
|
||||
yield_unit VARCHAR(32),
|
||||
image BLOB,
|
||||
thumb BLOB,
|
||||
deleted BOOLEAN,
|
||||
recipe_hash VARCHAR(32),
|
||||
ingredient_hash VARCHAR(32),
|
||||
link TEXT,
|
||||
last_modified INTEGER,
|
||||
PRIMARY KEY (id),
|
||||
CHECK (deleted IN (0, 1))
|
||||
);
|
||||
CREATE TABLE plugin_info (
|
||||
plugin TEXT,
|
||||
id INTEGER NOT NULL,
|
||||
version_super INTEGER,
|
||||
version_major INTEGER,
|
||||
version_minor INTEGER,
|
||||
plugin_version VARCHAR(32),
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
CREATE TABLE categories (
|
||||
id INTEGER NOT NULL,
|
||||
recipe_id INTEGER,
|
||||
category TEXT,
|
||||
PRIMARY KEY (id),
|
||||
FOREIGN KEY(recipe_id) REFERENCES recipe (id)
|
||||
);
|
||||
CREATE TABLE ingredients (
|
||||
id INTEGER NOT NULL,
|
||||
recipe_id INTEGER,
|
||||
refid INTEGER,
|
||||
unit TEXT,
|
||||
amount FLOAT,
|
||||
rangeamount FLOAT,
|
||||
item TEXT,
|
||||
ingkey TEXT,
|
||||
optional BOOLEAN,
|
||||
shopoptional INTEGER,
|
||||
inggroup TEXT,
|
||||
position INTEGER,
|
||||
deleted BOOLEAN,
|
||||
PRIMARY KEY (id),
|
||||
CHECK (deleted IN (0, 1)),
|
||||
FOREIGN KEY(recipe_id) REFERENCES recipe (id),
|
||||
FOREIGN KEY(refid) REFERENCES recipe (id),
|
||||
CHECK (optional IN (0, 1))
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "pantry" (
|
||||
id INTEGER NOT NULL,
|
||||
ingkey TEXT(32),
|
||||
pantry BOOLEAN,
|
||||
PRIMARY KEY (id),
|
||||
CHECK (pantry IN (0, 1))
|
||||
);
|
||||
CREATE TABLE unitdict (
|
||||
id INTEGER NOT NULL,
|
||||
ukey VARCHAR(150),
|
||||
value VARCHAR(150),
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
CREATE TABLE crossunitdict (
|
||||
id INTEGER NOT NULL,
|
||||
cukey VARCHAR(150),
|
||||
value VARCHAR(150),
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
CREATE TABLE density (
|
||||
id INTEGER NOT NULL,
|
||||
dkey VARCHAR(150),
|
||||
value VARCHAR(150),
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
CREATE TABLE shopcatsorder (
|
||||
id INTEGER NOT NULL,
|
||||
shopcategory TEXT(32),
|
||||
position INTEGER,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
CREATE TABLE convtable (
|
||||
id INTEGER NOT NULL,
|
||||
ckey VARCHAR(150),
|
||||
value VARCHAR(150),
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "shopcats" (
|
||||
id INTEGER NOT NULL,
|
||||
ingkey TEXT(32),
|
||||
shopcategory TEXT,
|
||||
position INTEGER,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "recipe_ingredients" ("Recipe_id" integer not null, "ingredientHash_id" integer not null, unique ("ingredientHash_id"));
|
|
@ -106,12 +106,10 @@ public class AdminMainBean implements Serializable {
|
|||
if (suggestionList == null) {
|
||||
switch (this.userSession.getSearchType()) {
|
||||
case rst_BY_CATEGORY:
|
||||
suggestionList =
|
||||
recipeService.findCategories();
|
||||
suggestionList = recipeService.findCategories();
|
||||
break;
|
||||
case rst_BY_CUISINE:
|
||||
suggestionList =
|
||||
recipeService.findCuisines();
|
||||
suggestionList = recipeService.findCuisines();
|
||||
break;
|
||||
default:
|
||||
suggestionList = new ArrayList<String>(1);
|
||||
|
@ -220,6 +218,14 @@ public class AdminMainBean implements Serializable {
|
|||
return "detailEdit?faces-redirect=true";
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to "More features" page (shopping list and
|
||||
* maint.)
|
||||
*/
|
||||
public String doMore() {
|
||||
return "shoppingList.jsf";
|
||||
}
|
||||
|
||||
/**
|
||||
* Show selected recipe
|
||||
*
|
||||
|
|
|
@ -243,6 +243,9 @@ public class RecipeDetailBean implements Serializable {
|
|||
buildIngredientFacade(recipe.getIngredientHash()));
|
||||
stringifyCategories(recipe);
|
||||
|
||||
this.shop = this.getUserSession().getShoppingList()
|
||||
.contains(recipe);
|
||||
|
||||
log.info("Set recipe: " + this.recipe);
|
||||
}
|
||||
|
||||
|
@ -262,8 +265,8 @@ public class RecipeDetailBean implements Serializable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Categories are a Set attached to recipe. Build
|
||||
* a displayable comma-separated list of them.
|
||||
* Categories are a Set attached to recipe. Build a
|
||||
* displayable comma-separated list of them.
|
||||
*
|
||||
* @param recipe Recipe to get categories from.
|
||||
*
|
||||
|
@ -667,6 +670,26 @@ public class RecipeDetailBean implements Serializable {
|
|||
.getWrappedData();
|
||||
}
|
||||
|
||||
private boolean shop = false;
|
||||
|
||||
public boolean isShop() {
|
||||
return shop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add/remove recipe to shopping list (toggle)
|
||||
*/
|
||||
public void doShop() {
|
||||
shop = !shop;
|
||||
List<Recipe> shoppingList =
|
||||
userSession.getShoppingList();
|
||||
if (shop) {
|
||||
shoppingList.add(recipe);
|
||||
} else {
|
||||
shoppingList.remove(recipe);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the recipe.
|
||||
*
|
||||
|
|
161
src/main/java/com/mousetech/gourmetj/ShopIngredient.java
Normal file
161
src/main/java/com/mousetech/gourmetj/ShopIngredient.java
Normal file
|
@ -0,0 +1,161 @@
|
|||
package com.mousetech.gourmetj;
|
||||
|
||||
import com.mousetech.gourmetj.utils.IngredientDigester;
|
||||
import com.mousetech.gourmetj.utils.IngredientDigester.IngredientAmountFormat;
|
||||
|
||||
public class ShopIngredient implements Comparable<Object> {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param shopCat
|
||||
* @param ingkey
|
||||
*/
|
||||
public ShopIngredient( String shopCat, String item,
|
||||
String ingkey) {
|
||||
this.shopCat = shopCat;
|
||||
this.ingkey = ingkey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param amount
|
||||
* @param unit
|
||||
* @param item
|
||||
* @param ingkey
|
||||
* @param shopCat
|
||||
*/
|
||||
public ShopIngredient( double amount, String unit,
|
||||
String item, String ingkey,
|
||||
String shopCat) {
|
||||
this.amount = amount;
|
||||
this.unit = unit;
|
||||
this.item = item;
|
||||
this.ingkey = ingkey;
|
||||
this.shopCat = shopCat;
|
||||
}
|
||||
|
||||
private String shopCat;
|
||||
|
||||
/**
|
||||
* @return the shopCat
|
||||
*/
|
||||
public String getShopCat() {
|
||||
return shopCat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param shopCat the shopCat to set
|
||||
*/
|
||||
public void setShopCat(String shopCat) {
|
||||
this.shopCat = shopCat;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the ingkey
|
||||
*/
|
||||
public String getIngkey() {
|
||||
return ingkey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ingkey the ingkey to set
|
||||
*/
|
||||
public void setIngkey(String ingkey) {
|
||||
this.ingkey = ingkey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the amount
|
||||
*/
|
||||
public double getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param amount the amount to set
|
||||
*/
|
||||
public void setAmount(double amount) {
|
||||
this.amount = amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the displayAmount
|
||||
*/
|
||||
public String getDisplayAmount() {
|
||||
return IngredientDigester.displayAmount(
|
||||
IngredientAmountFormat.IA_TEXT, this.getAmount(),
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the unit
|
||||
*/
|
||||
public String getUnit() {
|
||||
return unit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param unit the unit to set
|
||||
*/
|
||||
public void setUnit(String unit) {
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
private String item;
|
||||
|
||||
/**
|
||||
* @return the item
|
||||
*/
|
||||
public String getItem() {
|
||||
return item;
|
||||
}
|
||||
|
||||
private String ingkey;
|
||||
private double amount;
|
||||
private String displayAmount;
|
||||
private String unit;
|
||||
|
||||
@Override
|
||||
public int compareTo(Object o) {
|
||||
if ((o == null) || !(o instanceof ShopIngredient)) {
|
||||
throw new RuntimeException(
|
||||
"Invalid shipIngredient comparison");
|
||||
}
|
||||
ShopIngredient o1 = (ShopIngredient) o;
|
||||
int i = relate(this.getItem(), o1.getItem());
|
||||
if (i != 0) {
|
||||
return i;
|
||||
}
|
||||
i = relate(this.getShopCat(), o1.getShopCat());
|
||||
if (i != 0) {
|
||||
return i;
|
||||
}
|
||||
// TODO: normalize case, singular/plural/abbreviations
|
||||
i = relate(this.getUnit(), o1.getUnit());
|
||||
if (i != 0) {
|
||||
return i;
|
||||
}
|
||||
return i; // ZERO
|
||||
}
|
||||
|
||||
private int relate(String item2, String item3) {
|
||||
if ((item2 == null) && (item3 == null)) {
|
||||
return 0;
|
||||
}
|
||||
if (item2 == null) {
|
||||
return -1;
|
||||
}
|
||||
if (item3 == null) {
|
||||
return 1;
|
||||
}
|
||||
return item2.compareTo(item3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getAmount() + " " + this.getUnit() + " "
|
||||
+ this.getItem() + " " + this.getIngkey() + " "
|
||||
+ this.getShopCat();
|
||||
}
|
||||
}
|
213
src/main/java/com/mousetech/gourmetj/ShoppingListBean.java
Normal file
213
src/main/java/com/mousetech/gourmetj/ShoppingListBean.java
Normal file
|
@ -0,0 +1,213 @@
|
|||
package com.mousetech.gourmetj;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.faces.event.AjaxBehaviorEvent;
|
||||
import javax.faces.view.ViewScoped;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.primefaces.event.ReorderEvent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.mousetech.gourmetj.persistence.dao.ShopcatRepository;
|
||||
import com.mousetech.gourmetj.persistence.model.Ingredient;
|
||||
import com.mousetech.gourmetj.persistence.model.Recipe;
|
||||
import com.mousetech.gourmetj.persistence.model.Shopcat;
|
||||
|
||||
@Named
|
||||
@ViewScoped
|
||||
public class ShoppingListBean implements Serializable {
|
||||
|
||||
/**
|
||||
* Serial version for session save/restore
|
||||
*/
|
||||
private static final long serialVersionUID =
|
||||
7449440266704831598L;
|
||||
|
||||
/* Logger */
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(ShoppingListBean.class);
|
||||
|
||||
@Inject
|
||||
private UserSession userSession;
|
||||
|
||||
private List<ShopIngredient> siList;
|
||||
|
||||
// private List<ShopIngredient> ingredientList;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
// Load up details on recipes
|
||||
this.siList = new ArrayList<ShopIngredient>(30);
|
||||
buildMaps();
|
||||
}
|
||||
|
||||
public List<Recipe> getRecipeList() {
|
||||
return this.userSession.getShoppingList();
|
||||
}
|
||||
|
||||
public List<ShopIngredient> getIngredientList() {
|
||||
return this.siList;
|
||||
}
|
||||
|
||||
private void buildMaps() {
|
||||
for (Recipe r : this.getRecipeList()) {
|
||||
buildMapsFor(r);
|
||||
}
|
||||
// Now consolidate amounts and sort by
|
||||
// shopcat/item/ingkey
|
||||
optimizeIngredients(this.siList);
|
||||
|
||||
this.siList.sort(new ShopclassComparator());
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the ingredient list for the selected recipe and add
|
||||
* them to siList
|
||||
*
|
||||
* @param r
|
||||
*
|
||||
* @see #buildMaps()
|
||||
*/
|
||||
private void buildMapsFor(Recipe r) {
|
||||
for (Ingredient ing : r.getIngredientHash()) {
|
||||
String ingkey = ing.getIngkey();
|
||||
if (StringUtils.isBlank(ingkey)) {
|
||||
continue;
|
||||
}
|
||||
String shopCatName = ing.getShopCat() != null
|
||||
? ing.getShopCat().getShopcategory()
|
||||
: null;
|
||||
ShopIngredient sing = new ShopIngredient(
|
||||
ing.getAmount(), ing.getUnit(),
|
||||
ing.getItem(), ing.getIngkey(), shopCatName);
|
||||
siList.add(sing);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort ShopIngredient list, then optimize it by
|
||||
* consolidating amounts where possible.
|
||||
*
|
||||
* @param victim List to optimize
|
||||
*
|
||||
* #TestedBy @see ShoppingListBeanTest
|
||||
*/
|
||||
static void optimizeIngredients(
|
||||
List<ShopIngredient> victim) {
|
||||
victim.sort(null);
|
||||
for (int i = 0; i < (victim.size() - 1); i++) {
|
||||
ShopIngredient si = victim.get(i);
|
||||
ShopIngredient si2 = victim.get(i + 1);
|
||||
if (si.compareTo(si2) == 0) {
|
||||
si.setAmount(si.getAmount() + si2.getAmount());
|
||||
victim.remove(si2); // reduces size()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ShopclassComparator
|
||||
implements Comparator<ShopIngredient> {
|
||||
|
||||
@Override
|
||||
public int compare(ShopIngredient ing1,
|
||||
ShopIngredient ing2) {
|
||||
int i = 0;
|
||||
i = relate(ing1.getShopCat(), ing2.getShopCat());
|
||||
if (i != 0) {
|
||||
return i;
|
||||
}
|
||||
i = relate(ing1.getItem(), ing2.getItem());
|
||||
if (i != 0) {
|
||||
return i;
|
||||
}
|
||||
i = relate(ing1.getIngkey(), ing2.getIngkey());
|
||||
if (i != 0) {
|
||||
return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int relate(String item2, String item3) {
|
||||
if ((item2 == null) && (item3 == null)) {
|
||||
return 0;
|
||||
}
|
||||
if (item2 == null) {
|
||||
return -1;
|
||||
}
|
||||
if (item3 == null) {
|
||||
return 1;
|
||||
}
|
||||
return item2.compareTo(item3);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Shopcat> shopcatList;
|
||||
|
||||
public List<Shopcat> getShopcatList() {
|
||||
if (shopcatList == null) {
|
||||
shopcatList = loadShopcatList();
|
||||
}
|
||||
return shopcatList;
|
||||
}
|
||||
|
||||
@Inject
|
||||
ShopcatRepository shopcatRepository;
|
||||
|
||||
private List<Shopcat> loadShopcatList() {
|
||||
return shopcatRepository
|
||||
.findAllByOrderByShopcategoryAsc();
|
||||
}
|
||||
|
||||
private Shopcat xeditShopcat = new Shopcat();
|
||||
|
||||
private String oldShopcategoryName;
|
||||
|
||||
/**
|
||||
* @return the editShopcat
|
||||
*/
|
||||
public Shopcat getEditShopcat() {
|
||||
return xeditShopcat;
|
||||
}
|
||||
|
||||
public void doEditShopcat(int scId) {
|
||||
xeditShopcat = null;
|
||||
final List<Shopcat> scl = getShopcatList();
|
||||
for (Shopcat sc : scl) {
|
||||
if (sc.getId() == scId) {
|
||||
xeditShopcat = sc;
|
||||
this.oldShopcategoryName = sc.getShopcategory();
|
||||
if (sc.getPosition() == null) {
|
||||
sc.setPosition(0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
log.error("SHOPCAT " + scId + " NOT FOUND");
|
||||
}
|
||||
|
||||
public void ajaxOnClickShopcatIngkey() {
|
||||
// Saves 1 shopcat/ingkey
|
||||
this.shopcatRepository.save(xeditShopcat);
|
||||
this.shopcatList = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all ingredient keys for shopcat name-change.
|
||||
* Note that once done, this cannot be undone!
|
||||
*/
|
||||
public void ajaxOnClickShopcat() {
|
||||
this.shopcatRepository.UpdateShopcats(
|
||||
this.oldShopcategoryName, xeditShopcat.getShopcategory());
|
||||
this.shopcatList = null;
|
||||
}
|
||||
}
|
|
@ -182,6 +182,13 @@ public class UserSession implements Serializable {
|
|||
// Session timeout, ms (25 minutes)
|
||||
long sessionTimeoutInterval = 25 * 60_000L;
|
||||
|
||||
/**
|
||||
* When you click the "shop" button on a recipe, it
|
||||
* gets added to this list. Note that it's the fully-expanded
|
||||
* recipe!
|
||||
*/
|
||||
private List<Recipe> shoppingList = new ArrayList<Recipe>();
|
||||
|
||||
/**
|
||||
* @return the sessionTimeoutInterval
|
||||
*/
|
||||
|
@ -199,4 +206,8 @@ public class UserSession implements Serializable {
|
|||
log.warn("Session Idle listener logout");
|
||||
return "/main.jsf";
|
||||
}
|
||||
|
||||
public List<Recipe> getShoppingList() {
|
||||
return this.shoppingList ;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,10 @@ package com.mousetech.gourmetj.persistence.dao;
|
|||
|
||||
import java.util.List;
|
||||
|
||||
import javax.transaction.Transactional;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
|
@ -28,7 +31,14 @@ public interface ShopcatRepository
|
|||
@Query(value = SQL_FIND_CATEGORIES, nativeQuery = true)
|
||||
public List<String> findDistinctCategoryNative();
|
||||
|
||||
@Transactional
|
||||
@Query(value = "UPDATE Shopcat set shopcategory = :newCatname WHERE shopcategory = :oldCatname")
|
||||
@Modifying
|
||||
public int UpdateShopcats(String oldCatname, String newCatname);
|
||||
|
||||
public Shopcat findShopcatByIngkey(String ingkey);
|
||||
|
||||
public void deleteByIngkey(String key);
|
||||
|
||||
public List<Shopcat> findAllByOrderByShopcategoryAsc();
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
<h1>
|
||||
<ui:insert name="title">Gourmet Recipe Manager (web version)</ui:insert>
|
||||
</h1>
|
||||
<p:ajaxStatus onerror="PF('opError').show()"/>
|
||||
<ui:insert name="content">
|
||||
<ui:include src="content.xhtml" />
|
||||
</ui:insert>
|
||||
|
@ -29,10 +30,10 @@
|
|||
href="http://www.apache.org/licenses/LICENSE-2.0"
|
||||
>Apache License, Version 2.0</a>.
|
||||
<p>Based on Gourmet Recipe Manager by T. Hinkle</p>
|
||||
<h:form id="ftmTimeout">
|
||||
<h:form id="frmTimeout">
|
||||
<p:idleMonitor
|
||||
timeout="#{userSession.sessionTimeoutInterval}"
|
||||
onidle="PF('sessionExpiredConfirmation').show()"
|
||||
onidle="PF('dlgSessionExpired').show()"
|
||||
>
|
||||
<p:ajax event="idle"
|
||||
listener="#{userSession.sessionIdleListener}"
|
||||
|
@ -43,12 +44,12 @@
|
|||
message="Your session has expired."
|
||||
header="#{msgs['confirmDialog.initiatingDestroyProcess.label']}"
|
||||
severity="alert"
|
||||
widgetVar="sessionExpiredConfirmation"
|
||||
widgetVar="dlgSessionExpired"
|
||||
style="z-index: 25000"
|
||||
>
|
||||
<p:commandButton id="cmdExpiredOK"
|
||||
value="OK" action="/main.jsf"
|
||||
oncomplete="PF('sessionExpiredConfirmation').hide()"
|
||||
oncomplete="PF('dlgSessionExpired').hide()"
|
||||
/>
|
||||
</p:confirmDialog>
|
||||
</h:form>
|
||||
|
@ -60,7 +61,7 @@
|
|||
severity="alert" widgetVar="opError"
|
||||
>
|
||||
<p:commandButton value="OK"
|
||||
oncomplete="PF('cd').hide()"
|
||||
oncomplete="PF('opError').hide()"
|
||||
/>
|
||||
</p:confirmDialog>
|
||||
</h:form>
|
||||
|
|
|
@ -13,6 +13,10 @@
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.greenButton {
|
||||
background-color: green !important;
|
||||
}
|
||||
|
||||
textarea {
|
||||
font-family: 'latoregular', 'Trebuchet MS,Arial,Helvetica,sans-serif';
|
||||
font-size: 1em
|
||||
|
|
68
src/main/resources/META-INF/resources/editShopcatList.xhtml
Normal file
68
src/main/resources/META-INF/resources/editShopcatList.xhtml
Normal file
|
@ -0,0 +1,68 @@
|
|||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
||||
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||
xmlns:p="http://primefaces.org/ui"
|
||||
>
|
||||
<h:head>
|
||||
<title>Shopping Category</title>
|
||||
<style type="text/css">
|
||||
html {
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
</h:head>
|
||||
<h:body>
|
||||
<ui:component>
|
||||
<h:form id="frmShopcat">
|
||||
<p:panelGrid columns="1">
|
||||
<p:dataTable id="tblShopcats"
|
||||
style="width: 600px"
|
||||
value="#{shoppingListBean.shopcatList}"
|
||||
sortBy="#{item.shopCat}" var="item"
|
||||
>
|
||||
<p:headerRow>
|
||||
<p:column colspan="4">
|
||||
<h:outputText value="#{item.shopCat}" />
|
||||
</p:column>
|
||||
</p:headerRow>
|
||||
</p:dataTable>
|
||||
<div>Ingredient key:
|
||||
#{editShopcatBean.ingkey}</div>
|
||||
<p:outputLabel for="@next"
|
||||
value="Category Name"
|
||||
/>
|
||||
<p:inputText id="ctlShopcat"
|
||||
value="#{editShopcatBean.shopcatName}"
|
||||
>
|
||||
</p:inputText>
|
||||
<h:outputText value="suggestion:" />
|
||||
<p:selectOneMenu id="ctlShopcatMenu"
|
||||
value="#{editShopcatBean.shopcatSuggestion}"
|
||||
>
|
||||
<f:selectItems
|
||||
value="#{editShopcatBean.shopcatSuggestionList}"
|
||||
/>
|
||||
<p:ajax event="change"
|
||||
listener="#{editShopcatBean.ajaxShopcatSuggest}"
|
||||
update="ctlShopcat"
|
||||
/>
|
||||
</p:selectOneMenu>
|
||||
<p:panelGrid columns="2" style="width: 100%">
|
||||
<p:commandButton id="scDlgOK" value="OK"
|
||||
style="width: 6em"
|
||||
action="#{recipeDetailBean.doUpdateShopcat}"
|
||||
update="form1:tabGroupClient:ingredientTable"
|
||||
oncomplete="PF('editShopcatDlg').hide()"
|
||||
/>
|
||||
<p:commandButton id="scDlgCan"
|
||||
value="Cancel" style="width: 6em"
|
||||
onclick="PF('editShopcatDlg').hide()"
|
||||
/>
|
||||
</p:panelGrid>
|
||||
</p:panelGrid>
|
||||
</h:form>
|
||||
</ui:component>
|
||||
</h:body>
|
||||
</html>
|
|
@ -43,6 +43,9 @@
|
|||
<p:commandButton value="New Recipe"
|
||||
action="#{adminMainBean.doNewRecipe}"
|
||||
/>
|
||||
<p:commandButton value="More..."
|
||||
action="#{adminMainBean.doMore}"
|
||||
/>
|
||||
</div>
|
||||
</h:form>
|
||||
<h:form id="form2">
|
||||
|
|
|
@ -57,6 +57,15 @@
|
|||
styleClass="ui-button-print"
|
||||
immediate="true"
|
||||
/>
|
||||
<p:commandButton id="ctlShop"
|
||||
icon="ui-icon-cart"
|
||||
value="Shop"
|
||||
immediate="true"
|
||||
styleClass="#{recipeDetailBean.shop ? 'greenButton' : null}"
|
||||
action="#{recipeDetailBean.doShop}"
|
||||
update="ctlShop"
|
||||
/>
|
||||
<h:outputText value=""/>
|
||||
<p:outputLabel for="@next"
|
||||
value="Categories:"
|
||||
/>
|
||||
|
@ -197,8 +206,7 @@
|
|||
<p:confirmDialog closable="false" id="okDeleteDlg"
|
||||
header="Confirm Deletion"
|
||||
message="OK to delete this recipe?"
|
||||
severity="alert"
|
||||
widgetVar="okDeleteDlg"
|
||||
severity="alert" widgetVar="okDeleteDlg"
|
||||
style="z-index: 25000"
|
||||
>
|
||||
<p:commandButton id="dlgOK" value="OK"
|
||||
|
|
198
src/main/resources/META-INF/resources/shoppingList.xhtml
Normal file
198
src/main/resources/META-INF/resources/shoppingList.xhtml
Normal file
|
@ -0,0 +1,198 @@
|
|||
<?xml version="1.0"?>
|
||||
<ui:composition template="/WEB-INF/layout/layout.xhtml"
|
||||
xmlns:h="http://xmlns.jcp.org/jsf/html"
|
||||
xmlns:f="http://xmlns.jcp.org/jsf/core"
|
||||
xmlns:ui="http://java.sun.com/jsf/facelets"
|
||||
xmlns:p="http://primefaces.org/ui"
|
||||
xmlns:c="http://xmlns.jcp.org/jstl"
|
||||
>
|
||||
<ui:define name="title">Gourmet Recipe Manager - Shopping</ui:define>
|
||||
<ui:define name="content">
|
||||
<style>
|
||||
.recipeTitle {
|
||||
font-size: larger;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: large;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.ui-panelgrid-cell {
|
||||
border-width: 0;
|
||||
border-style: none;
|
||||
}
|
||||
</style>
|
||||
<h:messages />
|
||||
<h:form id="form1">
|
||||
<p:dataTable id="tblRecipes" style="width: 600px"
|
||||
value="#{shoppingListBean.recipeList}" var="item"
|
||||
>
|
||||
<p:column headerText="Recipe">
|
||||
<h:outputText value="#{item.title}" />
|
||||
</p:column>
|
||||
</p:dataTable>
|
||||
<!-- ====== Ingredients ============================ -->
|
||||
<p:column id="ingredientsc"
|
||||
style="width: 25%; vertical-align: top;"
|
||||
>
|
||||
<p:dataTable id="tblShopIngredients"
|
||||
style="width: 600px; margin-top: 10px"
|
||||
value="#{shoppingListBean.ingredientList}"
|
||||
sortBy="#{item.shopCat}" var="item"
|
||||
>
|
||||
<f:facet name="header">
|
||||
<h:outputText styleClass="subtitle"
|
||||
value="Ingredients"
|
||||
/>
|
||||
</f:facet>
|
||||
<p:headerRow>
|
||||
<p:column colspan="4">
|
||||
<h:outputText value="#{item.shopCat}" />
|
||||
</p:column>
|
||||
</p:headerRow>
|
||||
<p:column label="ingKey">
|
||||
<h:outputText value="#{item.ingkey}" />
|
||||
</p:column>
|
||||
<p:column label="Amt"
|
||||
style="width: 3em; text-align: right"
|
||||
>
|
||||
<h:outputText
|
||||
value="#{item.displayAmount}"
|
||||
/>
|
||||
</p:column>
|
||||
<p:column label="Units" style="width: 5em">
|
||||
<h:outputText value="#{item.unit}" />
|
||||
</p:column>
|
||||
<p:column label="Item" style="width: 20em">
|
||||
<h:outputText value="#{item.item}" />
|
||||
</p:column>
|
||||
</p:dataTable>
|
||||
</p:column>
|
||||
<p:commandButton id="doHome" value="Home"
|
||||
icon="ui-icon-home" ajax="false" immediate="true"
|
||||
action="main.jsf"
|
||||
/>
|
||||
<p:dataTable id="tblShopcats" style="width: 300px"
|
||||
value="#{shoppingListBean.shopcatList}"
|
||||
sortBy="#{item.shopcategory}" var="item"
|
||||
>
|
||||
<f:facet name="header">
|
||||
<h:outputText
|
||||
value="Ingredient Keys/Shopping Categories"
|
||||
/>
|
||||
</f:facet>
|
||||
<p:headerRow>
|
||||
<p:column>
|
||||
<h:outputText id="ctlCatname"
|
||||
value="#{item.shopcategory}"
|
||||
/>
|
||||
</p:column>
|
||||
<p:column style="width: 5em">
|
||||
<p:commandButton id="ctlEditCat"
|
||||
value="Rename"
|
||||
action="#{shoppingListBean.doEditShopcat(item.id)}"
|
||||
onclick="PF('dlgEditShopcat').show()"
|
||||
update="frmEscat:pnlData"
|
||||
/>
|
||||
</p:column>
|
||||
</p:headerRow>
|
||||
<p:column>
|
||||
<h:outputText value="#{item.ingkey}"
|
||||
onclick="clickIngkeyName()"
|
||||
/>
|
||||
</p:column>
|
||||
<p:column style="width: 5em">
|
||||
<p:commandButton id="ctlEditIngkey"
|
||||
value="Move"
|
||||
action="#{shoppingListBean.doEditShopcat(item.id)}"
|
||||
onclick="PF('dlgEditIngkey').show()"
|
||||
update="frmIngkey:pnlData"
|
||||
/>
|
||||
</p:column>
|
||||
</p:dataTable>
|
||||
</h:form>
|
||||
<!-- -->
|
||||
<h:form id="frmEscat">
|
||||
<p:dialog id="dlgEditShopcat"
|
||||
widgetVar="dlgEditShopcat"
|
||||
header="Edit/Rename Shopping Category"
|
||||
closeOnEscape="true"
|
||||
>
|
||||
<p:panelGrid id="pnlData" columns="2"
|
||||
styleClass="ui-panelgrid-blank"
|
||||
>
|
||||
<p:outputLabel for="@next"
|
||||
value="Shopping Category"
|
||||
/>
|
||||
<p:inputText id="ctlDlgShopcatName"
|
||||
label="Position" required="true"
|
||||
value="#{shoppingListBean.editShopcat.shopcategory}"
|
||||
/>
|
||||
<p:outputLabel for="@next" value="Position" />
|
||||
<p:inputText id="ctlDlgShopcatPosition"
|
||||
type="number" required="true" size="3"
|
||||
value="#{shoppingListBean.editShopcat.position}"
|
||||
/>
|
||||
</p:panelGrid>
|
||||
<p:panelGrid columns="2" style="width: 100%"
|
||||
styleClass="ui-panelgrid-blank"
|
||||
>
|
||||
<p:commandButton id="scDlgOK" value="OK"
|
||||
style="width: 6em"
|
||||
action="#{shoppingListBean.ajaxOnClickShopcat}"
|
||||
update="form1:tblShopcats"
|
||||
process="@this pnlData"
|
||||
oncomplete="PF('dlgEditShopcat').hide()"
|
||||
/>
|
||||
<p:commandButton id="scDlgCan" value="Cancel"
|
||||
style="width: 6em"
|
||||
onclick="PF('dlgEditShopcat').hide()"
|
||||
/>
|
||||
</p:panelGrid>
|
||||
</p:dialog>
|
||||
</h:form>
|
||||
<!-- -->
|
||||
<h:form id="frmIngkey">
|
||||
<p:dialog id="dlgEditIngkey"
|
||||
widgetVar="dlgEditIngkey"
|
||||
header="Edit/Rename Shopping Category for Ingredient Key"
|
||||
closeOnEscape="true"
|
||||
>
|
||||
<p:panelGrid id="pnlData" columns="2"
|
||||
styleClass="ui-panelgrid-blank"
|
||||
>
|
||||
<p:outputLabel for="@next"
|
||||
value="Ingredient Key"
|
||||
/>
|
||||
<h:outputText id="ctlDlgIngKeyIngkey"
|
||||
value="#{shoppingListBean.editShopcat.ingkey}"
|
||||
/>
|
||||
<p:outputLabel for="@next"
|
||||
value="Shopping Category"
|
||||
/>
|
||||
<p:inputText id="ctlDlgIngkeyShopcatName"
|
||||
label="Position" required="true"
|
||||
value="#{shoppingListBean.editShopcat.shopcategory}"
|
||||
/>
|
||||
</p:panelGrid>
|
||||
<p:panelGrid columns="2" style="width: 100%"
|
||||
styleClass="ui-panelgrid-blank"
|
||||
>
|
||||
<p:commandButton id="ingkDlgOK" value="OK"
|
||||
style="width: 6em"
|
||||
action="#{shoppingListBean.ajaxOnClickShopcatIngkey}"
|
||||
update="form1:tblShopcats"
|
||||
process="@this pnlData"
|
||||
oncomplete="PF('dlgEditIngkey').hide()"
|
||||
/>
|
||||
<p:commandButton id="ingkDlgCan"
|
||||
value="Cancel" style="width: 6em"
|
||||
onclick="PF('dlgEditIngkey').hide()"
|
||||
/>
|
||||
</p:panelGrid>
|
||||
</p:dialog>
|
||||
</h:form>
|
||||
</ui:define>
|
||||
</ui:composition>
|
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* Copyright (C) 2022, Tim Holloway
|
||||
*
|
||||
* Date written: Jan 12, 2022
|
||||
* Author: Tim Holloway <timh@mousetech.com>
|
||||
*/
|
||||
package com.mousetech.gourmetj;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.mousetech.gourmetj.ShopIngredient;
|
||||
import com.mousetech.gourmetj.persistence.model.Shopcat;
|
||||
|
||||
/**
|
||||
* @author timh
|
||||
* @since Jan 12, 2022
|
||||
*/
|
||||
class ShoppingListBeanTest {
|
||||
|
||||
static List<ShopIngredient> testList;
|
||||
|
||||
/**
|
||||
* @throws java.lang.Exception
|
||||
*/
|
||||
@BeforeAll
|
||||
static void setUpBeforeClass() throws Exception {
|
||||
testList = new ArrayList<ShopIngredient>();
|
||||
testList.add(new ShopIngredient(2.0d, "cup",
|
||||
"sugar", "sugar", "baking"));
|
||||
testList.add(new ShopIngredient(1.5, "tsp",
|
||||
"salt", "salt", "condiments"));
|
||||
testList.add(new ShopIngredient(0.5, "tsp",
|
||||
"pepper", "pepper", "condiments"));
|
||||
testList.add(new ShopIngredient(2.0d, "cup",
|
||||
"milk", "milk", "dairy"));
|
||||
testList.add(new ShopIngredient(0.75d, "cup",
|
||||
"sugar", "sugar", "baking"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void test() {
|
||||
ShoppingListBean.optimizeIngredients(testList);
|
||||
assertEquals(4, testList.size());
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user