grocery-app/frontend/src/components/manage/ManageStores.jsx
2026-05-31 20:56:45 -07:00

191 lines
6.8 KiB
JavaScript

import { useContext, useMemo, useState } from "react";
import { createHouseholdStore } from "../../api/stores";
import { HouseholdContext } from "../../context/HouseholdContext";
import { StoreContext } from "../../context/StoreContext";
import useActionToast from "../../hooks/useActionToast";
import getApiErrorMessage from "../../lib/getApiErrorMessage";
import StoreAvailableItemsManager from "./StoreAvailableItemsManager";
import StoreLocationManager from "./StoreLocationManager";
import StoreZoneManager from "./StoreZoneManager";
import "../../styles/components/manage/StoreAvailableItemsManager.css";
import "../../styles/components/manage/ManageStores.css";
function groupLocationsByStore(locations) {
const grouped = new Map();
for (const location of locations) {
const key = location.household_store_id;
if (!grouped.has(key)) {
grouped.set(key, {
household_store_id: location.household_store_id,
name: location.name,
locations: [],
});
}
grouped.get(key).locations.push(location);
}
return Array.from(grouped.values()).sort((a, b) => a.name.localeCompare(b.name));
}
function locationLabel(location) {
return location.display_name || location.name;
}
export default function ManageStores() {
const { activeHousehold } = useContext(HouseholdContext);
const {
activeStore,
stores: householdStores,
refreshStores,
refreshZones,
} = useContext(StoreContext);
const toast = useActionToast();
const [createForm, setCreateForm] = useState({
name: "",
location_name: "",
address: "",
});
const [saving, setSaving] = useState(false);
const isAdmin = ["owner", "admin"].includes(activeHousehold?.role);
const groupedStores = useMemo(
() => groupLocationsByStore(householdStores),
[householdStores]
);
const refreshAfterStoreChange = async () => {
await refreshStores();
await refreshZones();
};
const handleCreateStore = async (event) => {
event.preventDefault();
if (!createForm.name.trim()) return;
setSaving(true);
try {
await createHouseholdStore(activeHousehold.id, {
name: createForm.name.trim(),
location_name: createForm.location_name.trim() || "Default Location",
address: createForm.address.trim() || null,
});
setCreateForm({ name: "", location_name: "", address: "" });
await refreshAfterStoreChange();
toast.success("Created store", `Created store ${createForm.name.trim()}`);
} catch (error) {
const message = getApiErrorMessage(error, "Failed to create store");
toast.error("Create store failed", `Create store failed: ${message}`);
} finally {
setSaving(false);
}
};
return (
<div className="manage-stores">
<section className="manage-section">
<h2>Store Locations ({householdStores.length})</h2>
<p className="manage-stores-help">
Stores are private to this household. Locations define map-specific zones, item
placement, and shopping order.
</p>
{householdStores.length === 0 ? (
<p className="empty-message">No store locations added yet.</p>
) : (
<div className="stores-list">
{groupedStores.map((storeGroup) => (
<div key={storeGroup.household_store_id} className="store-card">
<div className="store-card-header">
<div className="store-info">
<h3>{storeGroup.name}</h3>
</div>
<StoreLocationManager
householdId={activeHousehold.id}
storeGroup={storeGroup}
allLocationCount={householdStores.length}
canManage={isAdmin}
refreshAfterStoreChange={refreshAfterStoreChange}
/>
</div>
<div className="store-location-list">
{storeGroup.locations.map((location) => {
const label = locationLabel(location);
const showLocationName =
storeGroup.locations.length > 1 || label !== storeGroup.name;
return (
<div key={location.id} className="store-location-row">
<div className="store-info store-location-copy">
{showLocationName ? <strong>{label}</strong> : null}
{location.address ? (
<p className="store-location">{location.address}</p>
) : null}
{location.is_default ? (
<p className="store-location">Default location</p>
) : null}
</div>
<div className="store-location-controls">
<StoreZoneManager
householdId={activeHousehold.id}
location={location}
canManage={isAdmin}
refreshActiveZones={refreshZones}
/>
<StoreAvailableItemsManager
householdId={activeHousehold.id}
store={location}
isAdmin={isAdmin}
/>
</div>
</div>
);
})}
</div>
</div>
))}
</div>
)}
</section>
{isAdmin ? (
<section className="manage-section">
<h2>Add Store</h2>
<form className="add-store-panel" onSubmit={handleCreateStore}>
<input
value={createForm.name}
onChange={(event) => setCreateForm((current) => ({ ...current, name: event.target.value }))}
placeholder="Store name, e.g. Costco"
required
/>
<input
value={createForm.location_name}
onChange={(event) =>
setCreateForm((current) => ({ ...current, location_name: event.target.value }))
}
placeholder="Location name, e.g. Fontana"
/>
<input
value={createForm.address}
onChange={(event) => setCreateForm((current) => ({ ...current, address: event.target.value }))}
placeholder="Address or notes"
/>
<button type="submit" className="btn-primary" disabled={saving}>
{saving ? "Adding..." : "+ Add Store"}
</button>
</form>
</section>
) : activeStore ? (
<p className="manage-stores-note">
Household members can manage item defaults. Only owners and admins can manage stores,
locations, zones, and item deletion.
</p>
) : null}
</div>
);
}