274 lines
8.4 KiB
TypeScript
274 lines
8.4 KiB
TypeScript
import { useState } from "react";
|
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
import { api } from "../../api.js";
|
|
import { Btn, Field, Input } from "../primitives/index.js";
|
|
import { ModalBackdrop, ModalHeader, ModalFooter } from "./AddProductFlow.js";
|
|
|
|
export function AddBrandModal({ onClose }: { onClose: () => void }) {
|
|
const qc = useQueryClient();
|
|
const [name, setName] = useState("");
|
|
const create = useMutation({
|
|
mutationFn: () => api.createBrand(name.trim()),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ["bootstrap"] });
|
|
onClose();
|
|
},
|
|
});
|
|
|
|
return (
|
|
<ModalBackdrop onClose={onClose}>
|
|
<div
|
|
style={{
|
|
width: "min(480px, 96vw)",
|
|
margin: "40px 20px",
|
|
background: "var(--bg)",
|
|
border: "1px solid var(--line)",
|
|
borderRadius: "var(--r-lg)",
|
|
boxShadow: "var(--shadow-lg)",
|
|
}}
|
|
>
|
|
<ModalHeader title="Add a brand" eyebrow="Catalog" onClose={onClose} />
|
|
<div style={{ padding: 32 }}>
|
|
<Field label="Brand name">
|
|
<Input
|
|
autoFocus
|
|
value={name}
|
|
onChange={(e) => setName(e.target.value)}
|
|
placeholder="e.g. Foxglove Farms"
|
|
/>
|
|
</Field>
|
|
</div>
|
|
<ModalFooter>
|
|
<div />
|
|
<div style={{ display: "flex", gap: 8 }}>
|
|
<Btn variant="ghost" onClick={onClose}>Cancel</Btn>
|
|
<Btn
|
|
variant="primary"
|
|
icon="check"
|
|
disabled={!name.trim() || create.isPending}
|
|
onClick={() => create.mutate()}
|
|
>
|
|
{create.isPending ? "Saving…" : "Add brand"}
|
|
</Btn>
|
|
</div>
|
|
</ModalFooter>
|
|
</div>
|
|
</ModalBackdrop>
|
|
);
|
|
}
|
|
|
|
export function EditBinModal({
|
|
bin,
|
|
onClose,
|
|
}: {
|
|
bin: { id: string; name: string; location: string | null; 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 }),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ["bootstrap"] });
|
|
onClose();
|
|
},
|
|
});
|
|
|
|
return (
|
|
<ModalBackdrop onClose={onClose}>
|
|
<div
|
|
style={{
|
|
width: "min(560px, 96vw)",
|
|
margin: "40px 20px",
|
|
background: "var(--bg)",
|
|
border: "1px solid var(--line)",
|
|
borderRadius: "var(--r-lg)",
|
|
boxShadow: "var(--shadow-lg)",
|
|
}}
|
|
>
|
|
<ModalHeader title="Edit bin" eyebrow="Storage" onClose={onClose} />
|
|
<div style={{ padding: 32, display: "grid", gap: 16 }}>
|
|
<Field label="Bin name">
|
|
<Input
|
|
autoFocus
|
|
value={name}
|
|
onChange={(e) => setName(e.target.value)}
|
|
placeholder="e.g. Top Drawer"
|
|
/>
|
|
</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 />
|
|
<div style={{ display: "flex", gap: 8 }}>
|
|
<Btn variant="ghost" onClick={onClose}>Cancel</Btn>
|
|
<Btn
|
|
variant="primary"
|
|
icon="check"
|
|
disabled={!name.trim() || update.isPending}
|
|
onClick={() => update.mutate()}
|
|
>
|
|
{update.isPending ? "Saving…" : "Save changes"}
|
|
</Btn>
|
|
</div>
|
|
</ModalFooter>
|
|
</div>
|
|
</ModalBackdrop>
|
|
);
|
|
}
|
|
|
|
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 }),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ["bootstrap"] });
|
|
onClose();
|
|
},
|
|
});
|
|
|
|
return (
|
|
<ModalBackdrop onClose={onClose}>
|
|
<div
|
|
style={{
|
|
width: "min(560px, 96vw)",
|
|
margin: "40px 20px",
|
|
background: "var(--bg)",
|
|
border: "1px solid var(--line)",
|
|
borderRadius: "var(--r-lg)",
|
|
boxShadow: "var(--shadow-lg)",
|
|
}}
|
|
>
|
|
<ModalHeader title="Add a bin" eyebrow="Storage" onClose={onClose} />
|
|
<div style={{ padding: 32, display: "grid", gap: 16 }}>
|
|
<Field label="Bin name">
|
|
<Input
|
|
autoFocus
|
|
value={name}
|
|
onChange={(e) => setName(e.target.value)}
|
|
placeholder="e.g. Top Drawer"
|
|
/>
|
|
</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 />
|
|
<div style={{ display: "flex", gap: 8 }}>
|
|
<Btn variant="ghost" onClick={onClose}>Cancel</Btn>
|
|
<Btn
|
|
variant="primary"
|
|
icon="check"
|
|
disabled={!name.trim() || create.isPending}
|
|
onClick={() => create.mutate()}
|
|
>
|
|
{create.isPending ? "Saving…" : "Add bin"}
|
|
</Btn>
|
|
</div>
|
|
</ModalFooter>
|
|
</div>
|
|
</ModalBackdrop>
|
|
);
|
|
}
|
|
|
|
export function AddShopModal({ onClose }: { onClose: () => void }) {
|
|
const qc = useQueryClient();
|
|
const [name, setName] = useState("");
|
|
const [location, setLocation] = useState("");
|
|
const create = useMutation({
|
|
mutationFn: () => api.createShop({ name: name.trim(), location: location.trim() }),
|
|
onSuccess: () => {
|
|
qc.invalidateQueries({ queryKey: ["bootstrap"] });
|
|
onClose();
|
|
},
|
|
});
|
|
|
|
return (
|
|
<ModalBackdrop onClose={onClose}>
|
|
<div
|
|
style={{
|
|
width: "min(560px, 96vw)",
|
|
margin: "40px 20px",
|
|
background: "var(--bg)",
|
|
border: "1px solid var(--line)",
|
|
borderRadius: "var(--r-lg)",
|
|
boxShadow: "var(--shadow-lg)",
|
|
}}
|
|
>
|
|
<ModalHeader title="Add a shop" eyebrow="Catalog" onClose={onClose} />
|
|
<div style={{ padding: 32, display: "grid", gap: 16 }}>
|
|
<Field label="Shop name">
|
|
<Input
|
|
autoFocus
|
|
value={name}
|
|
onChange={(e) => setName(e.target.value)}
|
|
placeholder="e.g. Greenleaf Co-op"
|
|
/>
|
|
</Field>
|
|
<Field label="Location (optional)">
|
|
<Input
|
|
value={location}
|
|
onChange={(e) => setLocation(e.target.value)}
|
|
placeholder="e.g. Capitol Hill"
|
|
/>
|
|
</Field>
|
|
</div>
|
|
<ModalFooter>
|
|
<div />
|
|
<div style={{ display: "flex", gap: 8 }}>
|
|
<Btn variant="ghost" onClick={onClose}>Cancel</Btn>
|
|
<Btn
|
|
variant="primary"
|
|
icon="check"
|
|
disabled={!name.trim() || create.isPending}
|
|
onClick={() => create.mutate()}
|
|
>
|
|
{create.isPending ? "Saving…" : "Add shop"}
|
|
</Btn>
|
|
</div>
|
|
</ModalFooter>
|
|
</div>
|
|
</ModalBackdrop>
|
|
);
|
|
}
|