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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable the write deadline for this long-lived connection.
|
// Disable read and write deadlines for this long-lived connection.
|
||||||
// The server's WriteTimeout (15s) would otherwise kill it.
|
// The server's ReadTimeout/WriteTimeout (15s) would otherwise kill it.
|
||||||
rc := http.NewResponseController(w)
|
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 {
|
if err := rc.SetWriteDeadline(time.Time{}); err != nil {
|
||||||
s.logger.Warn().Err(err).Msg("failed to disable write deadline for SSE")
|
s.logger.Warn().Err(err).Msg("failed to disable write deadline for SSE")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,6 +212,38 @@ export interface UpdateBOMEntryRequest {
|
|||||||
metadata?: Record<string, unknown>;
|
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
|
// Schema properties
|
||||||
export interface PropertyDef {
|
export interface PropertyDef {
|
||||||
type: string;
|
type: string;
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export function BOMTab({ partNumber, isEditor }: BOMTabProps) {
|
|||||||
const unitCost = (e: BOMEntry) => Number(meta(e).unit_cost) || 0;
|
const unitCost = (e: BOMEntry) => Number(meta(e).unit_cost) || 0;
|
||||||
const extCost = (e: BOMEntry) => unitCost(e) * (e.quantity ?? 0);
|
const extCost = (e: BOMEntry) => unitCost(e) * (e.quantity ?? 0);
|
||||||
const totalCost = entries.reduce((sum, e) => sum + extCost(e), 0);
|
const totalCost = entries.reduce((sum, e) => sum + extCost(e), 0);
|
||||||
|
const assemblyCount = entries.filter((e) => e.source === "assembly").length;
|
||||||
|
|
||||||
const formToRequest = () => ({
|
const formToRequest = () => ({
|
||||||
child_part_number: form.child_part_number,
|
child_part_number: form.child_part_number,
|
||||||
@@ -139,12 +140,15 @@ export function BOMTab({ partNumber, isEditor }: BOMTabProps) {
|
|||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td style={tdStyle}>
|
<td style={tdStyle}>
|
||||||
<input
|
<select
|
||||||
value={form.source}
|
value={form.source}
|
||||||
onChange={(e) => setForm({ ...form, source: e.target.value })}
|
onChange={(e) => setForm({ ...form, source: e.target.value })}
|
||||||
placeholder="Source"
|
|
||||||
style={inputStyle}
|
style={inputStyle}
|
||||||
/>
|
>
|
||||||
|
<option value="">—</option>
|
||||||
|
<option value="manual">manual</option>
|
||||||
|
<option value="assembly">assembly</option>
|
||||||
|
</select>
|
||||||
</td>
|
</td>
|
||||||
<td style={tdStyle}>
|
<td style={tdStyle}>
|
||||||
<input
|
<input
|
||||||
@@ -247,6 +251,24 @@ export function BOMTab({ partNumber, isEditor }: BOMTabProps) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</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" }}>
|
<div style={{ overflow: "auto" }}>
|
||||||
<table
|
<table
|
||||||
style={{
|
style={{
|
||||||
@@ -289,7 +311,15 @@ export function BOMTab({ partNumber, isEditor }: BOMTabProps) {
|
|||||||
>
|
>
|
||||||
{e.child_part_number}
|
{e.child_part_number}
|
||||||
</td>
|
</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
|
<td
|
||||||
style={{
|
style={{
|
||||||
...tdStyle,
|
...tdStyle,
|
||||||
@@ -420,6 +450,25 @@ const saveBtnStyle: React.CSSProperties = {
|
|||||||
marginRight: "0.25rem",
|
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 = {
|
const cancelBtnStyle: React.CSSProperties = {
|
||||||
padding: "0.2rem 0.4rem",
|
padding: "0.2rem 0.4rem",
|
||||||
fontSize: "0.75rem",
|
fontSize: "0.75rem",
|
||||||
|
|||||||
Reference in New Issue
Block a user