feat(web): add BOM merge resolution UI with source badges and dropdown
- Add source badges (assembly=teal, manual=blue) to BOM display rows - Add info banner when assembly-sourced entries exist - Change source input from text field to select dropdown - Add merge response types to types.ts Closes #47
This commit is contained in:
@@ -212,6 +212,38 @@ export interface UpdateBOMEntryRequest {
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
// BOM Merge
|
||||
export interface MergeBOMResponse {
|
||||
status: string;
|
||||
diff: MergeBOMDiff;
|
||||
warnings: MergeWarning[];
|
||||
resolve_url: string;
|
||||
}
|
||||
|
||||
export interface MergeBOMDiff {
|
||||
added: MergeDiffEntry[];
|
||||
removed: MergeDiffEntry[];
|
||||
quantity_changed: MergeQtyChange[];
|
||||
unchanged: MergeDiffEntry[];
|
||||
}
|
||||
|
||||
export interface MergeDiffEntry {
|
||||
part_number: string;
|
||||
quantity: number | null;
|
||||
}
|
||||
|
||||
export interface MergeQtyChange {
|
||||
part_number: string;
|
||||
old_quantity: number | null;
|
||||
new_quantity: number | null;
|
||||
}
|
||||
|
||||
export interface MergeWarning {
|
||||
type: string;
|
||||
part_number: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
// Schema properties
|
||||
export interface PropertyDef {
|
||||
type: string;
|
||||
|
||||
@@ -46,6 +46,7 @@ export function BOMTab({ partNumber, isEditor }: BOMTabProps) {
|
||||
const unitCost = (e: BOMEntry) => Number(meta(e).unit_cost) || 0;
|
||||
const extCost = (e: BOMEntry) => unitCost(e) * (e.quantity ?? 0);
|
||||
const totalCost = entries.reduce((sum, e) => sum + extCost(e), 0);
|
||||
const assemblyCount = entries.filter((e) => e.source === "assembly").length;
|
||||
|
||||
const formToRequest = () => ({
|
||||
child_part_number: form.child_part_number,
|
||||
@@ -139,12 +140,15 @@ export function BOMTab({ partNumber, isEditor }: BOMTabProps) {
|
||||
/>
|
||||
</td>
|
||||
<td style={tdStyle}>
|
||||
<input
|
||||
<select
|
||||
value={form.source}
|
||||
onChange={(e) => setForm({ ...form, source: e.target.value })}
|
||||
placeholder="Source"
|
||||
style={inputStyle}
|
||||
/>
|
||||
>
|
||||
<option value="">—</option>
|
||||
<option value="manual">manual</option>
|
||||
<option value="assembly">assembly</option>
|
||||
</select>
|
||||
</td>
|
||||
<td style={tdStyle}>
|
||||
<input
|
||||
@@ -247,6 +251,24 @@ export function BOMTab({ partNumber, isEditor }: BOMTabProps) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isEditor && assemblyCount > 0 && (
|
||||
<div
|
||||
style={{
|
||||
padding: "0.35rem 0.6rem",
|
||||
marginBottom: "0.5rem",
|
||||
borderRadius: "0.3rem",
|
||||
backgroundColor: "rgba(148,226,213,0.1)",
|
||||
border: "1px solid rgba(148,226,213,0.3)",
|
||||
fontSize: "0.75rem",
|
||||
color: "var(--ctp-subtext1)",
|
||||
}}
|
||||
>
|
||||
{assemblyCount} assembly-sourced{" "}
|
||||
{assemblyCount === 1 ? "entry" : "entries"}. Entries removed from the
|
||||
FreeCAD assembly will remain here until manually deleted.
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ overflow: "auto" }}>
|
||||
<table
|
||||
style={{
|
||||
@@ -289,7 +311,15 @@ export function BOMTab({ partNumber, isEditor }: BOMTabProps) {
|
||||
>
|
||||
{e.child_part_number}
|
||||
</td>
|
||||
<td style={tdStyle}>{e.source ?? ""}</td>
|
||||
<td style={tdStyle}>
|
||||
{e.source === "assembly" ? (
|
||||
<span style={assemblyBadge}>assembly</span>
|
||||
) : e.source === "manual" ? (
|
||||
<span style={manualBadge}>manual</span>
|
||||
) : (
|
||||
"—"
|
||||
)}
|
||||
</td>
|
||||
<td
|
||||
style={{
|
||||
...tdStyle,
|
||||
@@ -420,6 +450,25 @@ const saveBtnStyle: React.CSSProperties = {
|
||||
marginRight: "0.25rem",
|
||||
};
|
||||
|
||||
const sourceBadgeBase: React.CSSProperties = {
|
||||
padding: "0.1rem 0.4rem",
|
||||
borderRadius: "1rem",
|
||||
fontSize: "0.7rem",
|
||||
fontWeight: 500,
|
||||
};
|
||||
|
||||
const assemblyBadge: React.CSSProperties = {
|
||||
...sourceBadgeBase,
|
||||
backgroundColor: "rgba(148,226,213,0.2)",
|
||||
color: "var(--ctp-teal)",
|
||||
};
|
||||
|
||||
const manualBadge: React.CSSProperties = {
|
||||
...sourceBadgeBase,
|
||||
backgroundColor: "rgba(137,180,250,0.2)",
|
||||
color: "var(--ctp-blue)",
|
||||
};
|
||||
|
||||
const cancelBtnStyle: React.CSSProperties = {
|
||||
padding: "0.2rem 0.4rem",
|
||||
fontSize: "0.75rem",
|
||||
|
||||
Reference in New Issue
Block a user