Files
CutList/CutList.Web/Components/Pages/Materials/Edit.razor
AJ Isaacs 21d50e7c20 fix: Prevent shape change after material creation
Disable Shape dropdown on existing materials since changing shape would
require completely different dimension properties.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 00:19:01 -05:00

415 lines
16 KiB
Plaintext

@page "/materials/new"
@page "/materials/{Id:int}"
@inject MaterialService MaterialService
@inject NavigationManager Navigation
@using CutList.Core.Formatting
@using CutList.Web.Data.Entities
@using CutList.Web.Components.Shared
<PageTitle>@(IsNew ? "Add Material" : "Edit Material")</PageTitle>
<h1>@(IsNew ? "Add Material" : material.DisplayName)</h1>
@if (loading)
{
<p><em>Loading...</em></p>
}
else
{
<div class="row">
<div class="col-lg-6 mb-4">
<div class="card">
<div class="card-header">
<h5 class="mb-0">Material Details</h5>
</div>
<div class="card-body">
<EditForm Model="material" OnValidSubmit="SaveAsync">
<DataAnnotationsValidator />
<div class="mb-3">
<label class="form-label">Shape</label>
<InputSelect class="form-select" @bind-Value="selectedShape" @bind-Value:after="OnShapeChanged" disabled="@(!IsNew)">
<option value="">-- Select Shape --</option>
@foreach (var shape in Enum.GetValues<MaterialShape>())
{
<option value="@shape">@shape.GetDisplayName()</option>
}
</InputSelect>
@if (!IsNew)
{
<div class="form-text text-muted">Shape cannot be changed after creation.</div>
}
<ValidationMessage For="@(() => material.Shape)" />
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label">Type</label>
<InputSelect class="form-select" @bind-Value="material.Type">
@foreach (var type in Enum.GetValues<MaterialType>())
{
<option value="@type">@type</option>
}
</InputSelect>
</div>
<div class="col-md-6 mb-3">
<label class="form-label">Grade</label>
<InputText class="form-control" @bind-Value="material.Grade" placeholder="e.g., A36, Hot Roll, 304" />
</div>
</div>
@if (selectedShape != null)
{
@RenderDimensionInputs()
}
<div class="mb-3">
<label class="form-label">Size Display (auto-generated)</label>
<InputText class="form-control" @bind-Value="material.Size" placeholder="Will be auto-generated from dimensions" />
<div class="form-text">Leave blank to auto-generate from dimensions, or customize as needed.</div>
</div>
<div class="mb-3">
<label class="form-label">Description (optional)</label>
<InputText class="form-control" @bind-Value="material.Description" placeholder="Optional description" />
</div>
@if (!string.IsNullOrEmpty(errorMessage))
{
<div class="alert alert-danger">@errorMessage</div>
}
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary" disabled="@saving">
@if (saving)
{
<span class="spinner-border spinner-border-sm me-1"></span>
}
@(IsNew ? "Create Material" : "Save Changes")
</button>
<a href="materials" class="btn btn-outline-secondary">@(IsNew ? "Cancel" : "Back to List")</a>
</div>
</EditForm>
</div>
</div>
</div>
@if (selectedShape != null)
{
<div class="col-lg-6 mb-4">
<div class="card">
<div class="card-header">
<h5 class="mb-0">Preview</h5>
</div>
<div class="card-body">
<dl class="row mb-0">
<dt class="col-sm-4">Shape</dt>
<dd class="col-sm-8">@selectedShape.Value.GetDisplayName()</dd>
<dt class="col-sm-4">Type</dt>
<dd class="col-sm-8">@material.Type</dd>
@if (!string.IsNullOrWhiteSpace(material.Grade))
{
<dt class="col-sm-4">Grade</dt>
<dd class="col-sm-8">@material.Grade</dd>
}
<dt class="col-sm-4">Size</dt>
<dd class="col-sm-8">@GetPreviewSize()</dd>
@if (!string.IsNullOrWhiteSpace(material.Description))
{
<dt class="col-sm-4">Description</dt>
<dd class="col-sm-8">@material.Description</dd>
}
</dl>
</div>
</div>
</div>
}
</div>
}
@code {
[Parameter]
public int? Id { get; set; }
private Material material = new();
private MaterialShape? selectedShape;
private bool loading = true;
private bool saving;
private string? errorMessage;
// Typed dimension objects for each shape
private RoundBarDimensions roundBarDims = new();
private RoundTubeDimensions roundTubeDims = new();
private FlatBarDimensions flatBarDims = new();
private SquareBarDimensions squareBarDims = new();
private SquareTubeDimensions squareTubeDims = new();
private RectangularTubeDimensions rectTubeDims = new();
private AngleDimensions angleDims = new();
private ChannelDimensions channelDims = new();
private IBeamDimensions ibeamDims = new();
private PipeDimensions pipeDims = new();
private bool IsNew => !Id.HasValue;
protected override async Task OnInitializedAsync()
{
if (Id.HasValue)
{
var existing = await MaterialService.GetByIdAsync(Id.Value);
if (existing == null)
{
Navigation.NavigateTo("materials");
return;
}
material = existing;
selectedShape = existing.Shape;
LoadDimensionsFromMaterial(existing);
}
loading = false;
}
private void LoadDimensionsFromMaterial(Material m)
{
if (m.Dimensions == null) return;
switch (m.Dimensions)
{
case RoundBarDimensions d: roundBarDims = d; break;
case RoundTubeDimensions d: roundTubeDims = d; break;
case FlatBarDimensions d: flatBarDims = d; break;
case SquareBarDimensions d: squareBarDims = d; break;
case SquareTubeDimensions d: squareTubeDims = d; break;
case RectangularTubeDimensions d: rectTubeDims = d; break;
case AngleDimensions d: angleDims = d; break;
case ChannelDimensions d: channelDims = d; break;
case IBeamDimensions d: ibeamDims = d; break;
case PipeDimensions d: pipeDims = d; break;
}
}
private MaterialDimensions GetCurrentDimensions() => selectedShape switch
{
MaterialShape.RoundBar => roundBarDims,
MaterialShape.RoundTube => roundTubeDims,
MaterialShape.FlatBar => flatBarDims,
MaterialShape.SquareBar => squareBarDims,
MaterialShape.SquareTube => squareTubeDims,
MaterialShape.RectangularTube => rectTubeDims,
MaterialShape.Angle => angleDims,
MaterialShape.Channel => channelDims,
MaterialShape.IBeam => ibeamDims,
MaterialShape.Pipe => pipeDims,
_ => throw new InvalidOperationException("No shape selected")
};
private void OnShapeChanged()
{
if (selectedShape.HasValue)
{
material.Shape = selectedShape.Value;
}
}
private string GetPreviewSize()
{
if (!string.IsNullOrWhiteSpace(material.Size))
{
return material.Size;
}
if (selectedShape.HasValue)
{
try
{
var generated = GetCurrentDimensions().GenerateSizeString();
return string.IsNullOrWhiteSpace(generated) ? "(enter dimensions)" : generated;
}
catch
{
return "(enter dimensions)";
}
}
return "(select shape and enter dimensions)";
}
private RenderFragment RenderDimensionInputs() => __builder =>
{
switch (selectedShape!.Value)
{
case MaterialShape.RoundBar:
<div class="mb-3">
<label class="form-label">Diameter</label>
<LengthInput @bind-Value="roundBarDims.Diameter" Placeholder="e.g., 1/2&quot; or 0.5" />
</div>
break;
case MaterialShape.RoundTube:
<div class="mb-3">
<label class="form-label">Outer Diameter</label>
<LengthInput @bind-Value="roundTubeDims.OuterDiameter" Placeholder="e.g., 1&quot; or 1.0" />
</div>
<div class="mb-3">
<label class="form-label">Wall Thickness</label>
<LengthInput @bind-Value="roundTubeDims.Wall" Placeholder="e.g., 0.065 or 1/16&quot;" />
</div>
break;
case MaterialShape.FlatBar:
<div class="mb-3">
<label class="form-label">Width</label>
<LengthInput @bind-Value="flatBarDims.Width" Placeholder="e.g., 2&quot; or 2.0" />
</div>
<div class="mb-3">
<label class="form-label">Thickness</label>
<LengthInput @bind-Value="flatBarDims.Thickness" Placeholder="e.g., 1/4&quot; or 0.25" />
</div>
break;
case MaterialShape.SquareBar:
<div class="mb-3">
<label class="form-label">Size</label>
<LengthInput @bind-Value="squareBarDims.Size" Placeholder="e.g., 1&quot; or 1.0" />
</div>
break;
case MaterialShape.SquareTube:
<div class="mb-3">
<label class="form-label">Size</label>
<LengthInput @bind-Value="squareTubeDims.Size" Placeholder="e.g., 2&quot; or 2.0" />
</div>
<div class="mb-3">
<label class="form-label">Wall Thickness</label>
<LengthInput @bind-Value="squareTubeDims.Wall" Placeholder="e.g., 0.125 or 1/8&quot;" />
</div>
break;
case MaterialShape.RectangularTube:
<div class="mb-3">
<label class="form-label">Width</label>
<LengthInput @bind-Value="rectTubeDims.Width" Placeholder="e.g., 2&quot; or 2.0" />
</div>
<div class="mb-3">
<label class="form-label">Height</label>
<LengthInput @bind-Value="rectTubeDims.Height" Placeholder="e.g., 3&quot; or 3.0" />
</div>
<div class="mb-3">
<label class="form-label">Wall Thickness</label>
<LengthInput @bind-Value="rectTubeDims.Wall" Placeholder="e.g., 0.125 or 1/8&quot;" />
</div>
break;
case MaterialShape.Angle:
<div class="mb-3">
<label class="form-label">Leg 1</label>
<LengthInput @bind-Value="angleDims.Leg1" Placeholder="e.g., 2&quot; or 2.0" />
</div>
<div class="mb-3">
<label class="form-label">Leg 2</label>
<LengthInput @bind-Value="angleDims.Leg2" Placeholder="e.g., 2&quot; or 2.0" />
</div>
<div class="mb-3">
<label class="form-label">Thickness</label>
<LengthInput @bind-Value="angleDims.Thickness" Placeholder="e.g., 1/4&quot; or 0.25" />
</div>
break;
case MaterialShape.Channel:
<div class="mb-3">
<label class="form-label">Height</label>
<LengthInput @bind-Value="channelDims.Height" Placeholder="e.g., 4&quot; or 4.0" />
</div>
<div class="mb-3">
<label class="form-label">Flange Width</label>
<LengthInput @bind-Value="channelDims.Flange" Placeholder="e.g., 1.58" />
</div>
<div class="mb-3">
<label class="form-label">Web Thickness</label>
<LengthInput @bind-Value="channelDims.Web" Placeholder="e.g., 0.18" />
</div>
break;
case MaterialShape.IBeam:
<div class="mb-3">
<label class="form-label">Height (nominal)</label>
<LengthInput @bind-Value="ibeamDims.Height" Placeholder="e.g., 8" />
</div>
<div class="mb-3">
<label class="form-label">Weight per Foot (lbs)</label>
<input type="number" class="form-control" step="0.01" @bind="ibeamDims.WeightPerFoot" placeholder="e.g., 31" />
</div>
break;
case MaterialShape.Pipe:
<div class="mb-3">
<label class="form-label">Nominal Pipe Size (NPS)</label>
<LengthInput @bind-Value="pipeDims.NominalSize" Placeholder="e.g., 1&quot; or 1.0" />
</div>
<div class="mb-3">
<label class="form-label">Schedule (optional)</label>
<InputText class="form-control" @bind-Value="pipeDims.Schedule" placeholder="e.g., 40, 80, STD" />
</div>
<div class="mb-3">
<label class="form-label">Wall Thickness (if no schedule)</label>
<LengthInput @bind-NullableValue="pipeDims.Wall" Placeholder="e.g., 0.133" />
</div>
break;
}
};
private async Task SaveAsync()
{
errorMessage = null;
saving = true;
try
{
if (!selectedShape.HasValue)
{
errorMessage = "Shape is required";
return;
}
material.Shape = selectedShape.Value;
var dimensions = GetCurrentDimensions();
// Auto-generate Size if empty
if (string.IsNullOrWhiteSpace(material.Size))
{
material.Size = dimensions.GenerateSizeString();
}
if (string.IsNullOrWhiteSpace(material.Size))
{
errorMessage = "Size is required. Please enter dimensions or provide a size string.";
return;
}
// Check for duplicates
if (await MaterialService.ExistsAsync(material.Shape, material.Size, Id))
{
errorMessage = "A material with this shape and size already exists";
return;
}
if (IsNew)
{
var created = await MaterialService.CreateWithDimensionsAsync(material, dimensions);
Navigation.NavigateTo($"materials/{created.Id}");
}
else
{
await MaterialService.UpdateWithDimensionsAsync(material, dimensions);
}
}
finally
{
saving = false;
}
}
}