feat: Update UI for Jobs and enhanced Materials

Navigation:
- Rename Projects to Jobs in NavMenu
- Add new icon for multi-material boxes

Home page:
- Update references from Projects to Jobs

Materials pages:
- Add Type and Grade columns to index
- Shape-specific dimension editing with typed inputs
- Error handling with detailed messages

Stock pages:
- Show Shape, Type, Grade, Size columns
- Display QuantityOnHand with badges

Shared components:
- LengthInput: Add nullable binding mode for optional dimensions
- LengthInput: Format on blur for better UX
- CutListReport: Update for Job model references

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-04 23:38:15 -05:00
parent c5da5dda98
commit 6388e003d3
11 changed files with 627 additions and 235 deletions

View File

@@ -4,7 +4,8 @@
<input type="text"
class="form-control @(HasError ? "is-invalid" : "")"
value="@DisplayValue"
@onchange="OnInputChange"
@oninput="OnInputChange"
@onblur="OnBlur"
placeholder="@Placeholder" />
@if (HasError)
{
@@ -13,24 +14,53 @@
</div>
@code {
/// <summary>
/// Non-nullable decimal value binding.
/// </summary>
[Parameter]
public decimal Value { get; set; }
[Parameter]
public EventCallback<decimal> ValueChanged { get; set; }
/// <summary>
/// Nullable decimal value binding (used for optional dimension fields).
/// Takes precedence over Value if both ValueChanged and NullableValueChanged are set.
/// </summary>
[Parameter]
public decimal? NullableValue { get; set; }
[Parameter]
public EventCallback<decimal?> NullableValueChanged { get; set; }
[Parameter]
public string Placeholder { get; set; } = "e.g., 12' 6\" or 144";
private string DisplayValue { get; set; } = string.Empty;
private bool HasError { get; set; }
private string ErrorMessage { get; set; } = string.Empty;
private decimal? _lastValue;
private bool IsNullableMode => NullableValueChanged.HasDelegate;
private decimal? CurrentValue => IsNullableMode ? NullableValue : Value;
protected override void OnParametersSet()
{
if (Value > 0 && string.IsNullOrEmpty(DisplayValue))
// Reset display when Value changes externally (e.g., form reset)
if (CurrentValue != _lastValue)
{
DisplayValue = ArchUnits.FormatFromInches((double)Value);
_lastValue = CurrentValue;
if (CurrentValue.HasValue && CurrentValue.Value > 0)
{
DisplayValue = ArchUnits.FormatFromInches((double)CurrentValue.Value);
}
else
{
DisplayValue = string.Empty;
HasError = false;
ErrorMessage = string.Empty;
}
}
}
@@ -43,7 +73,15 @@
if (string.IsNullOrWhiteSpace(input))
{
await ValueChanged.InvokeAsync(0);
_lastValue = null;
if (IsNullableMode)
{
await NullableValueChanged.InvokeAsync(null);
}
else
{
await ValueChanged.InvokeAsync(0);
}
return;
}
@@ -51,14 +89,32 @@
{
// Try to parse as architectural units
var inches = ArchUnits.ParseToInches(input);
await ValueChanged.InvokeAsync((decimal)inches);
_lastValue = (decimal)inches;
if (IsNullableMode)
{
await NullableValueChanged.InvokeAsync(_lastValue);
}
else
{
await ValueChanged.InvokeAsync(_lastValue.Value);
}
}
catch
{
// Try to parse as plain decimal (inches)
if (decimal.TryParse(input, out var decimalValue))
{
await ValueChanged.InvokeAsync(decimalValue);
_lastValue = decimalValue;
if (IsNullableMode)
{
await NullableValueChanged.InvokeAsync(decimalValue);
}
else
{
await ValueChanged.InvokeAsync(decimalValue);
}
}
else
{
@@ -68,6 +124,15 @@
}
}
private void OnBlur()
{
// Format the display value nicely on blur if we have a valid value
if (!HasError && _lastValue.HasValue && _lastValue.Value > 0)
{
DisplayValue = ArchUnits.FormatFromInches((double)_lastValue.Value);
}
}
public static string FormatLength(decimal inches)
{
return ArchUnits.FormatFromInches((double)inches);