2026-03-23 16:16:45 -05:00
|
|
|
import { addKitComponent, buildAssembly } from "@/lib/actions";
|
|
|
|
|
import { getAssembliesWithComponents, getParts } from "@/lib/repository";
|
|
|
|
|
|
2026-03-23 17:12:35 -05:00
|
|
|
export default async function AssembliesPage({
|
|
|
|
|
searchParams
|
|
|
|
|
}: {
|
|
|
|
|
searchParams?: Promise<{ error?: string; success?: string }>;
|
|
|
|
|
}) {
|
|
|
|
|
const params = (await searchParams) ?? {};
|
2026-03-23 16:16:45 -05:00
|
|
|
const parts = getParts();
|
|
|
|
|
const assemblies = parts.filter((part) => part.kind === "assembly");
|
|
|
|
|
const components = parts.filter((part) => part.kind === "part");
|
|
|
|
|
const kitRows = getAssembliesWithComponents();
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="grid">
|
2026-03-23 17:12:35 -05:00
|
|
|
{params.error ? <section className="panel"><p className="muted" style={{ color: "var(--danger)" }}>{params.error}</p></section> : null}
|
|
|
|
|
{params.success ? <section className="panel"><p className="muted" style={{ color: "var(--success)" }}>{params.success}</p></section> : null}
|
2026-03-23 16:16:45 -05:00
|
|
|
<section className="two-up">
|
|
|
|
|
<article className="panel">
|
|
|
|
|
<h2 className="section-title">Bill of Materials</h2>
|
2026-03-23 17:16:47 -05:00
|
|
|
<p className="section-copy">Define which stocked parts are consumed to build each assembly. Start typing to filter large inventories quickly.</p>
|
2026-03-23 16:16:45 -05:00
|
|
|
<form action={addKitComponent} className="form-grid">
|
|
|
|
|
<div className="form-row">
|
2026-03-23 17:16:47 -05:00
|
|
|
<label htmlFor="assemblySku">Assembly</label>
|
|
|
|
|
<input
|
|
|
|
|
className="input"
|
|
|
|
|
id="assemblySku"
|
|
|
|
|
name="assemblySku"
|
|
|
|
|
list="assembly-skus"
|
|
|
|
|
placeholder="Type assembly SKU or name"
|
|
|
|
|
disabled={assemblies.length === 0}
|
|
|
|
|
required
|
|
|
|
|
/>
|
|
|
|
|
<datalist id="assembly-skus">
|
|
|
|
|
{assemblies.map((assembly) => (
|
|
|
|
|
<option key={assembly.id} value={assembly.sku}>
|
|
|
|
|
{assembly.name}
|
|
|
|
|
</option>
|
|
|
|
|
))}
|
|
|
|
|
</datalist>
|
2026-03-23 16:16:45 -05:00
|
|
|
</div>
|
|
|
|
|
<div className="form-row">
|
2026-03-23 17:16:47 -05:00
|
|
|
<label htmlFor="componentSku">Component</label>
|
|
|
|
|
<input
|
|
|
|
|
className="input"
|
|
|
|
|
id="componentSku"
|
|
|
|
|
name="componentSku"
|
|
|
|
|
list="component-skus"
|
|
|
|
|
placeholder="Type component SKU or name"
|
|
|
|
|
disabled={components.length === 0}
|
|
|
|
|
required
|
|
|
|
|
/>
|
|
|
|
|
<datalist id="component-skus">
|
|
|
|
|
{components.map((component) => (
|
|
|
|
|
<option key={component.id} value={component.sku}>
|
|
|
|
|
{component.name}
|
|
|
|
|
</option>
|
|
|
|
|
))}
|
|
|
|
|
</datalist>
|
2026-03-23 16:16:45 -05:00
|
|
|
</div>
|
|
|
|
|
<div className="form-row"><label htmlFor="component-qty">Quantity Per Assembly</label><input className="input" id="component-qty" name="quantity" type="number" min="0.01" step="0.01" defaultValue="1" /></div>
|
2026-03-23 17:12:35 -05:00
|
|
|
<button className="button" type="submit" disabled={assemblies.length === 0 || components.length === 0}>Save Component</button>
|
2026-03-23 16:16:45 -05:00
|
|
|
</form>
|
|
|
|
|
</article>
|
|
|
|
|
<article className="panel">
|
|
|
|
|
<h2 className="section-title">Build Assembly</h2>
|
2026-03-23 17:12:35 -05:00
|
|
|
<p className="section-copy">Consume component stock and create finished kit inventory in one transaction flow. This only works after the assembly has a BOM and enough component stock exists.</p>
|
2026-03-23 16:16:45 -05:00
|
|
|
<form action={buildAssembly} className="form-grid">
|
|
|
|
|
<div className="form-row">
|
2026-03-23 17:16:47 -05:00
|
|
|
<label htmlFor="build-assembly">Assembly</label>
|
|
|
|
|
<input
|
|
|
|
|
className="input"
|
|
|
|
|
id="build-assembly"
|
|
|
|
|
name="assemblySku"
|
|
|
|
|
list="build-assembly-skus"
|
|
|
|
|
placeholder="Type assembly SKU or name"
|
|
|
|
|
disabled={assemblies.length === 0}
|
|
|
|
|
required
|
|
|
|
|
/>
|
|
|
|
|
<datalist id="build-assembly-skus">
|
|
|
|
|
{assemblies.map((assembly) => (
|
|
|
|
|
<option key={assembly.id} value={assembly.sku}>
|
|
|
|
|
{assembly.name}
|
|
|
|
|
</option>
|
|
|
|
|
))}
|
|
|
|
|
</datalist>
|
2026-03-23 16:16:45 -05:00
|
|
|
</div>
|
|
|
|
|
<div className="form-row"><label htmlFor="build-qty">Build Quantity</label><input className="input" id="build-qty" name="quantity" type="number" min="1" step="1" defaultValue="1" /></div>
|
2026-03-23 17:12:35 -05:00
|
|
|
<button className="button secondary" type="submit" disabled={assemblies.length === 0}>Build Now</button>
|
2026-03-23 16:16:45 -05:00
|
|
|
</form>
|
|
|
|
|
</article>
|
|
|
|
|
</section>
|
|
|
|
|
<section className="panel">
|
|
|
|
|
<h2 className="section-title">Current Assemblies</h2>
|
|
|
|
|
<div className="table-wrap">
|
|
|
|
|
<table className="table">
|
|
|
|
|
<thead><tr><th>Assembly</th><th>Name</th><th>Component</th><th>Component Name</th><th>Qty Per</th></tr></thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
{kitRows.length === 0 ? (
|
|
|
|
|
<tr><td colSpan={5} className="muted">Add an assembly on the Parts page, then define its bill of materials here.</td></tr>
|
|
|
|
|
) : (
|
|
|
|
|
kitRows.map((row, index) => (
|
|
|
|
|
<tr key={`${row.assemblySku}-${row.componentSku}-${index}`}>
|
|
|
|
|
<td>{row.assemblySku}</td><td>{row.assemblyName}</td><td>{row.componentSku}</td><td>{row.componentName}</td><td>{row.quantity}</td>
|
|
|
|
|
</tr>
|
|
|
|
|
))
|
|
|
|
|
)}
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|