Group bins by letter, sort by number, drop location
Build and push image / build (push) Successful in 46s
Build and push image / build (push) Successful in 46s
Bins follow an A1/A2/B1 naming convention, so the Bins page now parses the leading letter prefix as a row group and the trailing number as the within-row order. Each letter starts a fresh grid section; bins whose names don't match the pattern fall into a trailing "Other" bucket sorted alphabetically. Removes the optional location field from bins end to end: the API client signatures, server POST/PATCH routes, both product-flow inline creates, the dropdown labels, the ProductDetail bin row, and the BinsView header line. The bootstrap query explicitly projects only id/name/capacity so the dead column doesn't leak through. The location column stays in the bins table on disk to avoid a migration on existing deployments — it just isn't read or written. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -34,7 +34,6 @@ export function AddProductFlow({ data, onClose }: { data: Bootstrap; onClose: ()
|
||||
const [newShopName, setNewShopName] = useState("");
|
||||
const [newShopLocation, setNewShopLocation] = useState("");
|
||||
const [newBinName, setNewBinName] = useState("");
|
||||
const [newBinLocation, setNewBinLocation] = useState("");
|
||||
const [newBinCapacity, setNewBinCapacity] = useState(10);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
@@ -98,7 +97,6 @@ export function AddProductFlow({ data, onClose }: { data: Bootstrap; onClose: ()
|
||||
if (!newBinName.trim()) throw new Error("New bin name required");
|
||||
const b = await api.createBin({
|
||||
name: newBinName.trim(),
|
||||
location: newBinLocation.trim(),
|
||||
capacity: newBinCapacity,
|
||||
});
|
||||
binId = b.id;
|
||||
@@ -200,7 +198,7 @@ export function AddProductFlow({ data, onClose }: { data: Bootstrap; onClose: ()
|
||||
<Field label="Bin">
|
||||
<Select value={form.binId} onChange={(e) => update("binId", e.target.value)}>
|
||||
{data.bins.map((b) => (
|
||||
<option key={b.id} value={b.id}>{b.name} — {b.location}</option>
|
||||
<option key={b.id} value={b.id}>{b.name}</option>
|
||||
))}
|
||||
<option value={NEW_BIN}>+ Add new bin…</option>
|
||||
</Select>
|
||||
@@ -211,14 +209,7 @@ export function AddProductFlow({ data, onClose }: { data: Bootstrap; onClose: ()
|
||||
<Input
|
||||
value={newBinName}
|
||||
onChange={(e) => setNewBinName(e.target.value)}
|
||||
placeholder="e.g. Top Drawer"
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Location (optional)">
|
||||
<Input
|
||||
value={newBinLocation}
|
||||
onChange={(e) => setNewBinLocation(e.target.value)}
|
||||
placeholder="e.g. Bedroom"
|
||||
placeholder="e.g. A1"
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Capacity">
|
||||
|
||||
@@ -125,16 +125,14 @@ export function EditBinModal({
|
||||
bin,
|
||||
onClose,
|
||||
}: {
|
||||
bin: { id: string; name: string; location: string | null; capacity: number };
|
||||
bin: { id: string; name: string; capacity: number };
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const qc = useQueryClient();
|
||||
const [name, setName] = useState(bin.name);
|
||||
const [location, setLocation] = useState(bin.location ?? "");
|
||||
const [capacity, setCapacity] = useState(bin.capacity);
|
||||
const update = useMutation({
|
||||
mutationFn: () =>
|
||||
api.updateBin(bin.id, { name: name.trim(), location: location.trim(), capacity }),
|
||||
mutationFn: () => api.updateBin(bin.id, { name: name.trim(), capacity }),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ["bootstrap"] });
|
||||
onClose();
|
||||
@@ -145,7 +143,7 @@ export function EditBinModal({
|
||||
<ModalBackdrop onClose={onClose}>
|
||||
<div
|
||||
style={{
|
||||
width: "min(560px, 96vw)",
|
||||
width: "min(480px, 96vw)",
|
||||
margin: "40px 20px",
|
||||
background: "var(--bg)",
|
||||
border: "1px solid var(--line)",
|
||||
@@ -154,33 +152,24 @@ export function EditBinModal({
|
||||
}}
|
||||
>
|
||||
<ModalHeader title="Edit bin" eyebrow="Storage" onClose={onClose} />
|
||||
<div style={{ padding: 32, display: "grid", gap: 16 }}>
|
||||
<div style={{ padding: 32, display: "grid", gridTemplateColumns: "2fr 1fr", gap: 16 }}>
|
||||
<Field label="Bin name">
|
||||
<Input
|
||||
autoFocus
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="e.g. Top Drawer"
|
||||
placeholder="e.g. A1"
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Capacity">
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
step={1}
|
||||
value={capacity}
|
||||
onChange={(e) => setCapacity(Math.max(1, Math.floor(+e.target.value || 1)))}
|
||||
/>
|
||||
</Field>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "2fr 1fr", gap: 16 }}>
|
||||
<Field label="Location (optional)">
|
||||
<Input
|
||||
value={location}
|
||||
onChange={(e) => setLocation(e.target.value)}
|
||||
placeholder="e.g. Bedroom"
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Capacity">
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
step={1}
|
||||
value={capacity}
|
||||
onChange={(e) => setCapacity(Math.max(1, Math.floor(+e.target.value || 1)))}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
<ModalFooter>
|
||||
<div />
|
||||
@@ -204,11 +193,9 @@ export function EditBinModal({
|
||||
export function AddBinModal({ onClose }: { onClose: () => void }) {
|
||||
const qc = useQueryClient();
|
||||
const [name, setName] = useState("");
|
||||
const [location, setLocation] = useState("");
|
||||
const [capacity, setCapacity] = useState(10);
|
||||
const create = useMutation({
|
||||
mutationFn: () =>
|
||||
api.createBin({ name: name.trim(), location: location.trim(), capacity }),
|
||||
mutationFn: () => api.createBin({ name: name.trim(), capacity }),
|
||||
onSuccess: () => {
|
||||
qc.invalidateQueries({ queryKey: ["bootstrap"] });
|
||||
onClose();
|
||||
@@ -219,7 +206,7 @@ export function AddBinModal({ onClose }: { onClose: () => void }) {
|
||||
<ModalBackdrop onClose={onClose}>
|
||||
<div
|
||||
style={{
|
||||
width: "min(560px, 96vw)",
|
||||
width: "min(480px, 96vw)",
|
||||
margin: "40px 20px",
|
||||
background: "var(--bg)",
|
||||
border: "1px solid var(--line)",
|
||||
@@ -228,33 +215,24 @@ export function AddBinModal({ onClose }: { onClose: () => void }) {
|
||||
}}
|
||||
>
|
||||
<ModalHeader title="Add a bin" eyebrow="Storage" onClose={onClose} />
|
||||
<div style={{ padding: 32, display: "grid", gap: 16 }}>
|
||||
<div style={{ padding: 32, display: "grid", gridTemplateColumns: "2fr 1fr", gap: 16 }}>
|
||||
<Field label="Bin name">
|
||||
<Input
|
||||
autoFocus
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="e.g. Top Drawer"
|
||||
placeholder="e.g. A1"
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Capacity">
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
step={1}
|
||||
value={capacity}
|
||||
onChange={(e) => setCapacity(Math.max(1, Math.floor(+e.target.value || 1)))}
|
||||
/>
|
||||
</Field>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "2fr 1fr", gap: 16 }}>
|
||||
<Field label="Location (optional)">
|
||||
<Input
|
||||
value={location}
|
||||
onChange={(e) => setLocation(e.target.value)}
|
||||
placeholder="e.g. Bedroom"
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Capacity">
|
||||
<Input
|
||||
type="number"
|
||||
min={1}
|
||||
step={1}
|
||||
value={capacity}
|
||||
onChange={(e) => setCapacity(Math.max(1, Math.floor(+e.target.value || 1)))}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
<ModalFooter>
|
||||
<div />
|
||||
|
||||
@@ -48,7 +48,6 @@ export function EditProductFlow({
|
||||
const [newShopName, setNewShopName] = useState("");
|
||||
const [newShopLocation, setNewShopLocation] = useState("");
|
||||
const [newBinName, setNewBinName] = useState("");
|
||||
const [newBinLocation, setNewBinLocation] = useState("");
|
||||
const [newBinCapacity, setNewBinCapacity] = useState(10);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
@@ -79,7 +78,6 @@ export function EditProductFlow({
|
||||
if (!newBinName.trim()) throw new Error("New bin name required");
|
||||
const b = await api.createBin({
|
||||
name: newBinName.trim(),
|
||||
location: newBinLocation.trim(),
|
||||
capacity: newBinCapacity,
|
||||
});
|
||||
binId = b.id;
|
||||
@@ -195,7 +193,7 @@ export function EditProductFlow({
|
||||
<Field label="Bin">
|
||||
<Select value={form.binId} onChange={(e) => update("binId", e.target.value)}>
|
||||
{data.bins.map((b) => (
|
||||
<option key={b.id} value={b.id}>{b.name} — {b.location}</option>
|
||||
<option key={b.id} value={b.id}>{b.name}</option>
|
||||
))}
|
||||
<option value={NEW_BIN}>+ Add new bin…</option>
|
||||
</Select>
|
||||
@@ -213,14 +211,7 @@ export function EditProductFlow({
|
||||
<Input
|
||||
value={newBinName}
|
||||
onChange={(e) => setNewBinName(e.target.value)}
|
||||
placeholder="e.g. Top Drawer"
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Location (optional)">
|
||||
<Input
|
||||
value={newBinLocation}
|
||||
onChange={(e) => setNewBinLocation(e.target.value)}
|
||||
placeholder="e.g. Bedroom"
|
||||
placeholder="e.g. A1"
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Capacity">
|
||||
|
||||
Reference in New Issue
Block a user