Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions app/blocks/sales-log/delete-sale-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Form } from "react-router";
import { IconX } from "@tabler/icons-react";
import styles from "./log-sale-modal.module.css";

interface Props {
saleId: string;
onClose: () => void;
}

export function DeleteSaleModal({ saleId, onClose }: Props) {
return (
<div
className={styles.overlay}
onClick={(e) => e.target === e.currentTarget && onClose()}
>
<div className={styles.modal}>
<div className={styles.header}>
<span className={styles.title}>Delete Sale</span>

<button
className={styles.closeBtn}
type="button"
onClick={onClose}
>
<IconX size={18} />
</button>
</div>

<Form method="post">
<input type="hidden" name="intent" value="delete" />
<input type="hidden" name="saleId" value={saleId} />

<div className={styles.body}>
Are you sure you want to delete this sale?
</div>

<div className={styles.footer}>
<button
type="button"
className={styles.cancelBtn}
onClick={onClose}
>
Cancel
</button>

<button
type="submit"
className={styles.saveBtn}
>
Delete
</button>
</div>
</Form>
</div>
</div>
);
}
265 changes: 216 additions & 49 deletions app/blocks/sales-log/log-sale-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,99 +3,255 @@ import { Form } from "react-router";
import { IconX } from "@tabler/icons-react";
import styles from "./log-sale-modal.module.css";

interface Props { className?: string; onClose: () => void; }

export function LogSaleModal({ className, onClose }: Props) {
const [salePrice, setSalePrice] = useState("");
const [platformFee, setPlatformFee] = useState("");
const [shippingCost, setShippingCost] = useState("");
const [selectedItemId, setSelectedItemId] = useState("");
const [selectedItem, setSelectedItem] = useState<any>(null);
interface Props {
className?: string;
onClose: () => void;
inventory?: any[];
sale?: any;
}

export function LogSaleModal({
className,
onClose,
inventory = [],
sale,
}: Props) {
const [salePrice, setSalePrice] = useState(
sale ? sale.salePrice.toString() : ""
);

const [platformFee, setPlatformFee] = useState(
sale ? (sale.platformFee ?? "").toString() : ""
);

const [shippingCost, setShippingCost] = useState(
sale ? (sale.shippingCost ?? "").toString() : ""
);

const [selectedItemId, setSelectedItemId] = useState(
sale ? sale.inventoryItemId : ""
);

const [selectedItem, setSelectedItem] = useState<any>(
sale ? sale.inventoryItem : null
);

const [search, setSearch] = useState("");
const [searchResults, setSearchResults] = useState<any[]>([]);


const [saleDate, setSaleDate] = useState(
sale
? new Date(sale.saleDate).toISOString().split("T")[0]
: ""
);

useEffect(() => {
if (sale) {
setSelectedItemId(sale.inventoryItemId);
setSelectedItem(sale.inventoryItem);
setSalePrice(sale.salePrice.toString());
setPlatformFee((sale.platformFee ?? "").toString());
setShippingCost((sale.shippingCost ?? "").toString());
setSaleDate(new Date(sale.saleDate).toISOString().split("T")[0]);
}
}, [sale]);

useEffect(() => {
if (search.length < 2) {
setSearchResults([]);
return;
}

const delayDebounceFn = setTimeout(() => {
fetch(`/api/inventory/search?q=${search}`)
.then(res => res.json())
.then(data => setSearchResults(data.items || []))
.catch(err => console.error(err));
.then((res) => res.json())
.then((data) => setSearchResults(data.items || []))
.catch(console.error);
}, 300);
return () => clearTimeout(delayDebounceFn);
}, [search]);

const purchasePrice = selectedItem ? Number(selectedItem.purchasePrice) : 0;
const currentItem = sale
? sale.inventoryItem
: selectedItem || inventory.find((i) => i.id === selectedItemId);

const purchasePrice = parseFloat(
String(currentItem?.purchasePrice ?? "0")
);

const salePriceValue = parseFloat(salePrice || "0");
const platformFeeValue = parseFloat(platformFee || "0");
const shippingCostValue = parseFloat(shippingCost || "0");
const profit = salePrice && selectedItem ? parseFloat(salePrice) - purchasePrice - platformFeeValue - shippingCostValue : null;

const profit =
currentItem
? salePriceValue -
purchasePrice -
platformFeeValue -
shippingCostValue
: null;

return (
<div className={styles.overlay} onClick={e => e.target === e.currentTarget && onClose()}>
<div className={[styles.modal, className].filter(Boolean).join(" ")}>
<div className={styles.header}>
<span className={styles.title}>Log Sale</span>
<span className={styles.title}>{sale ? "Edit Sale" : "Log Sale"}</span>
<button className={styles.closeBtn} onClick={onClose}><IconX size={18} /></button>
</div>
<Form method="post" onSubmit={() => onClose()}>
<input type="hidden" name="intent" value="create" />
<Form method="post">
<input type="hidden" name="intent" value={sale ? "edit" : "create"} /> {sale && (<input type="hidden" name="saleId" value={sale.id}
/>
)}
<div className={styles.body}>
<div className={styles.field} style={{ position: 'relative' }}>
<label className={styles.label}>Search Inventory Item *</label>
<input
className={styles.input}
placeholder="Type SKU or name..."
value={search}
onChange={e => { setSearch(e.target.value); setSelectedItem(null); setSelectedItemId(""); }}
required={!selectedItemId}
/>
{searchResults.length > 0 && !selectedItem && (
<ul className={styles.autocompleteList} style={{ position: 'absolute', top: '100%', left: 0, right: 0, zIndex: 10, background: '#fff', border: '1px solid #ccc', maxHeight: '150px', overflowY: 'auto', padding: 0, listStyle: 'none' }}>
{searchResults.map(item => (
<li
key={item.id}
style={{ padding: '8px', cursor: 'pointer', borderBottom: '1px solid #eee', color: '#000' }}
onClick={() => {
setSelectedItem(item);
setSelectedItemId(item.id);
setSearchResults([]);
setSearch(`${item.name} (${item.size}) - $${Number(item.purchasePrice)}`);
<div className={styles.field} style={{ position: "relative" }}>
<label className={styles.label}>Inventory Item *</label>

{sale ? (
<select
name="inventoryItemId"
className={styles.input}
value={selectedItemId}
disabled
>
<option value={sale.inventoryItemId}>
{sale.inventoryItem.name} ({sale.inventoryItem.size}) - $
{Number(sale.inventoryItem.purchasePrice)}
</option>
</select>
) : (
<>
<input
className={styles.input}
placeholder="Type SKU or name..."
value={search}
onChange={(e) => {
setSearch(e.target.value);
setSelectedItem(null);
setSelectedItemId("");
}}
required={!selectedItemId}
/>

{searchResults.length > 0 && !selectedItem && (
<ul
className={styles.autocompleteList}
style={{
position: "absolute",
top: "100%",
left: 0,
right: 0,
zIndex: 10,
background: "#fff",
border: "1px solid #ccc",
maxHeight: "150px",
overflowY: "auto",
padding: 0,
listStyle: "none",
}}
>
{item.name} ({item.size}) - ${Number(item.purchasePrice)}
</li>
))}
</ul>
{searchResults.map((item) => (
<li
key={item.id}
style={{
padding: "8px",
cursor: "pointer",
borderBottom: "1px solid #eee",
color: "#000",
}}
onClick={() => {
setSelectedItem(item);
setSelectedItemId(item.id);
setSearchResults([]);
setSearch(
`${item.name} (${item.size}) - $${Number(
item.purchasePrice
)}`
);
}}
>
{item.name} ({item.size}) - ${Number(item.purchasePrice)}
</li>
))}
</ul>
)}

<input
type="hidden"
name="inventoryItemId"
value={selectedItemId}
required
/>
</>
)}
<input type="hidden" name="inventoryItemId" value={selectedItemId} required />
</div>

<div className={styles.row}>
<div className={styles.field}>
<label className={styles.label}>Sale Price *</label>
<input name="salePrice" className={styles.input} type="number" step="0.01" placeholder="450" required value={salePrice} onChange={e => setSalePrice(e.target.value)} />
<input
name="salePrice"
className={styles.input}
type="number"
step="0.01"
placeholder="450"
required
value={salePrice}
onChange={(e) => setSalePrice(e.target.value)}
/>
</div>

<div className={styles.field}>
<label className={styles.label}>Sale Date *</label>
<input name="saleDate" className={styles.input} type="date" required />
<input
name="saleDate"
className={styles.input}
type="date"
required
value={saleDate}
onChange={(e) => setSaleDate(e.target.value)}
/>
</div>
</div>

<div className={styles.row}>
<div className={styles.field}>
<label className={styles.label}>Platform / Seller Fees</label>
<input name="platformFee" type="number" step="0.01" min="0" inputMode="decimal" className={styles.input} placeholder="0.00" value={platformFee} onChange={e => setPlatformFee(e.target.value)} />
<input
name="platformFee"
type="number"
step="0.01"
min="0"
inputMode="decimal"
className={styles.input}
placeholder="0.00"
value={platformFee}
onChange={(e) => setPlatformFee(e.target.value)}
/>
</div>

<div className={styles.field}>
<label className={styles.label}>Shipping Cost</label>
<input name="shippingCost" type="number" step="0.01" min="0" inputMode="decimal" className={styles.input} placeholder="0.00" value={shippingCost} onChange={e => setShippingCost(e.target.value)} />
<input
name="shippingCost"
type="number"
step="0.01"
min="0"
inputMode="decimal"
className={styles.input}
placeholder="0.00"
value={shippingCost}
onChange={(e) => setShippingCost(e.target.value)}
/>
</div>
</div>
<div className={styles.row}>
<div className={styles.field}>
<label className={styles.label}>Marketplace *</label>
<select name="marketplace" className={styles.input} required>
<select
name="marketplace"
className={styles.input}
required
defaultValue={sale?.marketplace ?? "EBAY"}>
<option value="EBAY">eBay</option>
<option value="AMAZON">Amazon</option>
<option value="MERCARI">Mercari</option>
Expand All @@ -115,7 +271,12 @@ export function LogSaleModal({ className, onClose }: Props) {
</div>
<div className={styles.field}>
<label className={styles.label}>Tracking Number</label>
<input name="trackingNumber" className={styles.input} placeholder="Optional" />
<input
name="trackingNumber"
className={styles.input}
placeholder="Optional"
defaultValue={sale?.trackingNumber ?? ""}
/>
</div>
</div>
{profit !== null && (
Expand All @@ -127,7 +288,13 @@ export function LogSaleModal({ className, onClose }: Props) {
</div>
<div className={styles.footer}>
<button type="button" className={styles.cancelBtn} onClick={onClose}>Cancel</button>
<button type="submit" className={styles.saveBtn} disabled={!selectedItemId || !salePrice}>Log Sale</button>
<button
type="submit"
className={styles.saveBtn}
disabled={!selectedItemId || !salePrice}
>
{sale ? "Save Changes" : "Log Sale"}
</button>
</div>
</Form>
</div >
Expand Down
Loading
Loading