Merge branch 'shoppinglist'
Adds Shopping List feature to app.
This commit is contained in:
commit
c80b8598d4
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"));
|
|
@ -101,17 +101,15 @@ public class AdminMainBean implements Serializable {
|
||||||
public void resetSuggestions() {
|
public void resetSuggestions() {
|
||||||
suggestionList = null;
|
suggestionList = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> searchSuggestionList(String query) {
|
public List<String> searchSuggestionList(String query) {
|
||||||
if (suggestionList == null) {
|
if (suggestionList == null) {
|
||||||
switch (this.userSession.getSearchType()) {
|
switch (this.userSession.getSearchType()) {
|
||||||
case rst_BY_CATEGORY:
|
case rst_BY_CATEGORY:
|
||||||
suggestionList =
|
suggestionList = recipeService.findCategories();
|
||||||
recipeService.findCategories();
|
|
||||||
break;
|
break;
|
||||||
case rst_BY_CUISINE:
|
case rst_BY_CUISINE:
|
||||||
suggestionList =
|
suggestionList = recipeService.findCuisines();
|
||||||
recipeService.findCuisines();
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
suggestionList = new ArrayList<String>(1);
|
suggestionList = new ArrayList<String>(1);
|
||||||
|
@ -119,7 +117,7 @@ public class AdminMainBean implements Serializable {
|
||||||
}
|
}
|
||||||
return suggestionList;
|
return suggestionList;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**/
|
/**/
|
||||||
transient DataModel<Recipe> searchResults;
|
transient DataModel<Recipe> searchResults;
|
||||||
|
|
||||||
|
@ -220,6 +218,14 @@ public class AdminMainBean implements Serializable {
|
||||||
return "detailEdit?faces-redirect=true";
|
return "detailEdit?faces-redirect=true";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to "More features" page (shopping list and
|
||||||
|
* maint.)
|
||||||
|
*/
|
||||||
|
public String doMore() {
|
||||||
|
return "shoppingList.jsf";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show selected recipe
|
* Show selected recipe
|
||||||
*
|
*
|
||||||
|
|
|
@ -243,6 +243,9 @@ public class RecipeDetailBean implements Serializable {
|
||||||
buildIngredientFacade(recipe.getIngredientHash()));
|
buildIngredientFacade(recipe.getIngredientHash()));
|
||||||
stringifyCategories(recipe);
|
stringifyCategories(recipe);
|
||||||
|
|
||||||
|
this.shop = this.getUserSession().getShoppingList()
|
||||||
|
.contains(recipe);
|
||||||
|
|
||||||
log.info("Set recipe: " + this.recipe);
|
log.info("Set recipe: " + this.recipe);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,8 +265,8 @@ public class RecipeDetailBean implements Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Categories are a Set attached to recipe. Build
|
* Categories are a Set attached to recipe. Build a
|
||||||
* a displayable comma-separated list of them.
|
* displayable comma-separated list of them.
|
||||||
*
|
*
|
||||||
* @param recipe Recipe to get categories from.
|
* @param recipe Recipe to get categories from.
|
||||||
*
|
*
|
||||||
|
@ -667,6 +670,26 @@ public class RecipeDetailBean implements Serializable {
|
||||||
.getWrappedData();
|
.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.
|
* Save the recipe.
|
||||||
*
|
*
|
||||||
|
|
165
src/main/java/com/mousetech/gourmetj/ShopIngredient.java
Normal file
165
src/main/java/com/mousetech/gourmetj/ShopIngredient.java
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
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() {
|
||||||
|
Double amt = this.getAmount();
|
||||||
|
if ( amt == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return IngredientDigester.displayAmount(
|
||||||
|
IngredientAmountFormat.IA_TEXT, amt,
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
397
src/main/java/com/mousetech/gourmetj/ShoppingListBean.java
Normal file
397
src/main/java/com/mousetech/gourmetj/ShoppingListBean.java
Normal file
|
@ -0,0 +1,397 @@
|
||||||
|
package com.mousetech.gourmetj;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import javax.faces.view.ViewScoped;
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.primefaces.model.ByteArrayContent;
|
||||||
|
import org.primefaces.model.DefaultStreamedContent;
|
||||||
|
import org.primefaces.model.StreamedContent;
|
||||||
|
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;
|
||||||
|
import com.mousetech.gourmetj.utils.YamlShoppingList;
|
||||||
|
|
||||||
|
@Named
|
||||||
|
@ViewScoped
|
||||||
|
public class ShoppingListBean implements Serializable {
|
||||||
|
|
||||||
|
public class RecipeReference {
|
||||||
|
|
||||||
|
private int count;
|
||||||
|
private Recipe recipe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor Constructor.
|
||||||
|
*
|
||||||
|
* @param r Recipe to reference (from Shopping List)
|
||||||
|
*/
|
||||||
|
public RecipeReference(Recipe r) {
|
||||||
|
count = 1;
|
||||||
|
recipe = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the count
|
||||||
|
*/
|
||||||
|
public int getCount() {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param count the count to set
|
||||||
|
*/
|
||||||
|
public void setCount(int count) {
|
||||||
|
this.count = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the recipe
|
||||||
|
*/
|
||||||
|
public Recipe getRecipe() {
|
||||||
|
return recipe;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param recipe the recipe to set
|
||||||
|
*/
|
||||||
|
public void setRecipe(Recipe recipe) {
|
||||||
|
this.recipe = recipe;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<RecipeReference> recipeList;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
// Load up details on recipes
|
||||||
|
this.siList = new ArrayList<ShopIngredient>(30);
|
||||||
|
buildMaps();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<RecipeReference> getRecipeList() {
|
||||||
|
if (this.recipeList == null) {
|
||||||
|
this.recipeList = loadRecipeList();
|
||||||
|
}
|
||||||
|
return this.recipeList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<RecipeReference> loadRecipeList() {
|
||||||
|
List<RecipeReference> list =
|
||||||
|
userSession.getShoppingList().stream()
|
||||||
|
.map(r -> new RecipeReference(r))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ShopIngredient> getIngredientList() {
|
||||||
|
return this.siList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildMaps() {
|
||||||
|
this.siList = new ArrayList<ShopIngredient>(30);
|
||||||
|
for (RecipeReference 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(RecipeReference r) {
|
||||||
|
final int multiplier = r.getCount();
|
||||||
|
if (multiplier == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (Ingredient ing : r.getRecipe()
|
||||||
|
.getIngredientHash()) {
|
||||||
|
String ingkey = ing.getIngkey();
|
||||||
|
if (StringUtils.isBlank(ingkey)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String shopCatName = ing.getShopCat() != null
|
||||||
|
? ing.getShopCat().getShopcategory()
|
||||||
|
: null;
|
||||||
|
ShopIngredient sing;
|
||||||
|
try {
|
||||||
|
Double amt = ing.getAmount();
|
||||||
|
if (multiplier > 1 && (amt != null)) {
|
||||||
|
amt *= multiplier;
|
||||||
|
}
|
||||||
|
sing = new ShopIngredient(amt, ing.getUnit(),
|
||||||
|
ing.getItem(), ing.getIngkey(),
|
||||||
|
shopCatName);
|
||||||
|
siList.add(sing);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Unable to create ShopIngredient for "
|
||||||
|
+ r.getRecipe() + " Ingredient " + ing);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public StreamedContent getDlIngredientList() {
|
||||||
|
return YamlShoppingList
|
||||||
|
.createDownload(getIngredientList());
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
private List<String> shopcatList;
|
||||||
|
|
||||||
|
public List<String> getShopcatList() {
|
||||||
|
if (shopcatList == null) {
|
||||||
|
shopcatList = loadShopcatList();
|
||||||
|
}
|
||||||
|
return shopcatList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
ShopcatRepository shopcatRepository;
|
||||||
|
|
||||||
|
private List<String> loadShopcatList() {
|
||||||
|
return shopcatRepository.findDistinctCategoryNative();
|
||||||
|
// .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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primefaces AJAX listener for changes to amount values of
|
||||||
|
* recipes the recipe list. Forces re-computation of
|
||||||
|
* ingredient requirements.
|
||||||
|
*/
|
||||||
|
public void pfAmountChange() {
|
||||||
|
buildMaps();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===
|
||||||
|
private String selectedShopcat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the selectedShopcat
|
||||||
|
*/
|
||||||
|
public String getSelectedShopcat() {
|
||||||
|
return selectedShopcat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param selectedShopcat the selectedShopcat to set
|
||||||
|
*/
|
||||||
|
public void setSelectedShopcat(String selectedShopcat) {
|
||||||
|
this.selectedShopcat = selectedShopcat;
|
||||||
|
this.ingkeyList = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> selectedIngkey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the selectedIngkey
|
||||||
|
*/
|
||||||
|
public List<String> getSelectedIngkey() {
|
||||||
|
return selectedIngkey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param selectedIngkey the selectedIngkey to set
|
||||||
|
*/
|
||||||
|
public void setSelectedIngkey(List<String> selectedIngkey) {
|
||||||
|
this.selectedIngkey = selectedIngkey;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> ingkeyList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the ingkeyList
|
||||||
|
*/
|
||||||
|
public List<String> getIngkeyList() {
|
||||||
|
if (ingkeyList == null) {
|
||||||
|
ingkeyList = loadIngkeyListFor(selectedShopcat);
|
||||||
|
}
|
||||||
|
return ingkeyList;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> loadIngkeyListFor(
|
||||||
|
String selectedShopcat2) {
|
||||||
|
List<String> list = this.shopcatRepository
|
||||||
|
.findByIngkeySorted(selectedShopcat2);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String newShopcat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the newShopcat
|
||||||
|
*/
|
||||||
|
public String getNewShopcat() {
|
||||||
|
return newShopcat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param newShopcat the newShopcat to set
|
||||||
|
*/
|
||||||
|
public void setNewShopcat(String newShopcat) {
|
||||||
|
this.newShopcat = newShopcat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> suggestShopcat(String query) {
|
||||||
|
return this.shopcatList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void doChangeShopcat() {
|
||||||
|
String oldCat = this.getSelectedShopcat();
|
||||||
|
String newCat = this.getNewShopcat();
|
||||||
|
if (oldCat.equals(newCat)) {
|
||||||
|
return; // effective NO-OP
|
||||||
|
}
|
||||||
|
newCat = newCat.trim();
|
||||||
|
if (StringUtils.isBlank(newCat)) {
|
||||||
|
this.shopcatRepository
|
||||||
|
.deleteShopcatFor(this.getSelectedIngkey());
|
||||||
|
} else {
|
||||||
|
this.shopcatRepository.updateShopcatFor(newCat,
|
||||||
|
this.getSelectedIngkey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -182,6 +182,13 @@ public class UserSession implements Serializable {
|
||||||
// Session timeout, ms (25 minutes)
|
// Session timeout, ms (25 minutes)
|
||||||
long sessionTimeoutInterval = 25 * 60_000L;
|
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
|
* @return the sessionTimeoutInterval
|
||||||
*/
|
*/
|
||||||
|
@ -199,4 +206,8 @@ public class UserSession implements Serializable {
|
||||||
log.warn("Session Idle listener logout");
|
log.warn("Session Idle listener logout");
|
||||||
return "/main.jsf";
|
return "/main.jsf";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Recipe> getShoppingList() {
|
||||||
|
return this.shoppingList ;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,15 +2,18 @@ package com.mousetech.gourmetj.persistence.dao;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.transaction.Transactional;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Modifying;
|
||||||
import org.springframework.data.jpa.repository.Query;
|
import org.springframework.data.jpa.repository.Query;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
import com.mousetech.gourmetj.persistence.model.Shopcat;
|
import com.mousetech.gourmetj.persistence.model.Shopcat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JpaRepository for Shopping Categories, which relate ManyToOne to Ingredient.
|
* JpaRepository for Shopping Categories, which relate ManyToOne
|
||||||
* Service method is @see RecipeService
|
* to Ingredient. Service method is @see RecipeService
|
||||||
*
|
*
|
||||||
* @author timh
|
* @author timh
|
||||||
* @since Dec 28, 2021
|
* @since Dec 28, 2021
|
||||||
|
@ -28,7 +31,27 @@ public interface ShopcatRepository
|
||||||
@Query(value = SQL_FIND_CATEGORIES, nativeQuery = true)
|
@Query(value = SQL_FIND_CATEGORIES, nativeQuery = true)
|
||||||
public List<String> findDistinctCategoryNative();
|
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 Shopcat findShopcatByIngkey(String ingkey);
|
||||||
|
|
||||||
public void deleteByIngkey(String key);
|
public void deleteByIngkey(String key);
|
||||||
|
|
||||||
|
public List<Shopcat> findAllByOrderByShopcategoryAsc();
|
||||||
|
|
||||||
|
@Query(value = "SELECT s.ingkey FROM Shopcat s WHERE s.shopcategory = :shopcat ORDER BY s.ingkey")
|
||||||
|
public List<String> findByIngkeySorted(String shopcat);
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Query(value = "UPDATE Shopcat set shopcategory = :newCat WHERE ingkey IN :selectedIngkey")
|
||||||
|
@Modifying
|
||||||
|
public void updateShopcatFor(String newCat, List<String> selectedIngkey);
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
@Query(value = "DELETE Shopcat WHERE ingkey IN :selectedIngkey")
|
||||||
|
@Modifying public void deleteShopcatFor(List<String> selectedIngkey);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
package com.mousetech.gourmetj.utils;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.primefaces.model.ByteArrayContent;
|
||||||
|
import org.primefaces.model.StreamedContent;
|
||||||
|
|
||||||
|
import com.mousetech.gourmetj.ShopIngredient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a Primefaces file output content for an ingredient
|
||||||
|
* list in YAML format.
|
||||||
|
*
|
||||||
|
* @author timh
|
||||||
|
* @since Jan 15, 2022
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class YamlShoppingList {
|
||||||
|
|
||||||
|
public static StreamedContent createDownload(
|
||||||
|
List<ShopIngredient> ingredientList) {
|
||||||
|
ByteArrayOutputStream ary = new ByteArrayOutputStream();
|
||||||
|
PrintWriter wtr = new PrintWriter(ary);
|
||||||
|
wtr.println("---");
|
||||||
|
formatContent(wtr, ingredientList);
|
||||||
|
wtr.close();
|
||||||
|
byte[] bas = ary.toByteArray();
|
||||||
|
|
||||||
|
StreamedContent dlList = new ByteArrayContent(bas,
|
||||||
|
"text/text", "shopping_list.yml");
|
||||||
|
return dlList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output line items in the ingredient list with topics for
|
||||||
|
* each Shopping Category.
|
||||||
|
*
|
||||||
|
* @param wtr Output Writer
|
||||||
|
* @param ingredientList Ingredient list to output.
|
||||||
|
*/
|
||||||
|
private static void formatContent(PrintWriter wtr,
|
||||||
|
List<ShopIngredient> ingredientList) {
|
||||||
|
String oldShopcat = null;
|
||||||
|
for (ShopIngredient ing : ingredientList) {
|
||||||
|
String newShopcat = ing.getShopCat();
|
||||||
|
if (StringUtils.isBlank(newShopcat)) {
|
||||||
|
newShopcat = "Unassigned";
|
||||||
|
}
|
||||||
|
if (!StringUtils.equals(newShopcat, oldShopcat)) {
|
||||||
|
wtr.println(newShopcat + ":");
|
||||||
|
oldShopcat = newShopcat;
|
||||||
|
}
|
||||||
|
wtr.print(" - ");
|
||||||
|
String displa = ing.getDisplayAmount();
|
||||||
|
wtr.print(displa);
|
||||||
|
if (!displa.isBlank()) {
|
||||||
|
wtr.print(' ');
|
||||||
|
}
|
||||||
|
String unit = ing.getUnit();
|
||||||
|
if (StringUtils.isNotBlank(unit)) {
|
||||||
|
wtr.print(unit);
|
||||||
|
wtr.print(' ');
|
||||||
|
}
|
||||||
|
wtr.println(ing.getItem());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -21,18 +21,23 @@
|
||||||
<h1>
|
<h1>
|
||||||
<ui:insert name="title">Gourmet Recipe Manager (web version)</ui:insert>
|
<ui:insert name="title">Gourmet Recipe Manager (web version)</ui:insert>
|
||||||
</h1>
|
</h1>
|
||||||
|
<p:ajaxStatus onerror="PF('opError').show()"/>
|
||||||
<ui:insert name="content">
|
<ui:insert name="content">
|
||||||
<ui:include src="content.xhtml" />
|
<ui:include src="content.xhtml" />
|
||||||
</ui:insert>
|
</ui:insert>
|
||||||
|
<!-- -->
|
||||||
(C) 2021 Tim Holloway, Licensed under the <a
|
<div id="footer">
|
||||||
href="http://www.apache.org/licenses/LICENSE-2.0"
|
(C) 2021 Tim Holloway, Licensed under the <a
|
||||||
>Apache License, Version 2.0</a>.
|
href="http://www.apache.org/licenses/LICENSE-2.0"
|
||||||
<p>Based on Gourmet Recipe Manager by T. Hinkle</p>
|
>Apache License, Version 2.0</a>.
|
||||||
<h:form id="ftmTimeout">
|
<p>Based on Gourmet Recipe Manager by T.
|
||||||
|
Hinkle</p>
|
||||||
|
</div>
|
||||||
|
<!-- -->
|
||||||
|
<h:form id="frmTimeout">
|
||||||
<p:idleMonitor
|
<p:idleMonitor
|
||||||
timeout="#{userSession.sessionTimeoutInterval}"
|
timeout="#{userSession.sessionTimeoutInterval}"
|
||||||
onidle="PF('sessionExpiredConfirmation').show()"
|
onidle="PF('dlgSessionExpired').show()"
|
||||||
>
|
>
|
||||||
<p:ajax event="idle"
|
<p:ajax event="idle"
|
||||||
listener="#{userSession.sessionIdleListener}"
|
listener="#{userSession.sessionIdleListener}"
|
||||||
|
@ -43,12 +48,12 @@
|
||||||
message="Your session has expired."
|
message="Your session has expired."
|
||||||
header="#{msgs['confirmDialog.initiatingDestroyProcess.label']}"
|
header="#{msgs['confirmDialog.initiatingDestroyProcess.label']}"
|
||||||
severity="alert"
|
severity="alert"
|
||||||
widgetVar="sessionExpiredConfirmation"
|
widgetVar="dlgSessionExpired"
|
||||||
style="z-index: 25000"
|
style="z-index: 25000"
|
||||||
>
|
>
|
||||||
<p:commandButton id="cmdExpiredOK"
|
<p:commandButton id="cmdExpiredOK"
|
||||||
value="OK" action="/main.jsf"
|
value="OK" action="/main.jsf"
|
||||||
oncomplete="PF('sessionExpiredConfirmation').hide()"
|
oncomplete="PF('dlgSessionExpired').hide()"
|
||||||
/>
|
/>
|
||||||
</p:confirmDialog>
|
</p:confirmDialog>
|
||||||
</h:form>
|
</h:form>
|
||||||
|
@ -60,7 +65,7 @@
|
||||||
severity="alert" widgetVar="opError"
|
severity="alert" widgetVar="opError"
|
||||||
>
|
>
|
||||||
<p:commandButton value="OK"
|
<p:commandButton value="OK"
|
||||||
oncomplete="PF('cd').hide()"
|
oncomplete="PF('opError').hide()"
|
||||||
/>
|
/>
|
||||||
</p:confirmDialog>
|
</p:confirmDialog>
|
||||||
</h:form>
|
</h:form>
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html 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"
|
||||||
|
>
|
||||||
|
<!-- === Edit ingkey/shopcat === -->
|
||||||
|
<h:form id="frmIsk">
|
||||||
|
<p:panelGrid>
|
||||||
|
<p:row>
|
||||||
|
<p:column>
|
||||||
|
<p:outputLabel for="ctlScSel"
|
||||||
|
value="Shopping Category"
|
||||||
|
/>
|
||||||
|
</p:column>
|
||||||
|
<p:column>
|
||||||
|
<p:outputLabel for="ctlIngkeySel"
|
||||||
|
value="Ingredient Key"
|
||||||
|
/>
|
||||||
|
</p:column>
|
||||||
|
</p:row>
|
||||||
|
<p:row style="vertical-align: top">
|
||||||
|
<p:column>
|
||||||
|
<p:selectOneListbox id="ctlScSel"
|
||||||
|
style="width: 240px"
|
||||||
|
value="#{shoppingListBean.selectedShopcat}"
|
||||||
|
>
|
||||||
|
<f:selectItems
|
||||||
|
value="#{shoppingListBean.shopcatList}"
|
||||||
|
/>
|
||||||
|
<p:ajax update="ctlIngkeySel" event="change" />
|
||||||
|
</p:selectOneListbox>
|
||||||
|
</p:column>
|
||||||
|
<p:column>
|
||||||
|
<h:selectManyListbox id="ctlIngkeySel"
|
||||||
|
style="width: 240px"
|
||||||
|
value="#{shoppingListBean.selectedIngkey}"
|
||||||
|
label="Ingcat"
|
||||||
|
>
|
||||||
|
<f:selectItems
|
||||||
|
value="#{shoppingListBean.ingkeyList}"
|
||||||
|
/>
|
||||||
|
<p:ajax event="change" update="ctlChangeCat"/>
|
||||||
|
</h:selectManyListbox>
|
||||||
|
</p:column>
|
||||||
|
</p:row>
|
||||||
|
<p:row>
|
||||||
|
<p:column>
|
||||||
|
<p:outputLabel
|
||||||
|
value="Change shopping category to:"
|
||||||
|
/>
|
||||||
|
</p:column>
|
||||||
|
<p:column>
|
||||||
|
<p:autoComplete
|
||||||
|
value="#{shoppingListBean.newShopcat}"
|
||||||
|
autoSelection="false" forceSelection="false"
|
||||||
|
maxResults="12"
|
||||||
|
completeMethod="#{shoppingListBean.suggestShopcat}"
|
||||||
|
/>
|
||||||
|
</p:column>
|
||||||
|
</p:row>
|
||||||
|
<p:row>
|
||||||
|
<p:column>
|
||||||
|
<p:commandButton id="ctlChangeCat" value="Change..."
|
||||||
|
disabled="#{empty shoppingListBean.selectedIngkey}"
|
||||||
|
onclick="PF('dlgOkRecat').show()"
|
||||||
|
/>
|
||||||
|
</p:column>
|
||||||
|
<p:column>
|
||||||
|
<h:outputText value="" />
|
||||||
|
</p:column>
|
||||||
|
</p:row>
|
||||||
|
</p:panelGrid>
|
||||||
|
</h:form>
|
||||||
|
<!-- -->
|
||||||
|
<h:form id="frmDelete">
|
||||||
|
<p:confirmDialog closable="false" id="dlgOkRecat"
|
||||||
|
header="Confirm Change - CANNOT UNDO"
|
||||||
|
message="OK to CHANGE Shopping Category for these Ingredient Keys?"
|
||||||
|
severity="alert" widgetVar="dlgOkRecat"
|
||||||
|
style="z-index: 25000"
|
||||||
|
>
|
||||||
|
<p:commandButton id="dlgOK" value="OK"
|
||||||
|
oncomplete="PF('dlgOkRecat').hide()"
|
||||||
|
action="#{shoppingListBean.doChangeShopcat}"
|
||||||
|
update="@form:@parent:frmIsk:ctlScSel @form:@parent:frmIsk:ctlIngkeySel"
|
||||||
|
immediate="true"
|
||||||
|
/>
|
||||||
|
<p:commandButton id="dlgCancel" value="Cancel"
|
||||||
|
onclick="PF('dlgOkRecat').hide()"
|
||||||
|
/>
|
||||||
|
</p:confirmDialog>
|
||||||
|
</h:form>
|
||||||
|
</html>
|
|
@ -13,7 +13,16 @@
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.greenButton {
|
||||||
|
background-color: green !important;
|
||||||
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
font-family: 'latoregular', 'Trebuchet MS,Arial,Helvetica,sans-serif';
|
font-family: 'latoregular', 'Trebuchet MS,Arial,Helvetica,sans-serif';
|
||||||
font-size: 1em
|
font-size: 1em
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer {
|
||||||
|
bottom: 90px;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
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>
|
|
@ -33,7 +33,9 @@
|
||||||
<f:selectItems
|
<f:selectItems
|
||||||
value="#{userSession.searchTypeList}"
|
value="#{userSession.searchTypeList}"
|
||||||
/>
|
/>
|
||||||
<p:ajax listener="#{adminMainBean.resetSuggestions}"/>
|
<p:ajax
|
||||||
|
listener="#{adminMainBean.resetSuggestions}"
|
||||||
|
/>
|
||||||
</p:selectOneMenu>
|
</p:selectOneMenu>
|
||||||
<p:commandButton id="ctlClear" value="Clear"
|
<p:commandButton id="ctlClear" value="Clear"
|
||||||
icon="ui-icon-close"
|
icon="ui-icon-close"
|
||||||
|
@ -43,6 +45,16 @@
|
||||||
<p:commandButton value="New Recipe"
|
<p:commandButton value="New Recipe"
|
||||||
action="#{adminMainBean.doNewRecipe}"
|
action="#{adminMainBean.doNewRecipe}"
|
||||||
/>
|
/>
|
||||||
|
<p:commandButton value="More..."
|
||||||
|
action="#{adminMainBean.doMore}"
|
||||||
|
/>
|
||||||
|
<h:outputText id="slistSSize"
|
||||||
|
style="margin-left: 2em"
|
||||||
|
value="#{userSession.shoppingList.size()}"
|
||||||
|
/>
|
||||||
|
<h:outputLabel for="slistSize"
|
||||||
|
value=" Recipes in Shopping List"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</h:form>
|
</h:form>
|
||||||
<h:form id="form2">
|
<h:form id="form2">
|
||||||
|
|
|
@ -57,7 +57,16 @@
|
||||||
styleClass="ui-button-print"
|
styleClass="ui-button-print"
|
||||||
immediate="true"
|
immediate="true"
|
||||||
/>
|
/>
|
||||||
<p:outputLabel for="@next"
|
<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:"
|
value="Categories:"
|
||||||
/>
|
/>
|
||||||
<h:outputText
|
<h:outputText
|
||||||
|
@ -197,8 +206,7 @@
|
||||||
<p:confirmDialog closable="false" id="okDeleteDlg"
|
<p:confirmDialog closable="false" id="okDeleteDlg"
|
||||||
header="Confirm Deletion"
|
header="Confirm Deletion"
|
||||||
message="OK to delete this recipe?"
|
message="OK to delete this recipe?"
|
||||||
severity="alert"
|
severity="alert" widgetVar="okDeleteDlg"
|
||||||
widgetVar="okDeleteDlg"
|
|
||||||
style="z-index: 25000"
|
style="z-index: 25000"
|
||||||
>
|
>
|
||||||
<p:commandButton id="dlgOK" value="OK"
|
<p:commandButton id="dlgOK" value="OK"
|
||||||
|
|
141
src/main/resources/META-INF/resources/shoppingList.xhtml
Normal file
141
src/main/resources/META-INF/resources/shoppingList.xhtml
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
<?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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.noRecipe {
|
||||||
|
text-decoration: line-through;
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plusRecipe {
|
||||||
|
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<h:messages />
|
||||||
|
<p:tabView id="tabGroupClient" orientation="left"
|
||||||
|
dynamic="true"
|
||||||
|
>
|
||||||
|
<p:tab id="overviewTab" title="Shopping List">
|
||||||
|
<h:form id="form1">
|
||||||
|
<p:dataTable id="tblRecipes"
|
||||||
|
style="width: 600px"
|
||||||
|
value="#{shoppingListBean.recipeList}"
|
||||||
|
var="item"
|
||||||
|
>
|
||||||
|
<f:facet name="header">
|
||||||
|
<h:outputText value="Recipes" />
|
||||||
|
</f:facet>
|
||||||
|
<p:column style="width: 4em">
|
||||||
|
<p:spinner required="true" min="0"
|
||||||
|
value="#{item.count}" size="1"
|
||||||
|
>
|
||||||
|
<p:ajax
|
||||||
|
listener="#{shoppingListBean.pfAmountChange}"
|
||||||
|
update="@form:tblShopIngredients rname"
|
||||||
|
/>
|
||||||
|
</p:spinner>
|
||||||
|
</p:column>
|
||||||
|
<p:column>
|
||||||
|
<h:outputText id="rname"
|
||||||
|
styleClass="#{(item.count eq 0) ? 'noRecipe' :'plusRecipe' }"
|
||||||
|
value="#{item.recipe.title}"
|
||||||
|
/>
|
||||||
|
</p:column>
|
||||||
|
</p:dataTable>
|
||||||
|
<!-- ====== Ingredients To Buy ======================= -->
|
||||||
|
<p:column id="dlIng">
|
||||||
|
<p:commandButton value="Download List" ajax="false">
|
||||||
|
<p:fileDownload
|
||||||
|
value="#{shoppingListBean.dlIngredientList}"
|
||||||
|
/>
|
||||||
|
</p:commandButton>
|
||||||
|
</p:column>
|
||||||
|
<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="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>
|
||||||
|
</h:form>
|
||||||
|
</p:tab>
|
||||||
|
<!-- -->
|
||||||
|
<p:tab id="ingshopcatEditTab"
|
||||||
|
title="Edit Shopping Categories"
|
||||||
|
>
|
||||||
|
<ui:include
|
||||||
|
src="/WEB-INF/layout/misctabs/ingshopkey.xhtml"
|
||||||
|
/>
|
||||||
|
</p:tab>
|
||||||
|
<!-- -->
|
||||||
|
<p:tab id="tabImportExport" title="Import/Export">
|
||||||
|
<h:outputText value="For future implementation" />
|
||||||
|
</p:tab>
|
||||||
|
</p:tabView>
|
||||||
|
<h:form id="frmHome">
|
||||||
|
<p:commandButton id="doHome" value="Home"
|
||||||
|
icon="ui-icon-home" ajax="false" immediate="true"
|
||||||
|
action="main.jsf"
|
||||||
|
/>
|
||||||
|
</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