Editable shopping categories

This commit is contained in:
Tim Holloway 2022-01-13 18:46:28 -05:00
parent 349fec17ac
commit 3fd7bdb842
14 changed files with 896 additions and 17 deletions

121
recipes.sql Normal file
View 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"));

View File

@ -106,12 +106,10 @@ public class AdminMainBean implements Serializable {
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);
@ -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
* *

View File

@ -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.
* *

View 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();
}
}

View 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;
}
}

View File

@ -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 ;
}
} }

View File

@ -2,7 +2,10 @@ 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;
@ -28,7 +31,14 @@ 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();
} }

View File

@ -21,6 +21,7 @@
<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>
@ -29,10 +30,10 @@
href="http://www.apache.org/licenses/LICENSE-2.0" href="http://www.apache.org/licenses/LICENSE-2.0"
>Apache License, Version 2.0</a>. >Apache License, Version 2.0</a>.
<p>Based on Gourmet Recipe Manager by T. Hinkle</p> <p>Based on Gourmet Recipe Manager by T. Hinkle</p>
<h:form id="ftmTimeout"> <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 +44,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 +61,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>

View File

@ -13,6 +13,10 @@
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

View 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>

View File

@ -43,6 +43,9 @@
<p:commandButton value="New Recipe" <p:commandButton value="New Recipe"
action="#{adminMainBean.doNewRecipe}" action="#{adminMainBean.doNewRecipe}"
/> />
<p:commandButton value="More..."
action="#{adminMainBean.doMore}"
/>
</div> </div>
</h:form> </h:form>
<h:form id="form2"> <h:form id="form2">

View File

@ -57,6 +57,15 @@
styleClass="ui-button-print" styleClass="ui-button-print"
immediate="true" 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" <p:outputLabel for="@next"
value="Categories:" value="Categories:"
/> />
@ -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"

View 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>

View File

@ -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());
}
}