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:
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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[]
|
||||
_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()
|
||||
{
|
||||
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"),
|
||||
};
|
||||
_importStatus = "Đang phân tích 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ị"),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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, mã 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user