diff --git a/frontend/src/api/availableItems.js b/frontend/src/api/availableItems.js index bac18d9..6eb63bd 100644 --- a/frontend/src/api/availableItems.js +++ b/frontend/src/api/availableItems.js @@ -9,7 +9,7 @@ function appendClassification(formData, classification) { } export const getAvailableItems = (householdId, storeId, query = "") => - api.get(`/households/${householdId}/stores/${storeId}/available-items`, { + api.get(`/households/${householdId}/locations/${storeId}/available-items`, { params: query ? { query } : undefined, }); @@ -21,7 +21,7 @@ export const createAvailableItem = (householdId, storeId, payload) => { formData.append("image", payload.imageFile); } - return api.post(`/households/${householdId}/stores/${storeId}/available-items`, formData, { + return api.post(`/households/${householdId}/locations/${storeId}/available-items`, formData, { headers: { "Content-Type": "multipart/form-data", }, @@ -41,7 +41,7 @@ export const updateAvailableItem = (householdId, storeId, itemId, payload) => { formData.append("image", payload.imageFile); } - return api.patch(`/households/${householdId}/stores/${storeId}/available-items/${itemId}`, formData, { + return api.patch(`/households/${householdId}/locations/${storeId}/available-items/${itemId}`, formData, { headers: { "Content-Type": "multipart/form-data", }, @@ -49,7 +49,7 @@ export const updateAvailableItem = (householdId, storeId, itemId, payload) => { }; export const deleteAvailableItem = (householdId, storeId, itemId) => - api.delete(`/households/${householdId}/stores/${storeId}/available-items/${itemId}`); + api.delete(`/households/${householdId}/locations/${storeId}/available-items/${itemId}`); export const importCurrentAvailableItems = (householdId, storeId) => - api.post(`/households/${householdId}/stores/${storeId}/available-items/import-current`); + api.post(`/households/${householdId}/locations/${storeId}/available-items/import-current`); diff --git a/frontend/src/api/list.js b/frontend/src/api/list.js index 259d402..66f79c7 100644 --- a/frontend/src/api/list.js +++ b/frontend/src/api/list.js @@ -3,14 +3,14 @@ import api from "./axios"; /** * Get grocery list for household and store */ -export const getList = (householdId, storeId) => - api.get(`/households/${householdId}/stores/${storeId}/list`); +export const getList = (householdId, storeId) => + api.get(`/households/${householdId}/locations/${storeId}/list`); /** * Get specific item by name */ -export const getItemByName = (householdId, storeId, itemName) => - api.get(`/households/${householdId}/stores/${storeId}/list/item`, { +export const getItemByName = (householdId, storeId, itemName) => + api.get(`/households/${householdId}/locations/${storeId}/list/item`, { params: { item_name: itemName } }); @@ -39,7 +39,7 @@ export const addItem = ( formData.append("image", imageFile); } - return api.post(`/households/${householdId}/stores/${storeId}/list/add`, formData, { + return api.post(`/households/${householdId}/locations/${storeId}/list/add`, formData, { headers: { "Content-Type": "multipart/form-data", }, @@ -49,8 +49,8 @@ export const addItem = ( /** * Get item classification */ -export const getClassification = (householdId, storeId, itemName) => - api.get(`/households/${householdId}/stores/${storeId}/list/classification`, { +export const getClassification = (householdId, storeId, itemName) => + api.get(`/households/${householdId}/locations/${storeId}/list/classification`, { params: { item_name: itemName } }); @@ -58,7 +58,7 @@ export const getClassification = (householdId, storeId, itemName) => * Set item classification */ export const setClassification = (householdId, storeId, itemName, classification) => - api.post(`/households/${householdId}/stores/${storeId}/list/classification`, { + api.post(`/households/${householdId}/locations/${storeId}/list/classification`, { item_name: itemName, classification }); @@ -103,8 +103,8 @@ export const updateItemWithClassification = (householdId, storeId, itemName, qua /** * Update item details (quantity, notes) */ -export const updateItem = (householdId, storeId, itemName, quantity, notes) => - api.put(`/households/${householdId}/stores/${storeId}/list/item`, { +export const updateItem = (householdId, storeId, itemName, quantity, notes) => + api.put(`/households/${householdId}/locations/${storeId}/list/item`, { item_name: itemName, quantity, notes @@ -113,8 +113,8 @@ export const updateItem = (householdId, storeId, itemName, quantity, notes) => /** * Mark item as bought or unbought */ -export const markBought = (householdId, storeId, itemName, quantityBought = null, bought = true) => - api.patch(`/households/${householdId}/stores/${storeId}/list/item`, { +export const markBought = (householdId, storeId, itemName, quantityBought = null, bought = true) => + api.patch(`/households/${householdId}/locations/${storeId}/list/item`, { item_name: itemName, bought, quantity_bought: quantityBought @@ -123,24 +123,24 @@ export const markBought = (householdId, storeId, itemName, quantityBought = null /** * Delete item from list */ -export const deleteItem = (householdId, storeId, itemName) => - api.delete(`/households/${householdId}/stores/${storeId}/list/item`, { +export const deleteItem = (householdId, storeId, itemName) => + api.delete(`/households/${householdId}/locations/${storeId}/list/item`, { data: { item_name: itemName } }); /** * Get suggestions based on query */ -export const getSuggestions = (householdId, storeId, query) => - api.get(`/households/${householdId}/stores/${storeId}/list/suggestions`, { +export const getSuggestions = (householdId, storeId, query) => + api.get(`/households/${householdId}/locations/${storeId}/list/suggestions`, { params: { query } }); /** * Get recently bought items */ -export const getRecentlyBought = (householdId, storeId) => - api.get(`/households/${householdId}/stores/${storeId}/list/recent`); +export const getRecentlyBought = (householdId, storeId) => + api.get(`/households/${householdId}/locations/${storeId}/list/recent`); /** * Update item image @@ -158,7 +158,7 @@ export const updateItemImage = ( formData.append("quantity", quantity); formData.append("image", imageFile); - return api.post(`/households/${householdId}/stores/${storeId}/list/update-image`, formData, { + return api.post(`/households/${householdId}/locations/${storeId}/list/update-image`, formData, { headers: { "Content-Type": "multipart/form-data", }, diff --git a/frontend/src/api/stores.js b/frontend/src/api/stores.js index 2b92f18..61da518 100644 --- a/frontend/src/api/stores.js +++ b/frontend/src/api/stores.js @@ -1,48 +1,55 @@ import api from "./axios"; -/** - * Get all stores in the system - */ +// Legacy global store catalog for the system-admin page. export const getAllStores = () => api.get("/stores"); - -/** - * Get stores linked to a household - */ -export const getHouseholdStores = (householdId) => - api.get(`/stores/household/${householdId}`); - -/** - * Add a store to a household - */ -export const addStoreToHousehold = (householdId, storeId, isDefault = false) => - api.post(`/stores/household/${householdId}`, { storeId: storeId, isDefault: isDefault }); - -/** - * Remove a store from a household - */ -export const removeStoreFromHousehold = (householdId, storeId) => - api.delete(`/stores/household/${householdId}/${storeId}`); - -/** - * Set a store as default for a household - */ -export const setDefaultStore = (householdId, storeId) => - api.patch(`/stores/household/${householdId}/${storeId}/default`); - -/** - * Create a new store (system admin only) - */ -export const createStore = (name, location) => - api.post("/stores", { name, location }); - -/** - * Update store details (system admin only) - */ -export const updateStore = (storeId, name, location) => - api.patch(`/stores/${storeId}`, { name, location }); - -/** - * Delete a store (system admin only) - */ +export const createStore = (name, default_zones) => + api.post("/stores", { name, default_zones }); +export const updateStore = (storeId, name, default_zones) => + api.patch(`/stores/${storeId}`, { name, default_zones }); export const deleteStore = (storeId) => api.delete(`/stores/${storeId}`); + +// Household-owned store locations used by the grocery flow. +export const getHouseholdStores = (householdId) => + api.get(`/households/${householdId}/stores`); + +export const createHouseholdStore = (householdId, payload) => + api.post(`/households/${householdId}/stores`, payload); + +export const updateHouseholdStore = (householdId, householdStoreId, payload) => + api.patch(`/households/${householdId}/stores/${householdStoreId}`, payload); + +export const deleteHouseholdStore = (householdId, householdStoreId) => + api.delete(`/households/${householdId}/stores/${householdStoreId}`); + +export const addLocationToStore = (householdId, householdStoreId, payload) => + api.post(`/households/${householdId}/stores/${householdStoreId}/locations`, payload); + +export const updateLocation = (householdId, locationId, payload) => + api.patch(`/households/${householdId}/locations/${locationId}`, payload); + +export const removeLocation = (householdId, locationId) => + api.delete(`/households/${householdId}/locations/${locationId}`); + +export const setDefaultLocation = (householdId, locationId) => + api.patch(`/households/${householdId}/locations/${locationId}/default`); + +export const getLocationZones = (householdId, locationId) => + api.get(`/households/${householdId}/locations/${locationId}/zones`); + +export const createLocationZone = (householdId, locationId, payload) => + api.post(`/households/${householdId}/locations/${locationId}/zones`, payload); + +export const updateLocationZone = (householdId, locationId, zoneId, payload) => + api.patch(`/households/${householdId}/locations/${locationId}/zones/${zoneId}`, payload); + +export const deleteLocationZone = (householdId, locationId, zoneId) => + api.delete(`/households/${householdId}/locations/${locationId}/zones/${zoneId}`); + +// Compatibility aliases for older callers. +export const addStoreToHousehold = (householdId, storeId, isDefault = false) => + api.post(`/stores/household/${householdId}`, { storeId, isDefault }); +export const removeStoreFromHousehold = (householdId, storeId) => + api.delete(`/stores/household/${householdId}/${storeId}`); +export const setDefaultStore = (householdId, storeId) => + api.patch(`/stores/household/${householdId}/${storeId}/default`); diff --git a/frontend/src/components/common/ListSearchInput.jsx b/frontend/src/components/common/ListSearchInput.jsx new file mode 100644 index 0000000..c3fc6db --- /dev/null +++ b/frontend/src/components/common/ListSearchInput.jsx @@ -0,0 +1,37 @@ +export default function ListSearchInput({ value, onChange, resultCount, totalCount }) { + const hasSearch = value.trim().length > 0; + + return ( +
+ {resultCount} of {totalCount} item{totalCount === 1 ? "" : "s"} +
+ )} +Household
- Keep the household name crisp and easy to recognize across invites and shared lists. -
Entry Rules
- Decide how new people can enter, review manual approvals, then create invite links for the flow you want. -
{inviteError}
} @@ -547,9 +549,6 @@ export default function ManageHousehold() {People
- Role badges and compact actions make it easier to see who runs the household and who just shops. -
Final Actions
- {isMemberOnly - ? "Leaving removes your access to this household." - : "Deleting a household is permanent and will delete all lists, items, and history."} -
Loading zones...
+ ) : zones.length === 0 ? ( +No zones for this location.
+ ) : ( +- Use each store card's Manage Items button to edit or delete the household/store item list. + Stores and locations are private to this household. Each location has its own zones, + item defaults, and shopping order.
- {!isAdmin && ( -- Only household owners and admins can manage store item catalogs. -
- )} {householdStores.length === 0 ? ( -No stores added yet.
+No store locations added yet.
) : ({store.location}
} +{location.address}
+ ) : null} + {location.is_default ? ( +Default shopping location
+ ) : null} ++ Household members can manage item defaults. Only owners and admins can manage stores, + locations, zones, and item deletion. +
+ ) : null}Manage the household/store items used for suggestions and store defaults.
+Manage location-specific items used for suggestions and defaults.
Add an image and classification to help organize your list
+Loading households...
@@ -785,7 +766,6 @@ export default function GroceryList() { return (+
Loading stores...
This household doesnβt have any stores yet. @@ -837,8 +815,7 @@ export default function GroceryList() { return (
Loading stores...
@@ -851,8 +828,7 @@ export default function GroceryList() { return (Loading grocery list...
+ {isListSearchActive + ? `No list items match "${listSearchQuery.trim()}".` + : "No items in this store yet."} +
+ ) : ( (() => { const grouped = groupItemsByZone(sortedItems); return Object.keys(grouped).map(zone => { - const isCollapsed = collapsedZones[zone]; + const isCollapsed = isListSearchActive ? false : collapsedZones[zone]; const itemCount = grouped[zone].length; return (- Your preferred sorting method when opening the list -
-