Files
Extrudex/frontend/src/app/components/filament-dialog/filament-dialog.component.html
dex-bot fbddcbdbc5
Some checks failed
Dev Build / build-test (pull_request) Failing after 51s
Dev Build / deploy-dev (pull_request) Has been skipped
Dev Build / notify-success (pull_request) Has been skipped
Dev Build / notify-failure (pull_request) Successful in 3s
CUB-35: build add/edit filament modal with Angular Material Dialog
2026-04-27 14:47:16 -04:00

225 lines
10 KiB
HTML

<!-- Filament Add/Edit Dialog — Angular Material Dialog -->
<mat-dialog-content class="filament-dialog-content">
<!-- Dialog Title -->
<h2 mat-dialog-title>{{ dialogTitle() }}</h2>
<!-- Loading state for lookup data -->
@if (lookupsLoading()) {
<div class="dialog-loading" role="status" aria-label="Loading material options">
<mat-spinner diameter="32"></mat-spinner>
<p>Loading material options…</p>
</div>
}
<!-- Form -->
@if (!lookupsLoading()) {
<form [formGroup]="form" class="filament-form" (ngSubmit)="save()">
<!-- Server Error Banner -->
@if (serverError()) {
<div class="error-banner" role="alert">
<mat-icon aria-hidden="true">error</mat-icon>
<span>{{ serverError() }}</span>
</div>
}
<!-- ── Material Section ──────────────────────────────── -->
<div class="form-section">
<h3 class="section-title">Material</h3>
<!-- Base Material -->
<mat-form-field appearance="outline" class="form-field full-width">
<mat-label>Base Material</mat-label>
<mat-select formControlName="materialBaseId" required aria-label="Base material">
@for (base of materialBases(); track base.id) {
<mat-option [value]="base.id">{{ base.name }}</mat-option>
}
</mat-select>
@if (form.get('materialBaseId')!.hasError('required') && form.get('materialBaseId')!.touched) {
<mat-error>Base material is required</mat-error>
}
</mat-form-field>
<!-- Finish -->
<mat-form-field appearance="outline" class="form-field full-width">
<mat-label>Finish</mat-label>
<mat-select formControlName="materialFinishId" required aria-label="Material finish">
<mat-option [value]="''" disabled>Select a base material first</mat-option>
@for (finish of filteredFinishes(); track finish.id) {
<mat-option [value]="finish.id">{{ finish.name }}</mat-option>
}
</mat-select>
@if (form.get('materialFinishId')!.hasError('required') && form.get('materialFinishId')!.touched) {
<mat-error>Finish is required</mat-error>
}
@if (filteredFinishes().length === 0 && form.get('materialBaseId')!.value) {
<mat-hint>No finishes available for this material</mat-hint>
}
</mat-form-field>
<!-- Modifier (optional) -->
<mat-form-field appearance="outline" class="form-field full-width">
<mat-label>Modifier (optional)</mat-label>
<mat-select formControlName="materialModifierId" aria-label="Material modifier">
<mat-option [value]="null">None</mat-option>
@for (modifier of filteredModifiers(); track modifier.id) {
<mat-option [value]="modifier.id">{{ modifier.name }}</mat-option>
}
</mat-select>
@if (filteredModifiers().length === 0 && form.get('materialBaseId')!.value) {
<mat-hint>No modifiers available for this material</mat-hint>
}
</mat-form-field>
</div>
<!-- ── Spool Details Section ──────────────────────────── -->
<div class="form-section">
<h3 class="section-title">Spool Details</h3>
<!-- Brand -->
<mat-form-field appearance="outline" class="form-field full-width">
<mat-label>Brand</mat-label>
<input matInput formControlName="brand" required maxlength="200"
placeholder="e.g., Bambu Lab, Polymaker" aria-label="Brand" />
@if (form.get('brand')!.hasError('required') && form.get('brand')!.touched) {
<mat-error>Brand is required</mat-error>
}
</mat-form-field>
<!-- Serial -->
<mat-form-field appearance="outline" class="form-field full-width">
<mat-label>Serial Number</mat-label>
<input matInput formControlName="spoolSerial" required maxlength="200"
placeholder="e.g., SN-001" aria-label="Serial number" />
@if (form.get('spoolSerial')!.hasError('required') && form.get('spoolSerial')!.touched) {
<mat-error>Serial number is required</mat-error>
}
</mat-form-field>
<!-- Color Name + Color Hex (side by side) -->
<div class="form-row">
<mat-form-field appearance="outline" class="form-field">
<mat-label>Color Name</mat-label>
<input matInput formControlName="colorName" required maxlength="200"
placeholder="e.g., Fire Engine Red" aria-label="Color name" />
@if (form.get('colorName')!.hasError('required') && form.get('colorName')!.touched) {
<mat-error>Color name is required</mat-error>
}
</mat-form-field>
<mat-form-field appearance="outline" class="form-field color-hex-field">
<mat-label>Color Hex</mat-label>
<input matInput formControlName="colorHex" required
placeholder="#FF0000" maxlength="7" aria-label="Color hex code" />
<span matTextSuffix class="color-preview">
<span class="color-swatch-mini" [style.background-color]="form.get('colorHex')!.value"></span>
</span>
@if (form.get('colorHex')!.hasError('required') && form.get('colorHex')!.touched) {
<mat-error>Color hex is required</mat-error>
}
@if (form.get('colorHex')!.hasError('pattern') && form.get('colorHex')!.touched) {
<mat-error>Must be #RRGGBB format</mat-error>
}
</mat-form-field>
</div>
</div>
<!-- ── Weight & Dimensions Section ────────────────────── -->
<div class="form-section">
<h3 class="section-title">Weight &amp; Dimensions</h3>
<div class="form-row">
<!-- Diameter -->
<mat-form-field appearance="outline" class="form-field">
<mat-label>Diameter (mm)</mat-label>
<input matInput type="number" formControlName="filamentDiameterMm" required
min="0.1" max="10" step="0.01" aria-label="Filament diameter in mm" />
@if (form.get('filamentDiameterMm')!.hasError('required') && form.get('filamentDiameterMm')!.touched) {
<mat-error>Diameter is required</mat-error>
}
</mat-form-field>
</div>
<div class="form-row">
<!-- Total Weight -->
<mat-form-field appearance="outline" class="form-field">
<mat-label>Total Weight (g)</mat-label>
<input matInput type="number" formControlName="weightTotalGrams" required
min="0.01" max="100000" step="1" aria-label="Total spool weight in grams" />
<mat-hint>Full spool weight</mat-hint>
@if (form.get('weightTotalGrams')!.hasError('required') && form.get('weightTotalGrams')!.touched) {
<mat-error>Total weight is required</mat-error>
}
</mat-form-field>
<!-- Remaining Weight -->
<mat-form-field appearance="outline" class="form-field">
<mat-label>Remaining Weight (g)</mat-label>
<input matInput type="number" formControlName="weightRemainingGrams" required
min="0" max="100000" step="1" aria-label="Remaining weight in grams" />
<mat-hint>Current remaining</mat-hint>
@if (form.get('weightRemainingGrams')!.hasError('required') && form.get('weightRemainingGrams')!.touched) {
<mat-error>Remaining weight is required</mat-error>
}
@if (form.get('weightRemainingGrams')!.hasError('exceedsTotal')) {
<mat-error>Cannot exceed total weight</mat-error>
}
</mat-form-field>
</div>
</div>
<!-- ── Purchase & Status Section ──────────────────────── -->
<div class="form-section">
<h3 class="section-title">Purchase &amp; Status</h3>
<div class="form-row">
<!-- Purchase Price -->
<mat-form-field appearance="outline" class="form-field">
<mat-label>Price</mat-label>
<input matInput type="number" formControlName="purchasePrice"
min="0" max="1000000" step="0.01"
placeholder="e.g., 25.00" aria-label="Purchase price" />
<span matTextSuffix>$</span>
@if (form.get('purchasePrice')!.hasError('min') && form.get('purchasePrice')!.touched) {
<mat-error>Price must be non-negative</mat-error>
}
</mat-form-field>
<!-- Purchase Date -->
<mat-form-field appearance="outline" class="form-field">
<mat-label>Purchase Date</mat-label>
<input matInput [matDatepicker]="picker" formControlName="purchaseDate"
aria-label="Purchase date" />
<mat-datepicker-toggle matIconSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>
</div>
<!-- Active Status -->
<div class="checkbox-row">
<mat-checkbox formControlName="isActive" aria-label="Active status">
Spool is active and available for use
</mat-checkbox>
</div>
</div>
</form>
}
</mat-dialog-content>
<!-- Dialog Actions -->
<mat-dialog-actions align="end">
<button mat-button type="button" (click)="cancel()" [disabled]="saving()"
aria-label="Cancel">
Cancel
</button>
<button mat-raised-button color="primary" type="button" (click)="save()"
[disabled]="saving() || form.invalid" aria-label="Save filament">
@if (saving()) {
<mat-spinner diameter="20" class="btn-spinner"></mat-spinner>
}
{{ isEditMode() ? 'Save Changes' : 'Add Filament' }}
</button>
</mat-dialog-actions>