536 lines
18 KiB
HTML
536 lines
18 KiB
HTML
<section class="section-card">
|
|
<header class="section-header">
|
|
<div>
|
|
<h2>Stock filamenti</h2>
|
|
<p>Gestione materiali, varianti e stock per il calcolatore.</p>
|
|
</div>
|
|
<button type="button" (click)="loadData()" [disabled]="loading">
|
|
Aggiorna
|
|
</button>
|
|
</header>
|
|
|
|
<div class="alerts">
|
|
<p class="error" *ngIf="errorMessage">{{ errorMessage }}</p>
|
|
<p class="success" *ngIf="successMessage">{{ successMessage }}</p>
|
|
</div>
|
|
|
|
<div class="content" *ngIf="!loading; else loadingTpl">
|
|
<section class="panel">
|
|
<div class="panel-header">
|
|
<h3>Inserimento rapido</h3>
|
|
<button
|
|
type="button"
|
|
class="panel-toggle"
|
|
(click)="toggleQuickInsertCollapsed()"
|
|
>
|
|
{{ quickInsertCollapsed ? "Espandi" : "Collassa" }}
|
|
</button>
|
|
</div>
|
|
|
|
<div *ngIf="!quickInsertCollapsed; else quickInsertCollapsedTpl">
|
|
<div class="create-grid">
|
|
<section class="subpanel">
|
|
<h4>Nuovo materiale</h4>
|
|
<div class="form-grid">
|
|
<label class="form-field form-field--wide">
|
|
<span>Codice materiale</span>
|
|
<input
|
|
type="text"
|
|
[(ngModel)]="newMaterial.materialCode"
|
|
placeholder="PLA, PETG, TPU..."
|
|
/>
|
|
</label>
|
|
<label class="form-field form-field--wide">
|
|
<span>Etichetta tecnico</span>
|
|
<input
|
|
type="text"
|
|
[(ngModel)]="newMaterial.technicalTypeLabel"
|
|
[disabled]="!newMaterial.isTechnical"
|
|
placeholder="alta temperatura, rinforzato..."
|
|
/>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="toggle-group">
|
|
<label class="toggle">
|
|
<input type="checkbox" [(ngModel)]="newMaterial.isFlexible" />
|
|
<span>Flessibile</span>
|
|
</label>
|
|
<label class="toggle">
|
|
<input type="checkbox" [(ngModel)]="newMaterial.isTechnical" />
|
|
<span>Tecnico</span>
|
|
</label>
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
(click)="createMaterial()"
|
|
[disabled]="creatingMaterial"
|
|
>
|
|
{{ creatingMaterial ? "Salvataggio..." : "Aggiungi materiale" }}
|
|
</button>
|
|
</section>
|
|
|
|
<section class="subpanel">
|
|
<h4>Nuova variante</h4>
|
|
<div class="form-grid">
|
|
<label class="form-field">
|
|
<span>Materiale</span>
|
|
<select [(ngModel)]="newVariant.materialTypeId">
|
|
<option
|
|
*ngFor="let material of materials; trackBy: trackById"
|
|
[ngValue]="material.id"
|
|
>
|
|
{{ material.materialCode }}
|
|
</option>
|
|
</select>
|
|
</label>
|
|
<label class="form-field">
|
|
<span>Nome variante</span>
|
|
<input
|
|
type="text"
|
|
[(ngModel)]="newVariant.variantDisplayName"
|
|
placeholder="PLA Nero Opaco BrandX"
|
|
/>
|
|
</label>
|
|
<label class="form-field">
|
|
<span>Colore</span>
|
|
<input
|
|
type="text"
|
|
[(ngModel)]="newVariant.colorName"
|
|
placeholder="Nero, Bianco..."
|
|
/>
|
|
</label>
|
|
<label class="form-field">
|
|
<span>Label IT</span>
|
|
<input type="text" [(ngModel)]="newVariant.colorLabelIt" />
|
|
</label>
|
|
<label class="form-field">
|
|
<span>Label EN</span>
|
|
<input type="text" [(ngModel)]="newVariant.colorLabelEn" />
|
|
</label>
|
|
<label class="form-field">
|
|
<span>Label DE</span>
|
|
<input type="text" [(ngModel)]="newVariant.colorLabelDe" />
|
|
</label>
|
|
<label class="form-field">
|
|
<span>Label FR</span>
|
|
<input type="text" [(ngModel)]="newVariant.colorLabelFr" />
|
|
</label>
|
|
<label class="form-field">
|
|
<span>Hex colore</span>
|
|
<input
|
|
type="text"
|
|
[(ngModel)]="newVariant.colorHex"
|
|
placeholder="#1A1A1A"
|
|
/>
|
|
</label>
|
|
<label class="form-field">
|
|
<span>Finitura</span>
|
|
<select [(ngModel)]="newVariant.finishType">
|
|
<option value="GLOSSY">GLOSSY</option>
|
|
<option value="MATTE">MATTE</option>
|
|
<option value="MARBLE">MARBLE</option>
|
|
<option value="SILK">SILK</option>
|
|
<option value="TRANSLUCENT">TRANSLUCENT</option>
|
|
<option value="SPECIAL">SPECIAL</option>
|
|
</select>
|
|
</label>
|
|
<label class="form-field">
|
|
<span>Brand</span>
|
|
<input
|
|
type="text"
|
|
[(ngModel)]="newVariant.brand"
|
|
placeholder="Bambu, SUNLU..."
|
|
/>
|
|
</label>
|
|
<label class="form-field">
|
|
<span>Costo CHF/kg</span>
|
|
<input
|
|
type="number"
|
|
step="0.01"
|
|
min="0"
|
|
[(ngModel)]="newVariant.costChfPerKg"
|
|
/>
|
|
</label>
|
|
<label class="form-field">
|
|
<span>Stock spool</span>
|
|
<input
|
|
type="number"
|
|
step="0.001"
|
|
min="0"
|
|
max="999.999"
|
|
[(ngModel)]="newVariant.stockSpools"
|
|
/>
|
|
</label>
|
|
<label class="form-field">
|
|
<span>Spool netto kg</span>
|
|
<input
|
|
type="number"
|
|
step="0.001"
|
|
min="0.001"
|
|
max="999.999"
|
|
[(ngModel)]="newVariant.spoolNetKg"
|
|
/>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="toggle-group">
|
|
<label class="toggle">
|
|
<input type="checkbox" [(ngModel)]="newVariant.isMatte" />
|
|
<span>Matte</span>
|
|
</label>
|
|
<label class="toggle">
|
|
<input type="checkbox" [(ngModel)]="newVariant.isSpecial" />
|
|
<span>Special</span>
|
|
</label>
|
|
<label class="toggle">
|
|
<input type="checkbox" [(ngModel)]="newVariant.isActive" />
|
|
<span>Attiva</span>
|
|
</label>
|
|
</div>
|
|
|
|
<p class="variant-meta">
|
|
Stock spools:
|
|
<strong>{{ newVariant.stockSpools | number: "1.0-3" }}</strong> |
|
|
Filamento totale:
|
|
<strong
|
|
>{{
|
|
computeStockFilamentGrams(
|
|
newVariant.stockSpools,
|
|
newVariant.spoolNetKg
|
|
) | number: "1.0-0"
|
|
}}
|
|
g</strong
|
|
>
|
|
</p>
|
|
|
|
<button
|
|
type="button"
|
|
(click)="createVariant()"
|
|
[disabled]="creatingVariant || !materials.length"
|
|
>
|
|
{{ creatingVariant ? "Salvataggio..." : "Aggiungi variante" }}
|
|
</button>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="panel">
|
|
<h3>Varianti filamento</h3>
|
|
<div class="variant-list">
|
|
<article
|
|
class="variant-row"
|
|
*ngFor="let variant of variants; trackBy: trackById"
|
|
>
|
|
<div class="variant-header">
|
|
<button
|
|
type="button"
|
|
class="expand-toggle"
|
|
(click)="toggleVariantExpanded(variant.id)"
|
|
[attr.aria-expanded]="isVariantExpanded(variant.id)"
|
|
>
|
|
{{ isVariantExpanded(variant.id) ? "▾" : "▸" }}
|
|
</button>
|
|
|
|
<div class="variant-head-main">
|
|
<strong>{{ variant.variantDisplayName }}</strong>
|
|
<div
|
|
class="variant-collapsed-summary"
|
|
*ngIf="!isVariantExpanded(variant.id)"
|
|
>
|
|
<span class="color-summary">
|
|
<span
|
|
class="color-dot"
|
|
[style.background-color]="getVariantColorHex(variant)"
|
|
></span>
|
|
{{ variant.colorLabelIt || variant.colorName || "N/D" }}
|
|
</span>
|
|
<span
|
|
>Stock spools:
|
|
{{ variant.stockSpools | number: "1.0-3" }}</span
|
|
>
|
|
<span
|
|
>Filamento:
|
|
{{
|
|
computeStockFilamentGrams(
|
|
variant.stockSpools,
|
|
variant.spoolNetKg
|
|
) | number: "1.0-0"
|
|
}}
|
|
g</span
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="variant-head-actions">
|
|
<span class="badge low" *ngIf="isLowStock(variant)"
|
|
>Stock basso</span
|
|
>
|
|
<span class="badge ok" *ngIf="!isLowStock(variant)"
|
|
>Stock ok</span
|
|
>
|
|
<button
|
|
type="button"
|
|
class="btn-delete"
|
|
(click)="openDeleteVariant(variant)"
|
|
[disabled]="deletingVariantIds.has(variant.id)"
|
|
>
|
|
{{
|
|
deletingVariantIds.has(variant.id)
|
|
? "Eliminazione..."
|
|
: "Elimina"
|
|
}}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-grid" *ngIf="isVariantExpanded(variant.id)">
|
|
<label class="form-field">
|
|
<span>Materiale</span>
|
|
<select [(ngModel)]="variant.materialTypeId">
|
|
<option
|
|
*ngFor="let material of materials; trackBy: trackById"
|
|
[ngValue]="material.id"
|
|
>
|
|
{{ material.materialCode }}
|
|
</option>
|
|
</select>
|
|
</label>
|
|
<label class="form-field">
|
|
<span>Nome variante</span>
|
|
<input type="text" [(ngModel)]="variant.variantDisplayName" />
|
|
</label>
|
|
<label class="form-field">
|
|
<span>Colore</span>
|
|
<input type="text" [(ngModel)]="variant.colorName" />
|
|
</label>
|
|
<label class="form-field">
|
|
<span>Label IT</span>
|
|
<input type="text" [(ngModel)]="variant.colorLabelIt" />
|
|
</label>
|
|
<label class="form-field">
|
|
<span>Label EN</span>
|
|
<input type="text" [(ngModel)]="variant.colorLabelEn" />
|
|
</label>
|
|
<label class="form-field">
|
|
<span>Label DE</span>
|
|
<input type="text" [(ngModel)]="variant.colorLabelDe" />
|
|
</label>
|
|
<label class="form-field">
|
|
<span>Label FR</span>
|
|
<input type="text" [(ngModel)]="variant.colorLabelFr" />
|
|
</label>
|
|
<label class="form-field">
|
|
<span>Hex colore</span>
|
|
<input type="text" [(ngModel)]="variant.colorHex" />
|
|
</label>
|
|
<label class="form-field">
|
|
<span>Finitura</span>
|
|
<select [(ngModel)]="variant.finishType">
|
|
<option value="GLOSSY">GLOSSY</option>
|
|
<option value="MATTE">MATTE</option>
|
|
<option value="MARBLE">MARBLE</option>
|
|
<option value="SILK">SILK</option>
|
|
<option value="TRANSLUCENT">TRANSLUCENT</option>
|
|
<option value="SPECIAL">SPECIAL</option>
|
|
</select>
|
|
</label>
|
|
<label class="form-field">
|
|
<span>Brand</span>
|
|
<input type="text" [(ngModel)]="variant.brand" />
|
|
</label>
|
|
<label class="form-field">
|
|
<span>Costo CHF/kg</span>
|
|
<input
|
|
type="number"
|
|
step="0.01"
|
|
min="0"
|
|
[(ngModel)]="variant.costChfPerKg"
|
|
/>
|
|
</label>
|
|
<label class="form-field">
|
|
<span>Stock spool</span>
|
|
<input
|
|
type="number"
|
|
step="0.001"
|
|
min="0"
|
|
max="999.999"
|
|
[(ngModel)]="variant.stockSpools"
|
|
/>
|
|
</label>
|
|
<label class="form-field">
|
|
<span>Spool netto kg</span>
|
|
<input
|
|
type="number"
|
|
step="0.001"
|
|
min="0.001"
|
|
max="999.999"
|
|
[(ngModel)]="variant.spoolNetKg"
|
|
/>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="toggle-group" *ngIf="isVariantExpanded(variant.id)">
|
|
<label class="toggle">
|
|
<input type="checkbox" [(ngModel)]="variant.isMatte" />
|
|
<span>Matte</span>
|
|
</label>
|
|
<label class="toggle">
|
|
<input type="checkbox" [(ngModel)]="variant.isSpecial" />
|
|
<span>Special</span>
|
|
</label>
|
|
<label class="toggle">
|
|
<input type="checkbox" [(ngModel)]="variant.isActive" />
|
|
<span>Attiva</span>
|
|
</label>
|
|
</div>
|
|
|
|
<p class="variant-meta" *ngIf="isVariantExpanded(variant.id)">
|
|
Stock spools:
|
|
<strong>{{ variant.stockSpools | number: "1.0-3" }}</strong> |
|
|
Filamento totale:
|
|
<strong
|
|
>{{
|
|
computeStockFilamentGrams(
|
|
variant.stockSpools,
|
|
variant.spoolNetKg
|
|
) | number: "1.0-0"
|
|
}}
|
|
g</strong
|
|
>
|
|
</p>
|
|
|
|
<button
|
|
type="button"
|
|
*ngIf="isVariantExpanded(variant.id)"
|
|
(click)="saveVariant(variant)"
|
|
[disabled]="savingVariantIds.has(variant.id)"
|
|
>
|
|
{{
|
|
savingVariantIds.has(variant.id)
|
|
? "Salvataggio..."
|
|
: "Salva variante"
|
|
}}
|
|
</button>
|
|
</article>
|
|
</div>
|
|
<p class="muted" *ngIf="variants.length === 0">
|
|
Nessuna variante configurata.
|
|
</p>
|
|
</section>
|
|
|
|
<section class="panel">
|
|
<div class="panel-header">
|
|
<h3>Materiali</h3>
|
|
<button
|
|
type="button"
|
|
class="panel-toggle"
|
|
(click)="toggleMaterialsCollapsed()"
|
|
>
|
|
{{ materialsCollapsed ? "Espandi" : "Collassa" }}
|
|
</button>
|
|
</div>
|
|
|
|
<div *ngIf="!materialsCollapsed; else materialsCollapsedTpl">
|
|
<div class="material-grid">
|
|
<article
|
|
class="material-card"
|
|
*ngFor="let material of materials; trackBy: trackById"
|
|
>
|
|
<div class="form-grid">
|
|
<label class="form-field form-field--wide">
|
|
<span>Codice</span>
|
|
<input type="text" [(ngModel)]="material.materialCode" />
|
|
</label>
|
|
<label class="form-field form-field--wide">
|
|
<span>Etichetta tecnico</span>
|
|
<input
|
|
type="text"
|
|
[(ngModel)]="material.technicalTypeLabel"
|
|
[disabled]="!material.isTechnical"
|
|
/>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="toggle-group">
|
|
<label class="toggle">
|
|
<input type="checkbox" [(ngModel)]="material.isFlexible" />
|
|
<span>Flessibile</span>
|
|
</label>
|
|
<label class="toggle">
|
|
<input type="checkbox" [(ngModel)]="material.isTechnical" />
|
|
<span>Tecnico</span>
|
|
</label>
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
(click)="saveMaterial(material)"
|
|
[disabled]="savingMaterialIds.has(material.id)"
|
|
>
|
|
{{
|
|
savingMaterialIds.has(material.id)
|
|
? "Salvataggio..."
|
|
: "Salva materiale"
|
|
}}
|
|
</button>
|
|
</article>
|
|
</div>
|
|
<p class="muted" *ngIf="materials.length === 0">
|
|
Nessun materiale configurato.
|
|
</p>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</section>
|
|
|
|
<ng-template #loadingTpl>
|
|
<p>Caricamento filamenti...</p>
|
|
</ng-template>
|
|
|
|
<ng-template #materialsCollapsedTpl>
|
|
<p class="muted">Sezione collassata ({{ materials.length }} materiali).</p>
|
|
</ng-template>
|
|
|
|
<ng-template #quickInsertCollapsedTpl>
|
|
<p class="muted">Sezione collassata.</p>
|
|
</ng-template>
|
|
|
|
<div
|
|
class="dialog-backdrop"
|
|
*ngIf="variantToDelete"
|
|
(click)="closeDeleteVariantDialog()"
|
|
></div>
|
|
<div class="confirm-dialog" *ngIf="variantToDelete">
|
|
<h4>Sei sicuro?</h4>
|
|
<p>
|
|
Vuoi eliminare la variante
|
|
<strong>{{ variantToDelete.variantDisplayName }}</strong
|
|
>?
|
|
</p>
|
|
<p class="muted">L'operazione non è reversibile.</p>
|
|
<div class="dialog-actions">
|
|
<button
|
|
type="button"
|
|
class="btn-secondary"
|
|
(click)="closeDeleteVariantDialog()"
|
|
>
|
|
Annulla
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="btn-delete"
|
|
(click)="confirmDeleteVariant()"
|
|
[disabled]="variantToDelete && deletingVariantIds.has(variantToDelete.id)"
|
|
>
|
|
{{
|
|
variantToDelete && deletingVariantIds.has(variantToDelete.id)
|
|
? "Eliminazione..."
|
|
: "Conferma elimina"
|
|
}}
|
|
</button>
|
|
</div>
|
|
</div>
|