refactor: redesign onboarding wizard UI — inline step progress, improved layout

Redesign all 6 onboarding steps with inline step indicators replacing
the fixed sidebar layout. Simplified structure with admin-content/admin-panel
pattern for consistency with other admin pages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ho Ngoc Hai
2026-03-20 16:42:10 +07:00
parent 3c43ca519e
commit ca022de832
6 changed files with 625 additions and 583 deletions

View File

@@ -3,119 +3,134 @@
@inherits AdminBase
@*
EN: Onboarding Step 1 — Business information (name, tax ID, address, logo upload).
VI: Bước 1 thiết lập — Thông tin doanh nghiệp (tên, MST, địa chỉ, logo).
EN: Onboarding Step 1 — Business info (name, type, tax ID, address, logo upload)
VI: Bước 1 thiết lập — Thông tin doanh nghiệp (tên, loại hình, MST, địa chỉ, logo)
Design: pencil-design/src/pages/tPOS/admin/onboarding-business.pen
*@
<PageTitle>Thiết lập doanh nghiệp — GoodGo Admin</PageTitle>
<div style="display:flex;height:100vh;overflow:hidden;">
<div class="admin-content" style="padding:40px;">
<div class="admin-panel" style="max-width:980px;margin:0 auto;display:flex;flex-direction:column;gap:32px;">
@* ═══ SIDEBAR — Step Progress ═══ *@
<div style="width:260px;min-width:260px;background-color:var(--admin-bg-elevated);border-right:1px solid var(--admin-border-subtle);display:flex;flex-direction:column;height:100vh;">
@* Logo *@
<div style="display:flex;align-items:center;gap:12px;padding:24px;border-bottom:1px solid var(--admin-border-subtle);">
<div style="width:40px;height:40px;border-radius:12px;background:var(--admin-orange-gradient);display:flex;align-items:center;justify-content:center;color:#FFF;font-size:20px;font-weight:800;">G</div>
<div style="display:flex;flex-direction:column;gap:2px;">
<span style="font-size:16px;font-weight:700;">GoodGo Admin</span>
<span style="font-size:11px;color:var(--admin-text-tertiary);">Thiết lập ban đầu</span>
</div>
</div>
@* Steps *@
<div style="flex:1;padding:32px 24px;display:flex;flex-direction:column;">
<span style="font-size:10px;font-weight:700;color:var(--admin-text-tertiary);letter-spacing:0.05em;margin-bottom:16px;">TIẾN TRÌNH</span>
@foreach (var s in _steps)
<section>
<div style="display:flex;gap:8px;align-items:center;margin-bottom:12px;">
@for (int i = 1; i <= 6; i++)
{
var step = s;
<div style="display:flex;align-items:center;gap:14px;padding:10px 8px;border-radius:10px;
background-color:@(step.Index == 1 ? "rgba(255,92,0,0.08)" : "transparent");">
<div style="width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:700;
background-color:@(step.Index == 1 ? "var(--admin-orange-primary)" : "var(--admin-bg-interactive)");
color:@(step.Index == 1 ? "#FFF" : "var(--admin-text-tertiary)");
border:@(step.Index > 1 ? "2px solid var(--admin-border-default)" : "none");">
@step.Index
<div style="width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px;
background-color:@(i == currentStep ? "var(--admin-orange-primary)" : i < currentStep ? "rgba(34,197,94,0.2)" : "var(--admin-bg-interactive)");
color:@(i == currentStep ? "#FFF" : i < currentStep ? "#22C55E" : "var(--admin-text-tertiary)");">
@(i < currentStep ? "✓" : i)
</div>
<span style="font-size:14px;font-weight:@(step.Index == 1 ? "600" : "500");color:@(step.Index == 1 ? "var(--admin-orange-primary)" : "var(--admin-text-tertiary)");">@step.Label</span>
</div>
@if (step.Index < 6)
@if (i < 6)
{
<div style="width:2px;height:24px;background-color:var(--admin-border-default);margin-left:23px;"></div>
<div style="flex:1;height:2px;background-color:@(i < currentStep ? "#22C55E" : "var(--admin-border-subtle)");"></div>
}
}
</div>
<div style="display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:6px;margin-bottom:6px;">
@for (int i = 1; i <= 6; i++)
{
<span style="text-align:center;font-size:12px;font-weight:@(i == currentStep ? "600" : "500");color:@(i == currentStep ? "var(--admin-orange-primary)" : "var(--admin-text-tertiary)");">
@_steps[i - 1].Label
</span>
}
</div>
<p style="font-size:12px;color:var(--admin-text-tertiary);margin:0;">Bước 1/6 — Nhập thông tin sơ bộ để GoodGo hiểu doanh nghiệp của bạn</p>
</section>
<section class="admin-panel" style="padding:32px;display:flex;flex-direction:column;gap:24px;">
<div style="display:flex;flex-direction:column;gap:6px;">
<h2 style="font-size:24px;margin:0;font-weight:700;">Thông tin doanh nghiệp</h2>
<p style="margin:0;font-size:14px;color:var(--admin-text-tertiary);">Giúp chúng tôi biết bạn bán gì và ở đâu.</p>
</div>
@* ═══ MAIN CONTENT ═══ *@
<div style="flex:1;display:flex;align-items:center;justify-content:center;padding:40px;overflow-y:auto;">
<div class="admin-panel" style="width:720px;max-width:100%;padding:32px;border-radius:16px;display:flex;flex-direction:column;gap:24px;">
@* Header *@
<div style="display:flex;flex-direction:column;gap:8px;">
<h2 style="font-size:22px;font-weight:700;margin:0;">Thông tin doanh nghiệp</h2>
<p style="font-size:14px;color:var(--admin-text-tertiary);margin:0;">Nhập thông tin cơ bản về doanh nghiệp của bạn</p>
</div>
@* Row 1: Business name + Tax ID *@
<div style="display:flex;gap:20px;">
<div class="admin-form-group" style="flex:1;">
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;">
<div class="admin-form-group">
<label class="admin-form-label">Tên doanh nghiệp <span style="color:var(--admin-danger);">*</span></label>
<input class="admin-form-input" type="text" placeholder="VD: Công ty TNHH ABC" @bind="_businessName" />
<input class="admin-form-input" type="text" @bind="_businessName" />
</div>
<div class="admin-form-group" style="flex:1;">
<div class="admin-form-group">
<label class="admin-form-label">Mã số thuế</label>
<input class="admin-form-input" type="text" placeholder="VD: 0312345678" @bind="_taxId" />
<input class="admin-form-input" type="text" @bind="_taxId" />
</div>
</div>
@* Row 2: Address *@
<div class="admin-form-group">
<label class="admin-form-label">Địa chỉ trụ sở <span style="color:var(--admin-danger);">*</span></label>
<input class="admin-form-input" type="text" placeholder="Nhập địa chỉ trụ sở chính" @bind="_address" />
<label class="admin-form-label">Địa chỉ trụ sở</label>
<input class="admin-form-input" type="text" @bind="_address" />
</div>
@* Row 3: Phone + Email *@
<div style="display:flex;gap:20px;">
<div class="admin-form-group" style="flex:1;">
<label class="admin-form-label">Số điện thoại <span style="color:var(--admin-danger);">*</span></label>
<input class="admin-form-input" type="tel" placeholder="VD: 028 1234 5678" @bind="_phone" />
</div>
<div class="admin-form-group" style="flex:1;">
<label class="admin-form-label">Email</label>
<input class="admin-form-input" type="email" placeholder="contact@company.com" @bind="_email" />
</div>
</div>
@* Row 4: Logo upload *@
<div class="admin-form-group">
<label class="admin-form-label">Số điện thoại</label>
<input class="admin-form-input" type="tel" @bind="_phone" />
</div>
<div class="admin-form-group">
<label class="admin-form-label">Email liên hệ</label>
<input class="admin-form-input" type="email" @bind="_email" />
</div>
</div>
<div>
<label class="admin-form-label">Loại hình kinh doanh</label>
<div style="display:flex;flex-wrap:wrap;gap:12px;padding-top:8px;">
@foreach (var type in _businessTypes)
{
<button type="button" class="admin-btn-secondary" style="background-color:@(type.Key == _businessType ? "var(--admin-orange-primary)" : "var(--admin-bg-interactive)");
color:@(type.Key == _businessType ? "#FFF" : "var(--admin-text-primary)");border:1px solid var(--admin-border-default);min-width:140px;display:flex;flex-direction:column;gap:4px;padding:14px;"
@onclick="() => _businessType = type.Key">
<span style="font-size:14px;font-weight:600;">@type.Label</span>
<span style="font-size:12px;color:@(type.Key == _businessType ? "rgba(255,255,255,0.8)" : "var(--admin-text-tertiary)");">@type.Description</span>
</button>
}
</div>
</div>
<div style="display:flex;flex-wrap:wrap;gap:20px;">
<div style="flex:1;min-width:220px;">
<label class="admin-form-label">Logo doanh nghiệp</label>
<div style="height:100px;border:2px dashed var(--admin-border-default);border-radius:10px;background-color:var(--admin-bg-interactive);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;cursor:pointer;">
<div style="height:120px;border:2px dashed var(--admin-border-default);border-radius:14px;background-color:var(--admin-bg-interactive);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;">
<i data-lucide="upload" style="width:28px;height:28px;color:var(--admin-text-tertiary);"></i>
<span style="font-size:13px;color:var(--admin-text-tertiary);">Kéo thả hoặc nhấn để tải logo</span>
<span style="font-size:11px;color:var(--admin-text-tertiary);">PNG, JPG tối đa 2MB</span>
<span style="font-size:13px;color:var(--admin-text-tertiary);">Kéo thả hoặc nhấn để chọn logo</span>
<span style="font-size:12px;color:var(--admin-text-secondary);">@_logoPlaceholder</span>
</div>
</div>
<div style="flex:1;min-width:220px;border-radius:14px;background-color:var(--admin-bg-interactive);padding:20px;display:flex;flex-direction:column;gap:8px;">
<span style="font-size:14px;font-weight:600;">Tóm tắt nhanh</span>
<div style="display:flex;justify-content:space-between;font-size:13px;color:var(--admin-text-tertiary);">
<span>Loại hình</span>
<span>@GetBusinessTypeLabel()</span>
</div>
<div style="display:flex;justify-content:space-between;font-size:13px;color:var(--admin-text-tertiary);">
<span>Địa chỉ</span>
<span>@_address</span>
</div>
<div style="display:flex;justify-content:space-between;font-size:13px;color:var(--admin-text-tertiary);">
<span>Liên hệ</span>
<span>@_phone</span>
</div>
</div>
</div>
@* Footer *@
<div style="display:flex;justify-content:flex-end;padding-top:8px;">
<button class="admin-btn-primary" @onclick="@(() => NavigateTo("onboarding/store"))">
<div style="display:flex;justify-content:flex-end;">
<button class="admin-btn-primary" @onclick='() => NavigateTo("onboarding/store")' style="display:flex;align-items:center;gap:8px;">
<span>Tiếp tục</span>
<i data-lucide="arrow-right"></i>
</button>
</div>
</div>
</section>
</div>
</div>
@code {
private string _businessName = "";
private string _taxId = "";
private string _address = "";
private string _phone = "";
private string _email = "";
private int currentStep = 1;
private string _businessName = "GoodGo Bistro";
private string _taxId = "0312345678";
private string _address = "123 Nguyễn Huệ, Quận 1, TP.HCM";
private string _phone = "028 7300 1234";
private string _email = "contact@goodgo.vn";
private string _businessType = "fnb";
private string _logoPlaceholder = "Chưa chọn tệp";
private record StepInfo(int Index, string Label);
private readonly StepInfo[] _steps = new[]
private readonly StepInfo[] _steps =
{
new StepInfo(1, "Doanh nghiệp"),
new StepInfo(2, "Cửa hàng"),
@@ -124,4 +139,15 @@
new StepInfo(5, "Thiết bị"),
new StepInfo(6, "Hoàn tất"),
};
private record BusinessType(string Key, string Label, string Description);
private readonly BusinessType[] _businessTypes =
{
new BusinessType("fnb", "F&B", "Quán cà phê, nhà hàng"),
new BusinessType("retail", "Bán lẻ", "Cửa hàng, showroom"),
new BusinessType("services", "Dịch vụ", "Spa, salon, gym"),
};
private string GetBusinessTypeLabel()
=> Array.Find(_businessTypes, t => t.Key == _businessType)?.Label ?? "F&B";
}

View File

@@ -3,165 +3,129 @@
@inherits AdminBase
@*
EN: Onboarding Step 5 — Device setup (POS terminal, printer, scanner pairing).
VI: Bước 5 thiết lập — Kết nối thiết bị (máy POS, máy in, máy quét).
EN: Onboarding Step 5 — Device setup (POS terminal, printer, scanner, test print)
VI: Bước 5 thiết lập — Kết nối thiết bị (máy POS, máy in, máy quét, in thử)
Design: pencil-design/src/pages/tPOS/admin/onboarding-device.pen
*@
<PageTitle>Kết nối thiết bị — GoodGo Admin</PageTitle>
<div style="display:flex;height:100vh;overflow:hidden;">
<div class="admin-content" style="padding:40px;">
<div class="admin-panel" style="max-width:980px;margin:0 auto;display:flex;flex-direction:column;gap:32px;">
@* ═══ SIDEBAR — Step Progress ═══ *@
<div style="width:260px;min-width:260px;background-color:var(--admin-bg-elevated);border-right:1px solid var(--admin-border-subtle);display:flex;flex-direction:column;height:100vh;">
<div style="display:flex;align-items:center;gap:12px;padding:24px;border-bottom:1px solid var(--admin-border-subtle);">
<div style="width:40px;height:40px;border-radius:12px;background:var(--admin-orange-gradient);display:flex;align-items:center;justify-content:center;color:#FFF;font-size:20px;font-weight:800;">G</div>
<div style="display:flex;flex-direction:column;gap:2px;">
<span style="font-size:16px;font-weight:700;">GoodGo Admin</span>
<span style="font-size:11px;color:var(--admin-text-tertiary);">Thiết lập ban đầu</span>
</div>
</div>
<div style="flex:1;padding:32px 24px;display:flex;flex-direction:column;">
<span style="font-size:10px;font-weight:700;color:var(--admin-text-tertiary);letter-spacing:0.05em;margin-bottom:16px;">TIẾN TRÌNH</span>
@foreach (var s in _steps)
<section>
<div style="display:flex;gap:8px;align-items:center;margin-bottom:12px;">
@for (int i = 1; i <= 6; i++)
{
var step = s;
var isCompleted = step.Index < 5;
var isCurrent = step.Index == 5;
<div style="display:flex;align-items:center;gap:14px;padding:10px 8px;border-radius:10px;
background-color:@(isCurrent ? "rgba(255,92,0,0.08)" : "transparent");">
<div style="width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:700;
background-color:@(isCompleted ? "#22C55E" : isCurrent ? "var(--admin-orange-primary)" : "var(--admin-bg-interactive)");
color:@(isCompleted || isCurrent ? "#FFF" : "var(--admin-text-tertiary)");
border:@(!isCompleted && !isCurrent ? "2px solid var(--admin-border-default)" : "none");">
@(isCompleted ? "✓" : step.Index)
<div style="width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px;
background-color:@(i == currentStep ? "var(--admin-orange-primary)" : i < currentStep ? "rgba(34,197,94,0.2)" : "var(--admin-bg-interactive)");
color:@(i == currentStep ? "#FFF" : i < currentStep ? "#22C55E" : "var(--admin-text-tertiary)");">
@(i < currentStep ? "✓" : i)
</div>
<span style="font-size:14px;font-weight:@(isCurrent ? "600" : "500");color:@(isCompleted ? "var(--admin-text-primary)" : isCurrent ? "var(--admin-orange-primary)" : "var(--admin-text-tertiary)");">@step.Label</span>
</div>
@if (step.Index < 6)
@if (i < 6)
{
<div style="width:2px;height:24px;margin-left:23px;background-color:@(isCompleted ? "#22C55E" : "var(--admin-border-default)");"></div>
<div style="flex:1;height:2px;background-color:@(i < currentStep ? "#22C55E" : "var(--admin-border-subtle)");"></div>
}
}
</div>
<div style="display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:6px;margin-bottom:6px;">
@for (int i = 1; i <= 6; i++)
{
<span style="text-align:center;font-size:12px;font-weight:@(i == currentStep ? "600" : "500");color:@(i == currentStep ? "var(--admin-orange-primary)" : "var(--admin-text-tertiary)");">
@_steps[i - 1].Label
</span>
}
</div>
<p style="font-size:12px;color:var(--admin-text-tertiary);margin:0;">Bước 5/6 — Ghép nối thiết bị và đảm bảo trang bị vận hành</p>
</section>
<section class="admin-panel" style="padding:32px;display:flex;flex-direction:column;gap:24px;">
<div style="display:flex;flex-direction:column;gap:6px;">
<h2 style="font-size:24px;margin:0;font-weight:700;">Kết nối thiết bị</h2>
<p style="margin:0;font-size:14px;color:var(--admin-text-tertiary);">Kết nối máy POS, máy in và máy quét để chuẩn bị cho phiên bán hàng đầu tiên.</p>
</div>
@* ═══ MAIN CONTENT ═══ *@
<div style="flex:1;display:flex;align-items:center;justify-content:center;padding:40px;overflow-y:auto;">
<div class="admin-panel" style="width:720px;max-width:100%;padding:32px;border-radius:16px;display:flex;flex-direction:column;gap:24px;">
<div style="display:flex;flex-direction:column;gap:8px;">
<h2 style="font-size:22px;font-weight:700;margin:0;">Kết nối thiết bị</h2>
<p style="font-size:14px;color:var(--admin-text-tertiary);margin:0;">Kết nối thiết bị POS và máy in để bắt đầu bán hàng</p>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:20px;">
<div style="border-radius:14px;border:1px solid var(--admin-border-subtle);padding:20px;display:flex;flex-direction:column;gap:12px;">
<div style="display:flex;align-items:center;gap:10px;font-weight:600;color:var(--admin-orange-primary);">
<i data-lucide="tablet" style="width:24px;height:24px;"></i>
<span>Thiết bị POS</span>
</div>
@* QR Pairing section *@
<div style="display:flex;gap:24px;">
<div style="width:160px;height:160px;border-radius:12px;background-color:#FFFFFF;display:flex;align-items:center;justify-content:center;">
<div style="width:120px;height:120px;background-color:#E5E5E5;border-radius:8px;display:flex;align-items:center;justify-content:center;">
<i data-lucide="qr-code" style="width:60px;height:60px;color:#333;"></i>
<span style="font-size:14px;font-weight:600;">@_terminalLabel</span>
<p style="margin:0;font-size:13px;color:var(--admin-text-tertiary);">Ghép nối qua mã QR hoặc Bluetooth trong ứng dụng GoodGo POS.</p>
<button class="admin-btn-secondary" @onclick="PairTerminal">Ghép nối</button>
</div>
<div style="border-radius:14px;border:1px solid var(--admin-border-subtle);padding:20px;display:flex;flex-direction:column;gap:12px;">
<div style="display:flex;align-items:center;gap:10px;font-weight:600;color:@(_scannerPaired ? "#22C55E" : "var(--admin-text-primary)");">
<i data-lucide="radio" style="width:20px;height:20px;color:@(_scannerPaired ? "#22C55E" : "var(--admin-text-tertiary)");"></i>
<span>Máy quét mã vạch</span>
</div>
<div style="flex:1;display:flex;flex-direction:column;justify-content:center;gap:16px;">
<div style="display:flex;flex-direction:column;gap:4px;">
<span style="font-size:16px;font-weight:700;">Quét mã QR</span>
<span style="font-size:13px;color:var(--admin-text-tertiary);">Mở ứng dụng GoodGo POS trên thiết bị và quét mã QR để kết nối</span>
</div>
<div style="display:flex;flex-direction:column;gap:4px;">
<span style="font-size:12px;color:var(--admin-text-tertiary);">Mã ghép nối</span>
<span style="font-size:20px;font-weight:700;letter-spacing:4px;color:var(--admin-orange-primary);">GG-8492</span>
</div>
<span style="font-size:13px;color:var(--admin-text-tertiary);">@(_scannerPaired ? "Đã ghép nối" : "Chưa ghép nối")</span>
<button class="admin-btn-primary" @onclick="ToggleScannerPairing">@(_scannerPaired ? "Đặt lại" : "Ghép nối")</button>
</div>
</div>
@* Connected devices *@
<div style="display:flex;flex-direction:column;gap:12px;">
<span style="font-size:13px;font-weight:600;color:var(--admin-text-secondary);">Thiết bị đã kết nối (1)</span>
<div style="display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border-radius:12px;background-color:var(--admin-bg-interactive);">
<div style="display:flex;align-items:center;gap:12px;">
<div style="width:40px;height:40px;border-radius:10px;background-color:rgba(34,197,94,0.15);display:flex;align-items:center;justify-content:center;">
<i data-lucide="tablet" style="width:20px;height:20px;color:#22C55E;"></i>
<div style="display:flex;flex-direction:column;gap:16px;">
<div style="display:flex;align-items:center;justify-content:space-between;">
<div>
<h3 style="margin:0;font-size:18px;font-weight:600;">Máy in hóa đơn</h3>
<p style="margin:0;font-size:13px;color:var(--admin-text-tertiary);">Chọn cổng kết nối phù hợp với cửa hàng</p>
</div>
<div style="display:flex;flex-direction:column;gap:2px;">
<span style="font-size:14px;font-weight:600;">iPad POS — Quầy 1</span>
<span style="font-size:12px;color:var(--admin-text-tertiary);">Kết nối 2 phút trước</span>
<span style="font-size:12px;color:var(--admin-orange-primary);font-weight:600;">Đang sử dụng: @GetPrinterLabel()</span>
</div>
</div>
<div class="admin-status-badge admin-status-badge--online">
<div class="admin-status-badge__dot"></div>
Online
</div>
</div>
</div>
@* Printer setup *@
<div style="display:flex;flex-direction:column;gap:12px;">
<span style="font-size:13px;font-weight:600;color:var(--admin-text-secondary);">Thiết lập máy in</span>
<div style="display:flex;gap:12px;">
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px;">
@foreach (var printer in _printerOptions)
{
var p = printer;
<div style="flex:1;padding:16px;border-radius:12px;background-color:var(--admin-bg-interactive);border:2px solid @(p.IsSelected ? "var(--admin-orange-primary)" : "var(--admin-border-default)");cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:8px;"
@onclick="@(() => SelectPrinter(p.Key))">
<i data-lucide="@p.Icon" style="width:24px;height:24px;color:@(p.IsSelected ? "var(--admin-orange-primary)" : "var(--admin-text-tertiary)");"></i>
<span style="font-size:13px;font-weight:600;color:@(p.IsSelected ? "var(--admin-orange-primary)" : "var(--admin-text-primary)");">@p.Label</span>
<span style="font-size:11px;color:var(--admin-text-tertiary);text-align:center;">@p.Desc</span>
<div style="border-radius:12px;padding:16px;border:2px solid @(printer.Key == _selectedPrinter ? "var(--admin-orange-primary)" : "var(--admin-border-default)");background-color:@(printer.Key == _selectedPrinter ? "rgba(255,92,0,0.1)" : "var(--admin-bg-interactive)");">
<div style="display:flex;align-items:center;gap:10px;margin-bottom:8px;font-weight:600;color:@(printer.Key == _selectedPrinter ? "var(--admin-orange-primary)" : "var(--admin-text-primary)");">
<i data-lucide="printer" style="width:20px;height:20px;"></i>
<span>@printer.Label</span>
</div>
<p style="margin:0;font-size:12px;color:var(--admin-text-tertiary);">@printer.Description</p>
<button class="admin-btn-secondary" style="margin-top:12px;width:100%;" @onclick="() => SelectPrinter(printer.Key)">Chọn</button>
</div>
}
</div>
</div>
@* Footer *@
<div style="display:flex;justify-content:space-between;padding-top:8px;">
<div style="display:flex;gap:12px;">
<button class="admin-btn-secondary" @onclick="@(() => NavigateTo("onboarding/staff"))">
<div style="border-radius:14px;border:1px solid var(--admin-border-subtle);padding:20px;display:flex;flex-direction:column;gap:12px;">
<div style="display:flex;align-items:center;gap:10px;">
<i data-lucide="printer" style="width:24px;height:24px;color:var(--admin-orange-primary);"></i>
<span style="font-weight:600;">In thử</span>
</div>
<p style="margin:0;font-size:13px;color:var(--admin-text-tertiary);">Đảm bảo đầu ra từ máy in trông ổn và dễ đọc.</p>
<div style="display:flex;gap:12px;flex-wrap:wrap;">
<button class="admin-btn-primary" @onclick="TriggerTestPrint">In thử</button>
<span style="font-size:12px;color:var(--admin-text-secondary);">@_testPrintMessage</span>
</div>
</div>
<div style="display:flex;justify-content:space-between;flex-wrap:wrap;gap:12px;">
<button class="admin-btn-secondary" @onclick='() => NavigateTo("onboarding/staff")' style="display:flex;gap:8px;align-items:center;">
<i data-lucide="arrow-left"></i>
<span>Quay lại</span>
</button>
</div>
<div style="display:flex;gap:12px;">
<button class="admin-btn-secondary" @onclick="@(() => NavigateTo("onboarding/ready"))">
<span>Bỏ qua</span>
</button>
<button class="admin-btn-primary" @onclick="@(() => NavigateTo("onboarding/ready"))">
<button class="admin-btn-secondary" @onclick='() => NavigateTo("onboarding/ready")'>Bỏ qua</button>
<button class="admin-btn-primary" @onclick='() => NavigateTo("onboarding/ready")' style="display:flex;gap:8px;align-items:center;">
<span>Tiếp tục</span>
<i data-lucide="arrow-right"></i>
</button>
</div>
</div>
</div>
</section>
</div>
</div>
@code {
private int currentStep = 5;
private string _selectedPrinter = "thermal";
private record PrinterOption(string Key, string Label, string Icon, string Desc, bool IsSelected);
private List<PrinterOption> _printerOptions = new();
protected override void OnInitialized()
{
UpdatePrinterOptions();
}
private void SelectPrinter(string key)
{
_selectedPrinter = key;
UpdatePrinterOptions();
}
private void UpdatePrinterOptions()
{
_printerOptions = new()
{
new("thermal", "Máy in nhiệt", "printer", "In hóa đơn 58/80mm", _selectedPrinter == "thermal"),
new("bluetooth", "Bluetooth", "bluetooth", "Kết nối không dây", _selectedPrinter == "bluetooth"),
new("usb", "USB", "usb", "Kết nối có dây", _selectedPrinter == "usb"),
};
}
private string _testPrintMessage = "Chưa test";
private bool _scannerPaired = false;
private string _terminalLabel = "iPad POS — Quầy 1";
private record StepInfo(int Index, string Label);
private readonly StepInfo[] _steps = new[]
private readonly StepInfo[] _steps =
{
new StepInfo(1, "Doanh nghiệp"),
new StepInfo(2, "Cửa hàng"),
@@ -170,4 +134,35 @@
new StepInfo(5, "Thiết bị"),
new StepInfo(6, "Hoàn tất"),
};
private record PrinterOption(string Key, string Label, string Description);
private readonly PrinterOption[] _printerOptions =
{
new PrinterOption("thermal", "Máy in nhiệt", "Hóa đơn 58/80mm"),
new PrinterOption("bluetooth", "Bluetooth", "Không dây cho quầy nhỏ"),
new PrinterOption("usb", "USB", "Kết nối có dây ổn định"),
};
private void SelectPrinter(string key)
{
_selectedPrinter = key;
}
private void PairTerminal()
{
_terminalLabel = "iPad POS — Quầy 2";
}
private void ToggleScannerPairing()
{
_scannerPaired = !_scannerPaired;
}
private void TriggerTestPrint()
{
_testPrintMessage = "Đã in thành công";
}
private string GetPrinterLabel()
=> Array.Find(_printerOptions, p => p.Key == _selectedPrinter)?.Label ?? "Máy in nhiệt";
}

View File

@@ -3,141 +3,134 @@
@inherits AdminBase
@*
EN: Onboarding Step 3 — Add initial products (quick form, CSV import, product list preview).
VI: Bước 3 thiết lập — Thêm sản phẩm ban đầu (form nhanh, import CSV, danh sách).
EN: Onboarding Step 3 — Add initial products (quick form, CSV import, product list preview)
VI: Bước 3 thiết lập — Thêm sản phẩm ban đầu (form nhanh, import CSV, danh sách)
Design: pencil-design/src/pages/tPOS/admin/onboarding-products.pen
*@
<PageTitle>Thêm sản phẩm — GoodGo Admin</PageTitle>
<div style="display:flex;height:100vh;overflow:hidden;">
<div class="admin-content" style="padding:40px;">
<div class="admin-panel" style="max-width:980px;margin:0 auto;display:flex;flex-direction:column;gap:32px;">
@* ═══ SIDEBAR — Step Progress ═══ *@
<div style="width:260px;min-width:260px;background-color:var(--admin-bg-elevated);border-right:1px solid var(--admin-border-subtle);display:flex;flex-direction:column;height:100vh;">
<div style="display:flex;align-items:center;gap:12px;padding:24px;border-bottom:1px solid var(--admin-border-subtle);">
<div style="width:40px;height:40px;border-radius:12px;background:var(--admin-orange-gradient);display:flex;align-items:center;justify-content:center;color:#FFF;font-size:20px;font-weight:800;">G</div>
<div style="display:flex;flex-direction:column;gap:2px;">
<span style="font-size:16px;font-weight:700;">GoodGo Admin</span>
<span style="font-size:11px;color:var(--admin-text-tertiary);">Thiết lập ban đầu</span>
</div>
</div>
<div style="flex:1;padding:32px 24px;display:flex;flex-direction:column;">
<span style="font-size:10px;font-weight:700;color:var(--admin-text-tertiary);letter-spacing:0.05em;margin-bottom:16px;">TIẾN TRÌNH</span>
@foreach (var s in _steps)
<section>
<div style="display:flex;gap:8px;align-items:center;margin-bottom:12px;">
@for (int i = 1; i <= 6; i++)
{
var step = s;
var isCompleted = step.Index < 3;
var isCurrent = step.Index == 3;
<div style="display:flex;align-items:center;gap:14px;padding:10px 8px;border-radius:10px;
background-color:@(isCurrent ? "rgba(255,92,0,0.08)" : "transparent");">
<div style="width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:700;
background-color:@(isCompleted ? "#22C55E" : isCurrent ? "var(--admin-orange-primary)" : "var(--admin-bg-interactive)");
color:@(isCompleted || isCurrent ? "#FFF" : "var(--admin-text-tertiary)");
border:@(!isCompleted && !isCurrent ? "2px solid var(--admin-border-default)" : "none");">
@(isCompleted ? "✓" : step.Index)
<div style="width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px;
background-color:@(i == currentStep ? "var(--admin-orange-primary)" : i < currentStep ? "rgba(34,197,94,0.2)" : "var(--admin-bg-interactive)");
color:@(i == currentStep ? "#FFF" : i < currentStep ? "#22C55E" : "var(--admin-text-tertiary)");">
@(i < currentStep ? "✓" : i)
</div>
<span style="font-size:14px;font-weight:@(isCurrent ? "600" : "500");color:@(isCompleted ? "var(--admin-text-primary)" : isCurrent ? "var(--admin-orange-primary)" : "var(--admin-text-tertiary)");">@step.Label</span>
</div>
@if (step.Index < 6)
@if (i < 6)
{
<div style="width:2px;height:24px;margin-left:23px;background-color:@(isCompleted ? "#22C55E" : "var(--admin-border-default)");"></div>
<div style="flex:1;height:2px;background-color:@(i < currentStep ? "#22C55E" : "var(--admin-border-subtle)");"></div>
}
}
</div>
<div style="display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:6px;margin-bottom:6px;">
@for (int i = 1; i <= 6; i++)
{
<span style="text-align:center;font-size:12px;font-weight:@(i == currentStep ? "600" : "500");color:@(i == currentStep ? "var(--admin-orange-primary)" : "var(--admin-text-tertiary)");">
@_steps[i - 1].Label
</span>
}
</div>
<p style="font-size:12px;color:var(--admin-text-tertiary);margin:0;">Bước 3/6 — Nhập vài sản phẩm mẫu hoặc import thẳng từ file CSV</p>
</section>
<section class="admin-panel" style="padding:32px;display:flex;flex-direction:column;gap:24px;">
<div style="display:flex;flex-direction:column;gap:6px;">
<h2 style="font-size:24px;margin:0;font-weight:700;">Thêm sản phẩm đầu tiên</h2>
<p style="margin:0;font-size:14px;color:var(--admin-text-tertiary);">Không cần làm tất cả ngay, bạn có thể bắt đầu với vài món tiêu biểu.</p>
</div>
@* ═══ MAIN CONTENT ═══ *@
<div style="flex:1;display:flex;align-items:center;justify-content:center;padding:40px;overflow-y:auto;">
<div class="admin-panel" style="width:720px;max-width:100%;padding:32px;border-radius:16px;display:flex;flex-direction:column;gap:24px;">
<div style="display:flex;flex-direction:column;gap:8px;">
<h2 style="font-size:22px;font-weight:700;margin:0;">Thêm sản phẩm</h2>
<p style="font-size:14px;color:var(--admin-text-tertiary);margin:0;">Thêm sản phẩm vào cửa hàng hoặc bỏ qua để thêm sau</p>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;">
<div style="border-radius:14px;border:1px dashed var(--admin-border-default);padding:20px;display:flex;flex-direction:column;gap:8px;align-items:flex-start;">
<div style="display:flex;align-items:center;gap:6px;font-weight:600;color:var(--admin-orange-primary);">
<i data-lucide="file-spreadsheet" style="width:20px;height:20px;"></i>
<span>Import từ CSV</span>
</div>
@* Import options *@
<div style="display:flex;gap:16px;">
<div style="flex:1;height:80px;border-radius:12px;background-color:var(--admin-bg-interactive);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;cursor:pointer;">
<i data-lucide="file-spreadsheet" style="width:24px;height:24px;color:var(--admin-text-tertiary);"></i>
<span style="font-size:13px;color:var(--admin-text-tertiary);">Import từ CSV</span>
<p style="margin:0;font-size:13px;color:var(--admin-text-tertiary);">Tải file định dạng chuẩn GoodGo để tiết kiệm thời gian.</p>
<button class="admin-btn-secondary" style="width:100%;" @onclick="ImportFromCsv">Tải file CSV</button>
<span style="font-size:12px;color:var(--admin-text-secondary);">@_importStatus</span>
</div>
<div style="flex:1;height:80px;border-radius:12px;background-color:rgba(255,92,0,0.08);border:2px solid var(--admin-orange-primary);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;cursor:pointer;">
<i data-lucide="pencil" style="width:24px;height:24px;color:var(--admin-orange-primary);"></i>
<span style="font-size:13px;color:var(--admin-orange-primary);font-weight:600;">Nhập thủ công</span>
</div>
</div>
@* Quick add form *@
<div style="display:flex;gap:12px;align-items:flex-end;">
<div class="admin-form-group" style="flex:1;">
<div style="border-radius:14px;background-color:rgba(255,92,0,0.08);border:1px solid var(--admin-orange-primary);padding:20px;display:flex;flex-direction:column;gap:10px;">
<span style="font-weight:600;color:var(--admin-orange-primary);">Nhập thủ công</span>
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:flex-end;">
<div class="admin-form-group" style="flex:1;min-width:140px;">
<label class="admin-form-label">Tên sản phẩm</label>
<input class="admin-form-input" type="text" placeholder="VD: Cà phê sữa đá" @bind="_newProductName" />
<input class="admin-form-input" type="text" @bind="_newProductName" />
</div>
<div class="admin-form-group" style="width:140px;">
<label class="admin-form-label">Giá bán</label>
<input class="admin-form-input" type="text" placeholder="35,000" @bind="_newProductPrice" />
<input class="admin-form-input" type="text" @bind="_newProductPrice" />
</div>
<div class="admin-form-group" style="width:140px;">
<label class="admin-form-label">Danh mục</label>
<select class="admin-form-input" @bind="_newProductCategory">
<option value="">Chọn</option>
<option value="drink">Đồ uống</option>
<option value="food">Đồ ăn</option>
<option value="dessert">Tráng miệng</option>
<option value="Đồ uống">Đồ uống</option>
<option value="Đồ ăn">Đồ ăn</option>
<option value="Tráng miệng">Tráng miệng</option>
</select>
</div>
<button style="width:40px;height:40px;min-width:40px;border-radius:10px;background-color:var(--admin-orange-primary);border:none;display:flex;align-items:center;justify-content:center;cursor:pointer;" @onclick="AddProduct">
<i data-lucide="plus" style="width:20px;height:20px;color:#FFF;"></i>
<button class="admin-btn-primary" style="height:44px;" @onclick="AddProduct">
<i data-lucide="plus" style="width:18px;height:18px;"></i>
</button>
</div>
</div>
</div>
@* Product list preview *@
<div style="display:flex;flex-direction:column;">
<span style="font-size:13px;font-weight:600;color:var(--admin-text-secondary);margin-bottom:8px;">Sản phẩm đã thêm (@_products.Count)</span>
<div style="display:flex;flex-direction:column;gap:10px;">
<h3 style="margin:0;font-size:16px;font-weight:600;">Danh sách sản phẩm (@_products.Count)</h3>
<div style="display:flex;flex-direction:column;gap:10px;">
@foreach (var product in _products)
{
var p = product;
<div style="display:flex;align-items:center;justify-content:space-between;padding:12px 0;border-bottom:1px solid var(--admin-border-subtle);">
<div style="display:flex;align-items:center;gap:12px;">
<div style="width:36px;height:36px;border-radius:8px;background-color:var(--admin-bg-interactive);display:flex;align-items:center;justify-content:center;">
<i data-lucide="package" style="width:16px;height:16px;color:var(--admin-text-tertiary);"></i>
<div style="display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-radius:12px;border:1px solid var(--admin-border-subtle);background-color:var(--admin-bg-interactive);">
<div>
<div style="font-weight:600;">@product.Name</div>
<div style="font-size:12px;color:var(--admin-text-tertiary);">@product.Category</div>
</div>
<div style="display:flex;flex-direction:column;gap:2px;">
<span style="font-size:14px;font-weight:600;">@p.Name</span>
<span style="font-size:12px;color:var(--admin-text-tertiary);">@p.Category</span>
</div>
</div>
<span style="font-size:14px;font-weight:600;color:var(--admin-orange-primary);">@p.Price</span>
<span style="font-weight:600;color:var(--admin-orange-primary);">@product.Price</span>
</div>
}
</div>
</div>
@* Footer *@
<div style="display:flex;justify-content:space-between;padding-top:8px;">
<div style="display:flex;gap:12px;">
<button class="admin-btn-secondary" @onclick="@(() => NavigateTo("onboarding/store"))">
<div style="display:flex;justify-content:space-between;flex-wrap:wrap;gap:12px;">
<button class="admin-btn-secondary" @onclick='() => NavigateTo("onboarding/store")' style="display:flex;gap:8px;align-items:center;">
<i data-lucide="arrow-left"></i>
<span>Quay lại</span>
</button>
</div>
<div style="display:flex;gap:12px;">
<button class="admin-btn-secondary" @onclick="@(() => NavigateTo("onboarding/staff"))">
<span>Bỏ qua</span>
</button>
<button class="admin-btn-primary" @onclick="@(() => NavigateTo("onboarding/staff"))">
<button class="admin-btn-secondary" @onclick='() => NavigateTo("onboarding/staff")'>Bỏ qua</button>
<button class="admin-btn-primary" @onclick='() => NavigateTo("onboarding/staff")' style="display:flex;gap:8px;align-items:center;">
<span>Tiếp tục</span>
<i data-lucide="arrow-right"></i>
</button>
</div>
</div>
</div>
</section>
</div>
</div>
@code {
private string _newProductName = "";
private string _newProductPrice = "";
private string _newProductCategory = "";
private int currentStep = 3;
private string _importStatus = "Chưa import";
private string _newProductName = "Cà phê sữa đá";
private string _newProductPrice = "35,000";
private string _newProductCategory = "Đồ uống";
private record StepInfo(int Index, string Label);
private readonly StepInfo[] _steps =
{
new StepInfo(1, "Doanh nghiệp"),
new StepInfo(2, "Cửa hàng"),
new StepInfo(3, "Sản phẩm"),
new StepInfo(4, "Nhân viên"),
new StepInfo(5, "Thiết bị"),
new StepInfo(6, "Hoàn tất"),
};
private record ProductItem(string Name, string Price, string Category);
private List<ProductItem> _products = new()
@@ -149,22 +142,20 @@
private void AddProduct()
{
if (!string.IsNullOrWhiteSpace(_newProductName))
if (string.IsNullOrWhiteSpace(_newProductName) || string.IsNullOrWhiteSpace(_newProductPrice))
{
_products.Add(new(_newProductName, $"{_newProductPrice}đ", _newProductCategory));
_newProductName = "";
_newProductPrice = "";
}
_importStatus = "Vui lòng nhập tên và giá";
return;
}
private record StepInfo(int Index, string Label);
private readonly StepInfo[] _steps = new[]
{
new StepInfo(1, "Doanh nghiệp"),
new StepInfo(2, "Cửa hàng"),
new StepInfo(3, "Sản phẩm"),
new StepInfo(4, "Nhân viên"),
new StepInfo(5, "Thiết bị"),
new StepInfo(6, "Hoàn tất"),
};
_products.Insert(0, new ProductItem(_newProductName, $"{_newProductPrice}đ", _newProductCategory));
_newProductName = string.Empty;
_newProductPrice = string.Empty;
_importStatus = "Đã thêm sản phẩm mới";
}
private void ImportFromCsv()
{
_importStatus = "Đang phân tích file...";
}
}

View File

@@ -3,105 +3,105 @@
@inherits AdminBase
@*
EN: Onboarding Step 6 — All set! Summary checklist and CTA to start selling.
VI: Bước 6 thiết lập — Hoàn tất! Checklist tóm tắt và nút bắt đầu bán hàng.
EN: Onboarding Step 6 — All set! Summary checklist and CTA to start selling
VI: Bước 6 thiết lập — Hoàn tất! Checklist tóm tắt và nút bắt đầu sử dụng
Design: pencil-design/src/pages/tPOS/admin/onboarding-ready.pen
*@
<PageTitle>Hoàn tất thiết lập — GoodGo Admin</PageTitle>
<div style="display:flex;height:100vh;overflow:hidden;">
<style>
.confetti-wrapper {
position:relative;
width:120px;
height:120px;
display:flex;
justify-content:center;
align-items:center;
}
@* ═══ SIDEBAR — Step Progress (all completed) ═══ *@
<div style="width:260px;min-width:260px;background-color:var(--admin-bg-elevated);border-right:1px solid var(--admin-border-subtle);display:flex;flex-direction:column;height:100vh;">
<div style="display:flex;align-items:center;gap:12px;padding:24px;border-bottom:1px solid var(--admin-border-subtle);">
<div style="width:40px;height:40px;border-radius:12px;background:var(--admin-orange-gradient);display:flex;align-items:center;justify-content:center;color:#FFF;font-size:20px;font-weight:800;">G</div>
<div style="display:flex;flex-direction:column;gap:2px;">
<span style="font-size:16px;font-weight:700;">GoodGo Admin</span>
<span style="font-size:11px;color:var(--admin-text-tertiary);">Thiết lập ban đầu</span>
</div>
</div>
<div style="flex:1;padding:32px 24px;display:flex;flex-direction:column;">
<span style="font-size:10px;font-weight:700;color:var(--admin-text-tertiary);letter-spacing:0.05em;margin-bottom:16px;">TIẾN TRÌNH</span>
@foreach (var s in _steps)
.confetti-dot {
position:absolute;
width:8px;
height:8px;
border-radius:50%;
animation:confetti-fall 1.6s linear infinite;
}
@keyframes confetti-fall {
0% {transform:translateY(0) scale(1);opacity:1;}
100% {transform:translateY(20px) scale(0.6);opacity:0;}
}
</style>
<div class="admin-content" style="padding:40px;">
<div class="admin-panel" style="max-width:860px;margin:0 auto;display:flex;flex-direction:column;gap:32px;">
<section>
<div style="display:flex;gap:8px;align-items:center;margin-bottom:12px;">
@for (int i = 1; i <= 6; i++)
{
var step = s;
var isLast = step.Index == 6;
<div style="display:flex;align-items:center;gap:14px;padding:10px 8px;border-radius:10px;
background-color:@(isLast ? "rgba(34,197,94,0.08)" : "transparent");">
<div style="width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:700;
background-color:#22C55E;color:#FFF;">
<div style="width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px;
background-color:@(i == currentStep ? "var(--admin-orange-primary)" : i < currentStep ? "rgba(34,197,94,0.2)" : "var(--admin-bg-interactive)");
color:@(i == currentStep ? "#FFF" : i < currentStep ? "#22C55E" : "var(--admin-text-tertiary)");">
@(i < currentStep ? "✓" : i)
</div>
<span style="font-size:14px;font-weight:@(isLast ? "600" : "500");color:@(isLast ? "#22C55E" : "var(--admin-text-primary)");">@step.Label</span>
</div>
@if (step.Index < 6)
@if (i < 6)
{
<div style="width:2px;height:24px;margin-left:23px;background-color:#22C55E;"></div>
<div style="flex:1;height:2px;background-color:@(i < currentStep ? "#22C55E" : "var(--admin-border-subtle)");"></div>
}
}
</div>
<div style="display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:6px;margin-bottom:6px;">
@for (int i = 1; i <= 6; i++)
{
<span style="text-align:center;font-size:12px;font-weight:@(i == currentStep ? "600" : "500");color:@(i == currentStep ? "var(--admin-orange-primary)" : "var(--admin-text-tertiary)");">
@_steps[i - 1].Label
</span>
}
</div>
<p style="font-size:12px;color:var(--admin-text-tertiary);margin:0;">Bước 6/6 — Mọi thứ đã sẵn sàng, hãy bắt đầu hành trình bán hàng</p>
</section>
<section class="admin-panel" style="padding:32px;display:flex;flex-direction:column;align-items:center;gap:20px;">
<div class="confetti-wrapper">
<div class="confetti-dot" style="background-color:#FF5C00;left:10px;animation-delay:0s;"></div>
<div class="confetti-dot" style="background-color:#22C55E;left:50px;animation-delay:0.2s;"></div>
<div class="confetti-dot" style="background-color:#3B82F6;left:90px;animation-delay:0.4s;"></div>
</div>
<div style="text-align:center;display:flex;flex-direction:column;gap:8px;">
<h2 style="margin:0;font-size:26px;font-weight:700;">Thiết lập hoàn tất!</h2>
<p style="margin:0;font-size:14px;color:var(--admin-text-tertiary);max-width:420px;">Tất cả cấu phần chính đã sẵn sàng. Hãy bắt đầu bán hàng và mang trải nghiệm GoodGo đến khách hàng.</p>
</div>
@* ═══ MAIN CONTENT ═══ *@
<div style="flex:1;display:flex;align-items:center;justify-content:center;padding:40px;overflow-y:auto;">
<div class="admin-panel" style="width:560px;max-width:100%;padding:40px;border-radius:16px;display:flex;flex-direction:column;align-items:center;gap:32px;">
@* Success icon *@
<div style="width:72px;height:72px;border-radius:50%;background-color:rgba(34,197,94,0.08);display:flex;align-items:center;justify-content:center;">
<i data-lucide="party-popper" style="width:36px;height:36px;color:#22C55E;"></i>
</div>
@* Title *@
<div style="display:flex;flex-direction:column;align-items:center;gap:8px;">
<h2 style="font-size:24px;font-weight:700;margin:0;text-align:center;">Thiết lập hoàn tất!</h2>
<p style="font-size:14px;color:var(--admin-text-tertiary);margin:0;text-align:center;">Mọi thứ đã sẵn sàng để bạn bắt đầu bán hàng</p>
</div>
@* Checklist *@
<div style="width:100%;padding:16px 20px;border-radius:12px;background-color:var(--admin-bg-interactive);display:flex;flex-direction:column;gap:14px;">
<div style="width:100%;display:flex;flex-direction:column;gap:12px;">
@foreach (var item in _checklist)
{
var c = item;
<div style="display:flex;align-items:center;justify-content:space-between;">
<div style="display:flex;align-items:center;gap:10px;">
<i data-lucide="@c.Icon" style="width:18px;height:18px;color:@c.IconColor;"></i>
<span style="font-size:14px;font-weight:500;">@c.Label</span>
</div>
<div style="display:flex;align-items:center;gap:6px;">
<span style="font-size:12px;color:var(--admin-text-tertiary);">@c.Value</span>
<i data-lucide="check-circle" style="width:16px;height:16px;color:#22C55E;"></i>
<div style="display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-radius:12px;border:1px solid var(--admin-border-subtle);background-color:var(--admin-bg-interactive);">
<div style="display:flex;align-items:center;gap:8px;">
<i data-lucide="@item.Icon" style="width:18px;height:18px;color:@item.IconColor;"></i>
<span style="font-size:14px;font-weight:600;">@item.Label</span>
</div>
<span style="font-size:13px;color:var(--admin-text-tertiary);">@item.Value</span>
</div>
}
</div>
@* CTA Button *@
<button style="width:100%;height:52px;border-radius:12px;background-color:var(--admin-orange-primary);border:none;display:flex;align-items:center;justify-content:center;gap:10px;cursor:pointer;transition:all 0.2s ease;"
@onclick="@(() => NavigateTo("dashboard"))">
<i data-lucide="rocket" style="width:20px;height:20px;color:#FFF;"></i>
<span style="font-size:16px;font-weight:700;color:#FFF;">Bắt đầu bán hàng</span>
<button class="admin-btn-primary" style="width:100%;height:52px;border-radius:12px;display:flex;align-items:center;justify-content:center;gap:10px;" @onclick='() => NavigateTo("dashboard")'>
<i data-lucide="rocket" style="width:20px;height:20px;"></i>
<span style="font-size:16px;font-weight:700;">Bắt đầu sử dụng</span>
</button>
@* Guide link *@
<span style="font-size:13px;color:var(--admin-text-tertiary);cursor:pointer;">Xem hướng dẫn sử dụng</span>
</div>
<button class="admin-btn-secondary" style="background:none;border:none;color:var(--admin-text-tertiary);font-size:13px;" @onclick='() => NavigateTo("/docs/getting-started")'>Xem hướng dẫn sử dụng</button>
</section>
</div>
</div>
@code {
private record ChecklistItem(string Icon, string IconColor, string Label, string Value);
private readonly ChecklistItem[] _checklist = new[]
{
new ChecklistItem("building-2", "#3B82F6", "Thông tin doanh nghiệp", "Đã hoàn tất"),
new ChecklistItem("store", "#FF5C00", "Cửa hàng đầu tiên", "Coffee House Q1"),
new ChecklistItem("package", "#8B5CF6", "Sản phẩm", "3 sản phẩm"),
new ChecklistItem("users", "#EC4899", "Nhân viên", "2 người"),
new ChecklistItem("tablet", "#22C55E", "Thiết bị POS", "1 thiết bị"),
};
private int currentStep = 6;
private record StepInfo(int Index, string Label);
private readonly StepInfo[] _steps = new[]
private readonly StepInfo[] _steps =
{
new StepInfo(1, "Doanh nghiệp"),
new StepInfo(2, "Cửa hàng"),
@@ -110,4 +110,14 @@
new StepInfo(5, "Thiết bị"),
new StepInfo(6, "Hoàn tất"),
};
private record ChecklistItem(string Icon, string IconColor, string Label, string Value);
private readonly ChecklistItem[] _checklist =
{
new ChecklistItem("building-2", "#3B82F6", "Thông tin doanh nghiệp", "Đã hoàn tất"),
new ChecklistItem("store", "#FF5C00", "Cửa hàng đầu tiên", "Coffee House Q1"),
new ChecklistItem("package", "#8B5CF6", "Sản phẩm", "3 món mẫu"),
new ChecklistItem("users", "#EC4899", "Nhân viên", "2 người"),
new ChecklistItem("tablet", "#22C55E", "Thiết bị", "1 thiết bị"),
};
}

View File

@@ -3,147 +3,135 @@
@inherits AdminBase
@*
EN: Onboarding Step 4 — Add staff members (name, role, email, PIN, staff list preview).
VI: Bước 4 thiết lập — Thêm nhân viên (tên, vai trò, email, PIN, danh sách).
EN: Onboarding Step 4 — Add staff members (name, role, email, PIN, staff list preview)
VI: Bước 4 thiết lập — Thêm nhân viên (tên, vai trò, email, PIN, danh sách)
Design: pencil-design/src/pages/tPOS/admin/onboarding-staff.pen
*@
<PageTitle>Thêm nhân viên — GoodGo Admin</PageTitle>
<div style="display:flex;height:100vh;overflow:hidden;">
<div class="admin-content" style="padding:40px;">
<div class="admin-panel" style="max-width:980px;margin:0 auto;display:flex;flex-direction:column;gap:32px;">
@* ═══ SIDEBAR — Step Progress ═══ *@
<div style="width:260px;min-width:260px;background-color:var(--admin-bg-elevated);border-right:1px solid var(--admin-border-subtle);display:flex;flex-direction:column;height:100vh;">
<div style="display:flex;align-items:center;gap:12px;padding:24px;border-bottom:1px solid var(--admin-border-subtle);">
<div style="width:40px;height:40px;border-radius:12px;background:var(--admin-orange-gradient);display:flex;align-items:center;justify-content:center;color:#FFF;font-size:20px;font-weight:800;">G</div>
<div style="display:flex;flex-direction:column;gap:2px;">
<span style="font-size:16px;font-weight:700;">GoodGo Admin</span>
<span style="font-size:11px;color:var(--admin-text-tertiary);">Thiết lập ban đầu</span>
</div>
</div>
<div style="flex:1;padding:32px 24px;display:flex;flex-direction:column;">
<span style="font-size:10px;font-weight:700;color:var(--admin-text-tertiary);letter-spacing:0.05em;margin-bottom:16px;">TIẾN TRÌNH</span>
@foreach (var s in _steps)
<section>
<div style="display:flex;gap:8px;align-items:center;margin-bottom:12px;">
@for (int i = 1; i <= 6; i++)
{
var step = s;
var isCompleted = step.Index < 4;
var isCurrent = step.Index == 4;
<div style="display:flex;align-items:center;gap:14px;padding:10px 8px;border-radius:10px;
background-color:@(isCurrent ? "rgba(255,92,0,0.08)" : "transparent");">
<div style="width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:700;
background-color:@(isCompleted ? "#22C55E" : isCurrent ? "var(--admin-orange-primary)" : "var(--admin-bg-interactive)");
color:@(isCompleted || isCurrent ? "#FFF" : "var(--admin-text-tertiary)");
border:@(!isCompleted && !isCurrent ? "2px solid var(--admin-border-default)" : "none");">
@(isCompleted ? "✓" : step.Index)
<div style="width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px;
background-color:@(i == currentStep ? "var(--admin-orange-primary)" : i < currentStep ? "rgba(34,197,94,0.2)" : "var(--admin-bg-interactive)");
color:@(i == currentStep ? "#FFF" : i < currentStep ? "#22C55E" : "var(--admin-text-tertiary)");">
@(i < currentStep ? "✓" : i)
</div>
<span style="font-size:14px;font-weight:@(isCurrent ? "600" : "500");color:@(isCompleted ? "var(--admin-text-primary)" : isCurrent ? "var(--admin-orange-primary)" : "var(--admin-text-tertiary)");">@step.Label</span>
</div>
@if (step.Index < 6)
@if (i < 6)
{
<div style="width:2px;height:24px;margin-left:23px;background-color:@(isCompleted ? "#22C55E" : "var(--admin-border-default)");"></div>
<div style="flex:1;height:2px;background-color:@(i < currentStep ? "#22C55E" : "var(--admin-border-subtle)");"></div>
}
}
</div>
<div style="display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:6px;margin-bottom:6px;">
@for (int i = 1; i <= 6; i++)
{
<span style="text-align:center;font-size:12px;font-weight:@(i == currentStep ? "600" : "500");color:@(i == currentStep ? "var(--admin-orange-primary)" : "var(--admin-text-tertiary)");">
@_steps[i - 1].Label
</span>
}
</div>
<p style="font-size:12px;color:var(--admin-text-tertiary);margin:0;">Bước 4/6 — Mời nhân viên cốt lõi vào hệ thống</p>
</section>
<section class="admin-panel" style="padding:32px;display:flex;flex-direction:column;gap:24px;">
<div style="display:flex;flex-direction:column;gap:6px;">
<h2 style="font-size:24px;margin:0;font-weight:700;">Thêm nhân viên</h2>
<p style="margin:0;font-size:14px;color:var(--admin-text-tertiary);">Cung cấp quyền truy cập cho quản lý, thu ngân và nhân viên hỗ trợ.</p>
</div>
@* ═══ MAIN CONTENT ═══ *@
<div style="flex:1;display:flex;align-items:center;justify-content:center;padding:40px;overflow-y:auto;">
<div class="admin-panel" style="width:720px;max-width:100%;padding:32px;border-radius:16px;display:flex;flex-direction:column;gap:24px;">
<div style="display:flex;flex-direction:column;gap:8px;">
<h2 style="font-size:22px;font-weight:700;margin:0;">Thêm nhân viên</h2>
<p style="font-size:14px;color:var(--admin-text-tertiary);margin:0;">Mời nhân viên tham gia hệ thống hoặc bỏ qua để thêm sau</p>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;">
<div class="admin-form-group">
<label class="admin-form-label">Họ tên nhân viên</label>
<input class="admin-form-input" type="text" @bind="_newStaffName" />
</div>
@* Invite form *@
<div style="display:flex;gap:12px;align-items:flex-end;">
<div class="admin-form-group" style="flex:1;">
<label class="admin-form-label">Email nhân viên</label>
<input class="admin-form-input" type="email" placeholder="nhanvien@email.com" @bind="_newStaffEmail" />
<div class="admin-form-group">
<label class="admin-form-label">Email</label>
<input class="admin-form-input" type="email" @bind="_newStaffEmail" />
</div>
<div class="admin-form-group" style="width:200px;">
<div class="admin-form-group">
<label class="admin-form-label">Vai trò</label>
<select class="admin-form-input" @bind="_newStaffRole">
<option value="cashier">Thu ngân</option>
<option value="manager">Quản lý</option>
<option value="cashier">Thu ngân</option>
<option value="barista">Barista</option>
<option value="waiter">Phục vụ</option>
</select>
</div>
<button class="admin-btn-primary" style="height:44px;" @onclick="InviteStaff">
<i data-lucide="user-plus" style="width:18px;height:18px;"></i>
<span>Mời</span>
<div class="admin-form-group">
<label class="admin-form-label">Mã PIN</label>
<input class="admin-form-input" type="text" maxlength="4" @bind="_newStaffPin" />
</div>
</div>
<div style="display:flex;justify-content:flex-end;">
<button class="admin-btn-primary" style="display:flex;align-items:center;gap:8px;" @onclick="AddStaff">
<i data-lucide="user-plus"></i>
<span>Mời nhân viên</span>
</button>
</div>
@* Staff list *@
<div style="display:flex;flex-direction:column;">
<span style="font-size:13px;font-weight:600;color:var(--admin-text-secondary);margin-bottom:8px;">Đã mời (@_staffList.Count)</span>
<div style="display:flex;flex-direction:column;gap:10px;">
<div style="display:flex;align-items:center;justify-content:space-between;">
<h3 style="margin:0;font-size:16px;font-weight:600;">Nhân viên đã mời (@_staffList.Count)</h3>
<span style="font-size:12px;color:var(--admin-text-tertiary);">Những mã PIN có thể đổi sau</span>
</div>
<div style="display:flex;flex-direction:column;gap:10px;">
@foreach (var staff in _staffList)
{
var s = staff;
<div style="display:flex;align-items:center;justify-content:space-between;padding:14px 0;border-bottom:1px solid var(--admin-border-subtle);">
<div style="display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-radius:12px;border:1px solid var(--admin-border-subtle);background-color:var(--admin-bg-interactive);">
<div style="display:flex;align-items:center;gap:12px;">
<div class="admin-user-avatar" style="width:36px;height:36px;font-size:13px;background-color:@s.Color;">@s.Initials</div>
<div style="display:flex;flex-direction:column;gap:2px;">
<span style="font-size:14px;font-weight:600;">@s.Email</span>
<span style="font-size:12px;color:var(--admin-text-tertiary);">@s.Role</span>
<div style="width:40px;height:40px;border-radius:10px;background-color:@staff.Color;display:flex;align-items:center;justify-content:center;font-weight:600;color:#FFF;">
@staff.Initials
</div>
<div>
<div style="font-weight:600;">@staff.Name</div>
<div style="font-size:12px;color:var(--admin-text-tertiary);">@staff.Role</div>
</div>
</div>
<div class="admin-status-badge admin-status-badge--setup">
<div style="display:flex;flex-direction:column;align-items:flex-end;gap:4px;">
<span style="font-size:12px;color:var(--admin-text-tertiary);">PIN: @staff.Pin</span>
<span class="admin-status-badge admin-status-badge--setup">
<div class="admin-status-badge__dot"></div>
Đang chờ
</span>
</div>
</div>
}
</div>
</div>
@* Footer *@
<div style="display:flex;justify-content:space-between;padding-top:8px;">
<div style="display:flex;gap:12px;">
<button class="admin-btn-secondary" @onclick="@(() => NavigateTo("onboarding/products"))">
<div style="display:flex;justify-content:space-between;flex-wrap:wrap;gap:12px;">
<button class="admin-btn-secondary" @onclick='() => NavigateTo("onboarding/products")' style="display:flex;gap:8px;align-items:center;">
<i data-lucide="arrow-left"></i>
<span>Quay lại</span>
</button>
</div>
<div style="display:flex;gap:12px;">
<button class="admin-btn-secondary" @onclick="@(() => NavigateTo("onboarding/device"))">
<span>Bỏ qua</span>
</button>
<button class="admin-btn-primary" @onclick="@(() => NavigateTo("onboarding/device"))">
<button class="admin-btn-secondary" @onclick='() => NavigateTo("onboarding/device")'>Bỏ qua</button>
<button class="admin-btn-primary" @onclick='() => NavigateTo("onboarding/device")' style="display:flex;gap:8px;align-items:center;">
<span>Tiếp tục</span>
<i data-lucide="arrow-right"></i>
</button>
</div>
</div>
</div>
</section>
</div>
</div>
@code {
private string _newStaffEmail = "";
private int currentStep = 4;
private string _newStaffName = "Nguyễn Văn A";
private string _newStaffEmail = "van.a@goodgo.vn";
private string _newStaffRole = "cashier";
private record StaffItem(string Email, string Role, string Initials, string Color);
private List<StaffItem> _staffList = new()
{
new("nguyenvana@email.com", "Quản lý", "NA", "#3B82F6"),
new("tranthib@email.com", "Thu ngân", "TB", "#8B5CF6"),
};
private void InviteStaff()
{
if (!string.IsNullOrWhiteSpace(_newStaffEmail))
{
var initials = _newStaffEmail[..2].ToUpper();
var roleName = _newStaffRole switch { "manager" => "Quản lý", "barista" => "Barista", "waiter" => "Phục vụ", _ => "Thu ngân" };
_staffList.Add(new(_newStaffEmail, roleName, initials, "#EC4899"));
_newStaffEmail = "";
}
}
private string _newStaffPin = "1234";
private record StepInfo(int Index, string Label);
private readonly StepInfo[] _steps = new[]
private readonly StepInfo[] _steps =
{
new StepInfo(1, "Doanh nghiệp"),
new StepInfo(2, "Cửa hàng"),
@@ -152,4 +140,46 @@
new StepInfo(5, "Thiết bị"),
new StepInfo(6, "Hoàn tất"),
};
private record StaffItem(string Name, string Email, string Role, string Pin, string Initials, string Color);
private List<StaffItem> _staffList = new()
{
new("Nguyễn Văn A", "manager@goodgo.vn", "Quản lý", "8974", "VA", "#3B82F6"),
new("Trần Thị B", "cashier@goodgo.vn", "Thu ngân", "5621", "TB", "#EC4899"),
};
private void AddStaff()
{
if (string.IsNullOrWhiteSpace(_newStaffName) || string.IsNullOrWhiteSpace(_newStaffEmail))
{
return;
}
var initials = string.Empty;
foreach (var chunk in _newStaffName.Split(' ', StringSplitOptions.RemoveEmptyEntries))
{
if (chunk.Length > 0)
{
initials += char.ToUpper(chunk[0]);
}
}
if (string.IsNullOrEmpty(initials) && _newStaffEmail.Length >= 2)
{
initials = _newStaffEmail.Substring(0, 2).ToUpper();
}
var roleLabel = _newStaffRole switch
{
"manager" => "Quản lý",
"barista" => "Barista",
"waiter" => "Phục vụ",
_ => "Thu ngân",
};
_staffList.Insert(0, new StaffItem(_newStaffName, _newStaffEmail, roleLabel, _newStaffPin, initials, "#8B5CF6"));
_newStaffName = string.Empty;
_newStaffEmail = string.Empty;
_newStaffPin = string.Empty;
}
}

View File

@@ -3,125 +3,106 @@
@inherits AdminBase
@*
EN: Onboarding Step 2 — First store setup (name, address, phone, business type).
VI: Bước 2 thiết lập — Tạo cửa hàng đầu tiên (tên, địa chỉ, SĐT, loại hình).
EN: Onboarding Step 2 — First store setup (name, address, phone, operating hours, store type)
VI: Bước 2 thiết lập — Tạo cửa hàng đầu tiên (tên, địa chỉ, SĐT, giờ hoạt động, loại hình)
Design: pencil-design/src/pages/tPOS/admin/onboarding-store.pen
*@
<PageTitle>Tạo cửa hàng — GoodGo Admin</PageTitle>
<div style="display:flex;height:100vh;overflow:hidden;">
<div class="admin-content" style="padding:40px;">
<div class="admin-panel" style="max-width:980px;margin:0 auto;display:flex;flex-direction:column;gap:32px;">
@* ═══ SIDEBAR — Step Progress ═══ *@
<div style="width:260px;min-width:260px;background-color:var(--admin-bg-elevated);border-right:1px solid var(--admin-border-subtle);display:flex;flex-direction:column;height:100vh;">
<div style="display:flex;align-items:center;gap:12px;padding:24px;border-bottom:1px solid var(--admin-border-subtle);">
<div style="width:40px;height:40px;border-radius:12px;background:var(--admin-orange-gradient);display:flex;align-items:center;justify-content:center;color:#FFF;font-size:20px;font-weight:800;">G</div>
<div style="display:flex;flex-direction:column;gap:2px;">
<span style="font-size:16px;font-weight:700;">GoodGo Admin</span>
<span style="font-size:11px;color:var(--admin-text-tertiary);">Thiết lập ban đầu</span>
</div>
</div>
<div style="flex:1;padding:32px 24px;display:flex;flex-direction:column;">
<span style="font-size:10px;font-weight:700;color:var(--admin-text-tertiary);letter-spacing:0.05em;margin-bottom:16px;">TIẾN TRÌNH</span>
@foreach (var s in _steps)
<section>
<div style="display:flex;gap:8px;align-items:center;margin-bottom:12px;">
@for (int i = 1; i <= 6; i++)
{
var step = s;
var isCompleted = step.Index < 2;
var isCurrent = step.Index == 2;
<div style="display:flex;align-items:center;gap:14px;padding:10px 8px;border-radius:10px;
background-color:@(isCurrent ? "rgba(255,92,0,0.08)" : "transparent");">
<div style="width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:700;
background-color:@(isCompleted ? "#22C55E" : isCurrent ? "var(--admin-orange-primary)" : "var(--admin-bg-interactive)");
color:@(isCompleted || isCurrent ? "#FFF" : "var(--admin-text-tertiary)");
border:@(!isCompleted && !isCurrent ? "2px solid var(--admin-border-default)" : "none");">
@(isCompleted ? "✓" : step.Index)
<div style="width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:14px;
background-color:@(i == currentStep ? "var(--admin-orange-primary)" : i < currentStep ? "rgba(34,197,94,0.2)" : "var(--admin-bg-interactive)");
color:@(i == currentStep ? "#FFF" : i < currentStep ? "#22C55E" : "var(--admin-text-tertiary)");">
@(i < currentStep ? "✓" : i)
</div>
<span style="font-size:14px;font-weight:@(isCurrent ? "600" : "500");color:@(isCompleted ? "var(--admin-text-primary)" : isCurrent ? "var(--admin-orange-primary)" : "var(--admin-text-tertiary)");">@step.Label</span>
</div>
@if (step.Index < 6)
@if (i < 6)
{
<div style="width:2px;height:24px;margin-left:23px;background-color:@(isCompleted ? "#22C55E" : "var(--admin-border-default)");"></div>
<div style="flex:1;height:2px;background-color:@(i < currentStep ? "#22C55E" : "var(--admin-border-subtle)");"></div>
}
}
</div>
<div style="display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:6px;margin-bottom:6px;">
@for (int i = 1; i <= 6; i++)
{
<span style="text-align:center;font-size:12px;font-weight:@(i == currentStep ? "600" : "500");color:@(i == currentStep ? "var(--admin-orange-primary)" : "var(--admin-text-tertiary)");">
@_steps[i - 1].Label
</span>
}
</div>
<p style="font-size:12px;color:var(--admin-text-tertiary);margin:0;">Bước 2/6 — Khởi tạo cửa hàng dựa trên thông tin doanh nghiệp hiện tại</p>
</section>
<section class="admin-panel" style="padding:32px;display:flex;flex-direction:column;gap:24px;">
<div style="display:flex;flex-direction:column;gap:6px;">
<h2 style="font-size:24px;margin:0;font-weight:700;">Tạo cửa hàng đầu tiên</h2>
<p style="margin:0;font-size:14px;color:var(--admin-text-tertiary);">Phiên bản đầu tiên của cửa hàng sẽ thừa hưởng từ <strong>@_parentBusinessName</strong>.</p>
</div>
@* ═══ MAIN CONTENT ═══ *@
<div style="flex:1;display:flex;align-items:center;justify-content:center;padding:40px;overflow-y:auto;">
<div class="admin-panel" style="width:720px;max-width:100%;padding:32px;border-radius:16px;display:flex;flex-direction:column;gap:24px;">
<div style="display:flex;flex-direction:column;gap:8px;">
<h2 style="font-size:22px;font-weight:700;margin:0;">Tạo cửa hàng đầu tiên</h2>
<p style="font-size:14px;color:var(--admin-text-tertiary);margin:0;">Thiết lập thông tin cửa hàng và chọn loại hình kinh doanh</p>
</div>
@* Store name *@
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;">
<div class="admin-form-group">
<label class="admin-form-label">Tên cửa hàng <span style="color:var(--admin-danger);">*</span></label>
<input class="admin-form-input" type="text" placeholder="VD: Coffee House Q1" @bind="_storeName" />
<label class="admin-form-label">Tên cửa hàng</label>
<input class="admin-form-input" type="text" @bind="_storeName" />
</div>
<div class="admin-form-group">
<label class="admin-form-label">Địa chỉ</label>
<input class="admin-form-input" type="text" @bind="_storeAddress" />
</div>
<div class="admin-form-group">
<label class="admin-form-label">Số điện thoại</label>
<input class="admin-form-input" type="tel" @bind="_storePhone" />
</div>
<div class="admin-form-group">
<label class="admin-form-label">Giờ hoạt động</label>
<input class="admin-form-input" type="text" @bind="_storeHours" />
</div>
</div>
@* Address *@
<div class="admin-form-group">
<label class="admin-form-label">Địa chỉ <span style="color:var(--admin-danger);">*</span></label>
<input class="admin-form-input" type="text" placeholder="123 Nguyễn Huệ, Quận 1, TP.HCM" @bind="_storeAddress" />
</div>
@* Phone *@
<div class="admin-form-group">
<label class="admin-form-label">Số điện thoại <span style="color:var(--admin-danger);">*</span></label>
<input class="admin-form-input" type="tel" placeholder="0901 234 567" @bind="_storePhone" />
</div>
@* Business type selector *@
<div class="admin-form-group">
<label class="admin-form-label">Chọn loại hình kinh doanh <span style="color:var(--admin-danger);">*</span></label>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
<div>
<label class="admin-form-label">Loại hình cửa hàng</label>
<div style="display:flex;flex-wrap:wrap;gap:12px;padding-top:8px;">
@foreach (var type in _storeTypes)
{
var t = type;
<button class="admin-type-card @(_selectedType == t.Key ? "admin-type-card--active" : "")" @onclick="@(() => _selectedType = t.Key)">
<i data-lucide="@t.Icon" style="width:24px;height:24px;color:@(t.Color);"></i>
<span style="font-weight:600;font-size:14px;">@t.Label</span>
<span style="font-size:12px;color:var(--admin-text-tertiary);">@t.Desc</span>
<button type="button" class="admin-btn-secondary" style="min-width:140px;border:1px solid var(--admin-border-default);display:flex;flex-direction:column;align-items:flex-start;gap:4px;padding:14px;background-color:@(type.Key == _storeType ? "var(--admin-orange-primary)" : "var(--admin-bg-interactive)");
color:@(type.Key == _storeType ? "#FFF" : "var(--admin-text-primary)");" @onclick="() => _storeType = type.Key">
<span style="font-size:14px;font-weight:600;">@type.Label</span>
<span style="font-size:12px;color:@(type.Key == _storeType ? "rgba(255,255,255,0.8)" : "var(--admin-text-tertiary)");">@type.Description</span>
</button>
}
</div>
</div>
@* Footer *@
<div style="display:flex;justify-content:space-between;padding-top:8px;">
<button class="admin-btn-secondary" @onclick="@(() => NavigateTo("onboarding/business"))">
<div style="display:flex;justify-content:space-between;flex-wrap:wrap;gap:12px;">
<button class="admin-btn-secondary" @onclick='() => NavigateTo("onboarding/business")' style="display:flex;gap:8px;align-items:center;">
<i data-lucide="arrow-left"></i>
<span>Quay lại</span>
</button>
<button class="admin-btn-primary" @onclick="@(() => NavigateTo("onboarding/products"))">
<button class="admin-btn-primary" @onclick='() => NavigateTo("onboarding/products")' style="display:flex;gap:8px;align-items:center;">
<span>Tiếp tục</span>
<i data-lucide="arrow-right"></i>
</button>
</div>
</div>
</section>
</div>
</div>
@code {
private string _storeName = "";
private string _storeAddress = "";
private string _storePhone = "";
private string? _selectedType;
private record StoreType(string Key, string Label, string Icon, string Color, string Desc);
private readonly StoreType[] _storeTypes = new[]
{
new StoreType("cafe", "Café", "coffee", "#FF5C00", "Quán cà phê, trà sữa"),
new StoreType("restaurant", "Nhà hàng", "utensils", "#3B82F6", "Nhà hàng, quán ăn"),
new StoreType("karaoke", "Karaoke", "mic", "#8B5CF6", "Karaoke, giải trí"),
new StoreType("spa", "Spa & Beauty", "sparkles", "#EC4899", "Spa, thẩm mỹ, nail"),
new StoreType("retail", "Bán lẻ", "shopping-bag", "#22C55E", "Cửa hàng bán lẻ"),
};
private int currentStep = 2;
private string _parentBusinessName = "GoodGo Bistro";
private string _storeName = "GoodGo Coffee Q1";
private string _storeAddress = "123 Nguyễn Huệ, Quận 1, TP.HCM";
private string _storePhone = "0901 234 567";
private string _storeHours = "08:00 - 22:00";
private string _storeType = "cafe";
private record StepInfo(int Index, string Label);
private readonly StepInfo[] _steps = new[]
private readonly StepInfo[] _steps =
{
new StepInfo(1, "Doanh nghiệp"),
new StepInfo(2, "Cửa hàng"),
@@ -130,4 +111,13 @@
new StepInfo(5, "Thiết bị"),
new StepInfo(6, "Hoàn tất"),
};
private record StoreType(string Key, string Label, string Description);
private readonly StoreType[] _storeTypes =
{
new StoreType("cafe", "Café", "Cà phê, trà sữa"),
new StoreType("restaurant", "Nhà hàng", "Đồ ăn, buffet"),
new StoreType("retail", "Kinh doanh", "Bán lẻ, cửa hàng"),
new StoreType("services", "Dịch vụ", "Spa, salon, gym"),
};
}