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:
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user