Compare commits
4 Commits
issue-37-f
...
fix-sse-re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7aa673d2c | ||
| 2157b40d06 | |||
|
|
25c42bd70b | ||
| 8d88f77ff6 |
@@ -16,9 +16,12 @@ func (s *Server) HandleEvents(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Disable the write deadline for this long-lived connection.
|
||||
// The server's WriteTimeout (15s) would otherwise kill it.
|
||||
// Disable read and write deadlines for this long-lived connection.
|
||||
// The server's ReadTimeout/WriteTimeout (15s) would otherwise kill it.
|
||||
rc := http.NewResponseController(w)
|
||||
if err := rc.SetReadDeadline(time.Time{}); err != nil {
|
||||
s.logger.Warn().Err(err).Msg("failed to disable read deadline for SSE")
|
||||
}
|
||||
if err := rc.SetWriteDeadline(time.Time{}); err != nil {
|
||||
s.logger.Warn().Err(err).Msg("failed to disable write deadline for SSE")
|
||||
}
|
||||
|
||||
@@ -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