feat: material override in new product order wizard (Step 3)

- Dropdown in Step 3 review to set a single material override for all lines
- Override is passed to each OrderLine.material_override at creation time
- Amber background when override is active
- Library materials loaded on Step 3 entry

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-14 14:41:42 +01:00
parent d84ce8252e
commit f7aeeec5d8
+32
View File
@@ -11,6 +11,8 @@ import { listOutputTypes } from '../api/outputTypes'
import { createOrder } from '../api/orders' import { createOrder } from '../api/orders'
import { estimatePrice } from '../api/pricing' import { estimatePrice } from '../api/pricing'
import { listGlobalRenderPositions } from '../api/renderPositions' import { listGlobalRenderPositions } from '../api/renderPositions'
import { listMaterials } from '../api/materials'
import type { Material } from '../api/materials'
import type { Product, RenderPosition } from '../api/products' import type { Product, RenderPosition } from '../api/products'
import type { GlobalRenderPosition } from '../api/renderPositions' import type { GlobalRenderPosition } from '../api/renderPositions'
import type { OutputType } from '../api/outputTypes' import type { OutputType } from '../api/outputTypes'
@@ -47,6 +49,7 @@ export default function NewProductOrderPage() {
const [positionSelections, setPositionSelections] = useState<PositionSelections>({}) const [positionSelections, setPositionSelections] = useState<PositionSelections>({})
const [globalPositionSelections, setGlobalPositionSelections] = useState<GlobalPositionSelections>({}) const [globalPositionSelections, setGlobalPositionSelections] = useState<GlobalPositionSelections>({})
const [notes, setNotes] = useState('') const [notes, setNotes] = useState('')
const [materialOverride, setMaterialOverride] = useState<string>('')
const [submitting, setSubmitting] = useState(false) const [submitting, setSubmitting] = useState(false)
// ---- Step 1: load products with STEP files ---- // ---- Step 1: load products with STEP files ----
@@ -72,6 +75,13 @@ export default function NewProductOrderPage() {
queryFn: listGlobalRenderPositions, queryFn: listGlobalRenderPositions,
}) })
const { data: allMaterials } = useQuery({
queryKey: ['materials'],
queryFn: listMaterials,
enabled: step >= 3,
})
const libMaterials = (allMaterials ?? []).filter((m: Material) => m.schaeffler_code !== null).sort((a: Material, b: Material) => a.name.localeCompare(b.name))
function initPositionsForProduct(product: Product, globals: GlobalRenderPosition[] = []) { function initPositionsForProduct(product: Product, globals: GlobalRenderPosition[] = []) {
// Pre-select all per-product positions (if any) // Pre-select all per-product positions (if any)
if ((product.render_positions?.length ?? 0) > 0) { if ((product.render_positions?.length ?? 0) > 0) {
@@ -378,6 +388,7 @@ export default function NewProductOrderPage() {
output_type_id: l.outputType.id, output_type_id: l.outputType.id,
render_position_id: l.position?.id ?? null, render_position_id: l.position?.id ?? null,
global_render_position_id: l.globalPosition?.id ?? null, global_render_position_id: l.globalPosition?.id ?? null,
material_override: materialOverride || null,
})), })),
}) })
toast.success(`Draft order ${result.order_number} created — review and submit`) toast.success(`Draft order ${result.order_number} created — review and submit`)
@@ -823,6 +834,27 @@ export default function NewProductOrderPage() {
</table> </table>
</div> </div>
{/* Material Override */}
<div className="card p-4 mb-4">
<label className="block text-sm font-medium text-content-secondary mb-1">
Material Override (optional)
</label>
<p className="text-xs text-content-muted mb-2">
Apply a single material to all parts of all products in this order. Leave empty to use each product's own materials.
</p>
<select
value={materialOverride}
onChange={(e) => setMaterialOverride(e.target.value)}
className="w-full max-w-md px-3 py-2 border border-border-default rounded-lg text-sm focus:outline-none focus:border-accent"
style={{ backgroundColor: materialOverride ? 'rgba(245, 158, 11, 0.1)' : 'var(--color-bg-surface)' }}
>
<option value="">No material override</option>
{libMaterials.map((m: Material) => (
<option key={m.id} value={m.name}>{m.name}</option>
))}
</select>
</div>
{/* Notes */} {/* Notes */}
<div className="card p-4 mb-4"> <div className="card p-4 mb-4">
<label className="block text-sm font-medium text-content-secondary mb-1"> <label className="block text-sm font-medium text-content-secondary mb-1">