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() {
|
||||
suggestionList = null;
|
||||
}
|
||||
|
||||
|
||||
public List<String> searchSuggestionList(String query) {
|
||||
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);
|
||||
|
@ -119,7 +117,7 @@ public class AdminMainBean implements Serializable {
|
|||
}
|
||||
return suggestionList;
|
||||
}
|
||||
|
||||
|
||||
/**/
|
||||
transient DataModel<Recipe> searchResults;
|
||||
|
||||
|
@ -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.
|
||||
*
|
||||
|
|
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)
|
||||
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,15 +2,18 @@ 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;
|
||||
|
||||
import com.mousetech.gourmetj.persistence.model.Shopcat;
|
||||
|
||||
/**
|
||||
* JpaRepository for Shopping Categories, which relate ManyToOne to Ingredient.
|
||||
* Service method is @see RecipeService
|
||||
* JpaRepository for Shopping Categories, which relate ManyToOne
|
||||
* to Ingredient. Service method is @see RecipeService
|
||||
*
|
||||
* @author timh
|
||||
* @since Dec 28, 2021
|
||||
|
@ -28,7 +31,27 @@ 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();
|
||||
|
||||
@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>
|
||||
<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>
|
||||
|
||||
(C) 2021 Tim Holloway, Licensed under the <a
|
||||
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">
|
||||
<!-- -->
|
||||
<div id="footer">
|
||||
(C) 2021 Tim Holloway, Licensed under the <a
|
||||
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>
|
||||
</div>
|
||||
<!-- -->
|
||||
<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 +48,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 +65,7 @@
|
|||
severity="alert" widgetVar="opError"
|
||||
>
|
||||
<p:commandButton value="OK"
|
||||
oncomplete="PF('cd').hide()"
|
||||
oncomplete="PF('opError').hide()"
|
||||
/>
|
||||
</p:confirmDialog>
|
||||
</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;
|
||||
}
|
||||
|
||||
.greenButton {
|
||||
background-color: green !important;
|
||||
}
|
||||
|
||||
textarea {
|
||||
font-family: 'latoregular', 'Trebuchet MS,Arial,Helvetica,sans-serif';
|
||||
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
|
||||
value="#{userSession.searchTypeList}"
|
||||
/>
|
||||
<p:ajax listener="#{adminMainBean.resetSuggestions}"/>
|
||||
<p:ajax
|
||||
listener="#{adminMainBean.resetSuggestions}"
|
||||
/>
|
||||
</p:selectOneMenu>
|
||||
<p:commandButton id="ctlClear" value="Clear"
|
||||
icon="ui-icon-close"
|
||||
|
@ -43,6 +45,16 @@
|
|||
<p:commandButton value="New Recipe"
|
||||
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>
|
||||
</h:form>
|
||||
<h:form id="form2">
|
||||
|
|
|
@ -57,7 +57,16 @@
|
|||
styleClass="ui-button-print"
|
||||
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:"
|
||||
/>
|
||||
<h:outputText
|
||||
|
@ -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"
|
||||
|
|
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