This commit is contained in:
@@ -1,119 +1,147 @@
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
|
||||
<div class="create-product-container">
|
||||
<EditForm Model="@ProductModel" OnValidSubmit="HandleValidSubmit" class="form-entry-canvas">
|
||||
<DataAnnotationsValidator />
|
||||
<div class="create-product-shell">
|
||||
|
||||
<div class="form-scroll-viewport">
|
||||
<div class="form-section-header">
|
||||
<span class="panel-title-lbl field-accent-tag">Initialization Sequence</span>
|
||||
<p class="text-muted">Register a new asset node into the core product catalog matrix.</p>
|
||||
<div class="book-preview-drawer @(string.IsNullOrEmpty(ActivePreviewUrl) ? "" : "is-open")">
|
||||
@if (!string.IsNullOrEmpty(ActivePreviewUrl))
|
||||
{
|
||||
<button type="button" class="btn-close-preview-floating" title="Collapse Frame" @onclick="ClosePreviewDrawer">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="drawer-portrait-frame">
|
||||
<img src="@ActivePreviewUrl" alt="Active Book Design Preview" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="form-grid-layout">
|
||||
<div class="create-product-container">
|
||||
<EditForm Model="@ProductModel" OnValidSubmit="HandleValidSubmit" class="form-entry-canvas">
|
||||
<DataAnnotationsValidator />
|
||||
|
||||
<div class="form-column">
|
||||
<div class="form-scroll-viewport">
|
||||
|
||||
<div class="form-section-header">
|
||||
<span class="field-accent-tag">PRODUCT MASTER LEDGER</span>
|
||||
<p>Provision catalog items metadata, book cover assets, and supplementary chapter telemetry designs.</p>
|
||||
</div>
|
||||
|
||||
<div class="form-text-inputs-section">
|
||||
<div class="console-field-group">
|
||||
<label class="console-field-label">Product Name</label>
|
||||
<InputText @bind-Value="ProductModel.Name" placeholder="e.g., Quantum Link Core Subsystem" class="console-input" />
|
||||
<label class="console-field-label">Book Title</label>
|
||||
<InputText @bind-Value="ProductModel.Name" class="console-input" placeholder="e.g., Neuromancer" />
|
||||
<ValidationMessage For="@(() => ProductModel.Name)" style="color: #ff5722; font-size: 0.75rem;" />
|
||||
</div>
|
||||
|
||||
<div class="console-field-group">
|
||||
<label class="console-field-label">Telemetry Short Summary</label>
|
||||
<InputText @bind-Value="ProductModel.Summary" placeholder="Brief technical summary tooltip description..." class="console-input" />
|
||||
<label class="console-field-label">Short Summary</label>
|
||||
<InputText @bind-Value="ProductModel.Summary" class="console-input" placeholder="Brief catchphrase description metadata line..." />
|
||||
<ValidationMessage For="@(() => ProductModel.Summary)" style="color: #ff5722; font-size: 0.75rem;" />
|
||||
</div>
|
||||
|
||||
<div class="console-field-group">
|
||||
<label class="console-field-label">Deep System Description</label>
|
||||
<InputTextArea @bind-Value="ProductModel.Description" rows="5" placeholder="Provide raw configuration guidelines, catalog notes, or full logistical parameters..." class="console-textarea" />
|
||||
<label class="console-field-label">Base Ledger Price (ZAR)</label>
|
||||
<InputNumber @bind-Value="ProductModel.Price" class="console-input" placeholder="0.00" />
|
||||
<ValidationMessage For="@(() => ProductModel.Price)" style="color: #ff5722; font-size: 0.75rem;" />
|
||||
</div>
|
||||
|
||||
<div class="console-field-group">
|
||||
<label class="console-field-label">Full Catalog Description</label>
|
||||
<InputTextArea @bind-Value="ProductModel.Description" class="console-textarea" rows="4" placeholder="Enter extended markdown contents..." />
|
||||
<ValidationMessage For="@(() => ProductModel.Description)" style="color: #ff5722; font-size: 0.75rem;" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-column">
|
||||
<div class="console-field-group">
|
||||
<label class="console-field-label">Primary Engine Asset (Image URL)</label>
|
||||
<div class="input-asset-addon">
|
||||
<InputText @bind-Value="ProductModel.ImageUrl" placeholder="https://assets.litecharms.internal/nodes/primary.png" class="console-input asset-path-input" />
|
||||
<div class="asset-preview-stub">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2" /><circle cx="8.5" cy="8.5" r="1.5" /><polyline points="21 15 16 10 5 21" /></svg>
|
||||
<div class="form-media-deck-section">
|
||||
<div class="form-section-header" style="margin-bottom: 1rem; padding-bottom: 0.5rem;">
|
||||
<p style="text-transform: uppercase; font-weight: 600; color: #cbd5e1; font-size: 0.8rem; letter-spacing: 0.05em;">Media Assets Node Array</p>
|
||||
</div>
|
||||
|
||||
<div class="media-deck-row">
|
||||
|
||||
<div class="console-field-group">
|
||||
<label class="console-field-label">Primary Cover</label>
|
||||
<div class="book-cover-dropzone">
|
||||
<InputFile OnChange="HandleMainImageUpload" accept=".png,.jpg,.jpeg,.webp" class="hidden-file-input" id="main-image-file" />
|
||||
|
||||
@if (string.IsNullOrEmpty(ProductModel.ImageUrl))
|
||||
{
|
||||
/* Clicking anywhere inside this label launches the file system picker */
|
||||
<label for="main-image-file" class="dropzone-interactive-layer">
|
||||
<div class="empty-slot-blueprint">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<line x1="12" y1="5" x2="12" y2="19"></line>
|
||||
<line x1="5" y1="12" x2="19" y2="12"></line>
|
||||
</svg>
|
||||
<span style="font-size: 0.7rem; font-family: monospace; color: #475569; margin-top: 0.5rem;">UPLOAD COVER</span>
|
||||
</div>
|
||||
</label>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="dropzone-active-preview">
|
||||
<img src="@ProductModel.ImageUrl" alt="Main Book Cover" />
|
||||
|
||||
<div class="image-actions-overlay">
|
||||
<button type="button" class="btn-micro-action" title="Preview Image" @onclick="() => SetPreviewActive(ProductModel.ImageUrl)">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>
|
||||
</button>
|
||||
<button type="button" class="btn-micro-action danger" title="Remove Asset" @onclick="ClearMainImage">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="console-field-group spacing-top-modifier">
|
||||
<label class="console-field-label">
|
||||
Telemetry Media Thumbnails <span class="text-mono node-dim-label">(Max 5 Slots)</span>
|
||||
</label>
|
||||
<div class="console-field-group">
|
||||
<label class="console-field-label">Additional Media Slots</label>
|
||||
<div class="thumbnail-deck-grid">
|
||||
@for (int i = 0; i < 5; i++)
|
||||
{
|
||||
var index = i;
|
||||
<div class="thumbnail-slot-node @(HasAssetAt(index) ? "populated" : "empty")">
|
||||
@if (HasAssetAt(index))
|
||||
{
|
||||
<img src="@ProductModel.Thumbnails[index]" alt="Slot @(index + 1)" />
|
||||
|
||||
<div class="thumbnail-deck-grid">
|
||||
@for (int i = 0; i < 5; i++)
|
||||
{
|
||||
var index = i;
|
||||
<div class="thumbnail-slot-node @(HasAssetAt(index) ? "populated" : "empty")">
|
||||
@if (HasAssetAt(index))
|
||||
{
|
||||
<img src="@ProductModel.Thumbnails[index]" alt="Slot @(index + 1)" />
|
||||
<button type="button" class="btn-clear-slot" @onclick="() => RemoveThumbnailAt(index)">✕</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="empty-slot-blueprint">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>
|
||||
<span class="text-mono">0@(index + 1)</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div class="image-actions-overlay">
|
||||
<button type="button" class="btn-micro-action" title="Preview Image" @onclick="() => SetPreviewActive(ProductModel.Thumbnails[index])">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>
|
||||
</button>
|
||||
<button type="button" class="btn-micro-action danger" title="Remove Asset" @onclick="() => RemoveThumbnailAt(index)">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Clean hidden execution context matched back to label action surfaces */
|
||||
<InputFile OnChange="@(e => HandleThumbnailUpload(e, index))" accept=".png,.jpg,.jpeg,.webp" class="hidden-file-input" id="@($"thumb-file-{index}")" />
|
||||
<label for="@($"thumb-file-{index}")" class="empty-slot-blueprint">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
||||
<line x1="12" y1="5" x2="12" y2="19"></line>
|
||||
<line x1="5" y1="12" x2="19" y2="12"></line>
|
||||
</svg>
|
||||
<span style="font-size: 0.65rem; font-family: monospace; margin-top: 0.25rem;">0@(index + 1)</span>
|
||||
</label>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-action-footer">
|
||||
<button type="button" class="btn-console-flat">Abort Registers</button>
|
||||
<button type="submit" class="btn-apply-filters">Commit Node to Ledger</button>
|
||||
</div>
|
||||
</EditForm>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private Product ProductModel { get; set; } = new();
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
ProductModel.Active = true;
|
||||
ProductModel.Thumbnails ??= new string[0];
|
||||
}
|
||||
|
||||
private bool HasAssetAt(int index) =>
|
||||
ProductModel.Thumbnails != null && index < ProductModel.Thumbnails.Length && !string.IsNullOrWhiteSpace(ProductModel.Thumbnails[index]);
|
||||
|
||||
private void RemoveThumbnailAt(int index)
|
||||
{
|
||||
if (ProductModel.Thumbnails == null) return;
|
||||
var list = ProductModel.Thumbnails.ToList();
|
||||
if (index < list.Count)
|
||||
{
|
||||
list.RemoveAt(index);
|
||||
ProductModel.Thumbnails = list.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleValidSubmit()
|
||||
{
|
||||
// Save operation business logic goes here
|
||||
}
|
||||
|
||||
public class Product
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public string? Name { get; set; }
|
||||
public string? Summary { get; set; }
|
||||
public string? Description { get; set; }
|
||||
public string? ImageUrl { get; set; }
|
||||
public string[]? Thumbnails { get; set; }
|
||||
public bool Active { get; set; }
|
||||
}
|
||||
}
|
||||
<div class="form-action-footer">
|
||||
<button type="submit" class="btn-apply-filters">Commit Record Ledger</button>
|
||||
</div>
|
||||
</EditForm>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user