feat(karaoke): add 4 missing Blazor Razor workflow files
- KaraokeJourney.razor: 6-step end-to-end session workflow tracker - PeakWarning.razor: Peak hours pricing warning with cost estimator - RoomExtend.razor: Room extension dialog with time options and preview - RoomReset.razor: Room cleanup/reset checklist with progress tracking All files follow existing Karaoke patterns (PosLayout, PosBase, FormatPrice, NavigateTo, bilingual comments, section markers, CSS vars, Lucide icons, Vietnamese demo data). Co-authored-by: Velik <hongochai10@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
@*
|
||||
EN: Karaoke Journey — End-to-end session workflow tracker with 6 steps from reception to payment.
|
||||
VI: Hành trình Karaoke — Theo dõi quy trình phiên từ đón khách đến thanh toán qua 6 bước.
|
||||
*@
|
||||
@page "/pos/karaoke/karaoke-journey"
|
||||
@layout PosLayout
|
||||
@inherits PosBase
|
||||
|
||||
<div style="flex:1;display:flex;flex-direction:column;overflow:hidden;">
|
||||
@* ═══ HEADER / TIÊU ĐỀ ═══ *@
|
||||
<div style="display:flex;align-items:center;gap:12px;padding:12px 16px;border-bottom:1px solid var(--pos-border-subtle);flex-shrink:0;">
|
||||
<button style="background:var(--pos-bg-interactive);border:none;color:var(--pos-text-primary);
|
||||
padding:8px 12px;border-radius:8px;cursor:pointer;font-size:13px;"
|
||||
@onclick="@(() => NavigateTo("karaoke"))">
|
||||
<i data-lucide="arrow-left" style="width:16px;height:16px;display:inline;"></i>
|
||||
</button>
|
||||
<span style="font-size:16px;font-weight:700;">Hành trình Karaoke</span>
|
||||
<span style="font-size:12px;color:var(--pos-text-tertiary);">Bước @(_activeStep + 1)/6</span>
|
||||
</div>
|
||||
|
||||
@* ═══ STEP INDICATOR / CHỈ BÁO BƯỚC ═══ *@
|
||||
<div style="display:flex;align-items:center;padding:16px;gap:4px;flex-shrink:0;overflow-x:auto;">
|
||||
@for (var i = 0; i < _steps.Length; i++)
|
||||
{
|
||||
var idx = i;
|
||||
<div @onclick="() => _activeStep = idx"
|
||||
style="flex:1;display:flex;flex-direction:column;align-items:center;gap:6px;cursor:pointer;min-width:90px;">
|
||||
<div style="width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;
|
||||
background:@(idx == _activeStep ? "var(--pos-orange-primary)" : idx < _activeStep ? "rgba(34,197,94,.15)" : "var(--pos-bg-interactive)");
|
||||
color:@(idx == _activeStep ? "#FFF" : idx < _activeStep ? "#22C55E" : "var(--pos-text-tertiary)");
|
||||
transition:all .2s ease;">
|
||||
<i data-lucide="@_steps[idx].Icon" style="width:18px;height:18px;"></i>
|
||||
</div>
|
||||
<span style="font-size:11px;font-weight:@(idx == _activeStep ? "700" : "500");text-align:center;
|
||||
color:@(idx == _activeStep ? "var(--pos-orange-primary)" : idx < _activeStep ? "#22C55E" : "var(--pos-text-tertiary)");">
|
||||
@_steps[idx].Label
|
||||
</span>
|
||||
</div>
|
||||
@if (i < _steps.Length - 1)
|
||||
{
|
||||
<div style="flex:0 0 20px;height:2px;background:@(i < _activeStep ? "#22C55E" : "var(--pos-border-subtle)");
|
||||
margin-top:-18px;"></div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
@* ═══ STEP CONTENT / NỘI DUNG BƯỚC ═══ *@
|
||||
<div style="flex:1;overflow-y:auto;padding:16px;">
|
||||
@switch (_activeStep)
|
||||
{
|
||||
@* EN: Step 1 — Guest reception / VI: Bước 1 — Đón khách *@
|
||||
case 0:
|
||||
<div style="background:var(--pos-bg-elevated);border-radius:var(--pos-radius);padding:20px;margin-bottom:16px;">
|
||||
<div style="font-size:14px;font-weight:600;margin-bottom:12px;">
|
||||
<i data-lucide="users" style="width:16px;height:16px;display:inline;"></i> Đón khách
|
||||
</div>
|
||||
<div style="display:flex;gap:16px;margin-bottom:16px;">
|
||||
<div style="flex:1;">
|
||||
<div style="font-size:12px;color:var(--pos-text-tertiary);margin-bottom:6px;">Số khách</div>
|
||||
<div style="display:flex;align-items:center;gap:12px;">
|
||||
<button style="width:36px;height:36px;border-radius:8px;border:1px solid var(--pos-border-default);
|
||||
background:transparent;color:var(--pos-text-primary);cursor:pointer;font-size:16px;"
|
||||
@onclick="() => _guestCount = Math.Max(1, _guestCount - 1)">−</button>
|
||||
<span style="font-size:24px;font-weight:700;min-width:30px;text-align:center;">@_guestCount</span>
|
||||
<button style="width:36px;height:36px;border-radius:8px;border:1px solid var(--pos-border-default);
|
||||
background:transparent;color:var(--pos-text-primary);cursor:pointer;font-size:16px;"
|
||||
@onclick="() => _guestCount++">+</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex:1;">
|
||||
<div style="font-size:12px;color:var(--pos-text-tertiary);margin-bottom:6px;">Thẻ thành viên</div>
|
||||
<div style="display:flex;gap:8px;">
|
||||
<div style="flex:1;display:flex;align-items:center;gap:8px;background:var(--pos-bg-interactive);
|
||||
border-radius:8px;padding:0 12px;border:1px solid var(--pos-border-default);">
|
||||
<i data-lucide="search" style="width:14px;height:14px;color:var(--pos-text-tertiary);"></i>
|
||||
<input type="text" placeholder="SĐT hoặc mã thẻ..." @bind="_memberSearch"
|
||||
style="flex:1;background:transparent;border:none;color:var(--pos-text-primary);
|
||||
font-size:13px;padding:10px 0;outline:none;" />
|
||||
</div>
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(_memberSearch))
|
||||
{
|
||||
<div style="font-size:12px;color:#22C55E;margin-top:6px;">
|
||||
<i data-lucide="check-circle" style="width:12px;height:12px;display:inline;"></i>
|
||||
Nguyễn Văn Minh — Gold • 2,450 điểm
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
break;
|
||||
|
||||
@* EN: Step 2 — Room selection / VI: Bước 2 — Chọn phòng *@
|
||||
case 1:
|
||||
<div style="background:var(--pos-bg-elevated);border-radius:var(--pos-radius);padding:20px;margin-bottom:16px;">
|
||||
<div style="font-size:14px;font-weight:600;margin-bottom:12px;">
|
||||
<i data-lucide="door-open" style="width:16px;height:16px;display:inline;"></i> Chọn phòng
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;">
|
||||
<div style="text-align:center;padding:16px;background:var(--pos-bg-interactive);border-radius:var(--pos-radius);">
|
||||
<div style="font-size:11px;color:var(--pos-text-tertiary);margin-bottom:4px;">Phòng</div>
|
||||
<div style="font-size:18px;font-weight:700;">VIP 2</div>
|
||||
</div>
|
||||
<div style="text-align:center;padding:16px;background:var(--pos-bg-interactive);border-radius:var(--pos-radius);">
|
||||
<div style="font-size:11px;color:var(--pos-text-tertiary);margin-bottom:4px;">Sức chứa</div>
|
||||
<div style="font-size:18px;font-weight:700;">20 người</div>
|
||||
</div>
|
||||
<div style="text-align:center;padding:16px;background:var(--pos-bg-interactive);border-radius:var(--pos-radius);">
|
||||
<div style="font-size:11px;color:var(--pos-text-tertiary);margin-bottom:4px;">Loại</div>
|
||||
<div style="font-size:18px;font-weight:700;">Deluxe</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-size:13px;color:var(--pos-text-secondary);margin-top:12px;text-align:center;">
|
||||
Tầng 3 • Khu Deluxe • @FormatPrice(200_000)/giờ
|
||||
</div>
|
||||
</div>
|
||||
break;
|
||||
|
||||
@* EN: Step 3 — Open room / VI: Bước 3 — Mở phòng *@
|
||||
case 2:
|
||||
<div style="background:var(--pos-bg-elevated);border-radius:var(--pos-radius);padding:20px;margin-bottom:16px;">
|
||||
<div style="font-size:14px;font-weight:600;margin-bottom:12px;">
|
||||
<i data-lucide="play" style="width:16px;height:16px;display:inline;"></i> Mở phòng
|
||||
</div>
|
||||
<div style="display:flex;flex-direction:column;gap:10px;">
|
||||
<div style="display:flex;justify-content:space-between;font-size:13px;">
|
||||
<span style="color:var(--pos-text-secondary);">Giờ bắt đầu</span>
|
||||
<span style="font-weight:600;">19:30</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:13px;">
|
||||
<span style="color:var(--pos-text-secondary);">Thời lượng đặt</span>
|
||||
<span style="font-weight:600;">2.5 giờ</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:13px;">
|
||||
<span style="color:var(--pos-text-secondary);">Giá/giờ</span>
|
||||
<span style="font-weight:600;">@FormatPrice(200_000)</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:13px;">
|
||||
<span style="color:var(--pos-text-secondary);">Giờ kết thúc dự kiến</span>
|
||||
<span style="font-weight:600;">22:00</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:14px;font-weight:600;
|
||||
padding-top:10px;border-top:1px solid var(--pos-border-subtle);">
|
||||
<span>Tạm tính phòng</span>
|
||||
<span style="color:var(--pos-orange-primary);">@FormatPrice(500_000)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
break;
|
||||
|
||||
@* EN: Step 4 — In room / VI: Bước 4 — Trong phòng *@
|
||||
case 3:
|
||||
<div style="background:var(--pos-bg-elevated);border-radius:var(--pos-radius);padding:24px;
|
||||
text-align:center;margin-bottom:16px;">
|
||||
<div style="font-size:12px;color:var(--pos-text-tertiary);font-weight:600;margin-bottom:8px;">
|
||||
THỜI GIAN SỬ DỤNG
|
||||
</div>
|
||||
<div style="font-size:48px;font-weight:700;color:var(--pos-orange-primary);font-variant-numeric:tabular-nums;">
|
||||
02:15:00
|
||||
</div>
|
||||
<div style="font-size:13px;color:var(--pos-text-secondary);margin-top:8px;">
|
||||
Bắt đầu: <b>19:30</b> • Dự kiến: <b>22:00</b>
|
||||
</div>
|
||||
</div>
|
||||
<div style="background:var(--pos-bg-elevated);border-radius:var(--pos-radius);padding:16px;margin-bottom:16px;">
|
||||
<div style="font-size:13px;font-weight:600;margin-bottom:10px;">Đơn F&B hiện tại</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:13px;margin-bottom:6px;">
|
||||
<span style="color:var(--pos-text-secondary);">Số món</span>
|
||||
<span>6 món</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:14px;font-weight:600;
|
||||
padding-top:8px;border-top:1px solid var(--pos-border-subtle);">
|
||||
<span>Tổng F&B</span>
|
||||
<span style="color:var(--pos-orange-primary);">@FormatPrice(830_000)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
|
||||
<button style="padding:14px;border-radius:var(--pos-radius);background:rgba(59,130,246,.15);
|
||||
border:none;color:#3B82F6;cursor:pointer;font-size:13px;font-weight:500;">
|
||||
<i data-lucide="utensils" style="width:16px;height:16px;display:block;margin:0 auto 4px;"></i>
|
||||
Gọi thêm F&B
|
||||
</button>
|
||||
<button style="padding:14px;border-radius:var(--pos-radius);background:rgba(255,92,0,.15);
|
||||
border:none;color:var(--pos-orange-primary);cursor:pointer;font-size:13px;font-weight:500;">
|
||||
<i data-lucide="clock" style="width:16px;height:16px;display:block;margin:0 auto 4px;"></i>
|
||||
Gia hạn
|
||||
</button>
|
||||
</div>
|
||||
break;
|
||||
|
||||
@* EN: Step 5 — Close room / VI: Bước 5 — Đóng phòng *@
|
||||
case 4:
|
||||
<div style="background:var(--pos-bg-elevated);border-radius:var(--pos-radius);padding:20px;margin-bottom:16px;">
|
||||
<div style="font-size:14px;font-weight:600;margin-bottom:12px;">
|
||||
<i data-lucide="lock" style="width:16px;height:16px;display:inline;"></i> Kết thúc phiên
|
||||
</div>
|
||||
<div style="display:flex;flex-direction:column;gap:8px;">
|
||||
<div style="display:flex;justify-content:space-between;font-size:13px;">
|
||||
<span style="color:var(--pos-text-secondary);">Thời gian sử dụng</span>
|
||||
<span style="font-weight:600;">2 giờ 30 phút</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:13px;">
|
||||
<span style="color:var(--pos-text-secondary);">Tiền phòng</span>
|
||||
<span>@FormatPrice(500_000)</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:13px;">
|
||||
<span style="color:var(--pos-text-secondary);">Tiền F&B</span>
|
||||
<span>@FormatPrice(830_000)</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:15px;font-weight:700;
|
||||
padding-top:10px;border-top:1px solid var(--pos-border-subtle);">
|
||||
<span>Tổng cộng</span>
|
||||
<span style="color:var(--pos-orange-primary);">@FormatPrice(1_330_000)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
break;
|
||||
|
||||
@* EN: Step 6 — Payment / VI: Bước 6 — Thanh toán *@
|
||||
case 5:
|
||||
<div style="background:var(--pos-bg-elevated);border-radius:var(--pos-radius);padding:24px;
|
||||
text-align:center;margin-bottom:16px;">
|
||||
<div style="font-size:12px;color:var(--pos-text-tertiary);font-weight:600;margin-bottom:8px;
|
||||
text-transform:uppercase;letter-spacing:1px;">
|
||||
TỔNG THANH TOÁN
|
||||
</div>
|
||||
<div style="font-size:40px;font-weight:700;color:var(--pos-orange-primary);">
|
||||
@FormatPrice(1_330_000)
|
||||
</div>
|
||||
<div style="font-size:13px;color:var(--pos-text-secondary);margin-top:8px;">
|
||||
Phòng VIP 2 • 2h30 • 6 món F&B
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:16px;">
|
||||
<button style="padding:16px;border-radius:var(--pos-radius);background:rgba(34,197,94,.15);
|
||||
border:none;color:#22C55E;cursor:pointer;font-size:14px;font-weight:600;">
|
||||
<i data-lucide="banknote" style="width:18px;height:18px;display:block;margin:0 auto 4px;"></i>
|
||||
Tiền mặt
|
||||
</button>
|
||||
<button style="padding:16px;border-radius:var(--pos-radius);background:rgba(59,130,246,.15);
|
||||
border:none;color:#3B82F6;cursor:pointer;font-size:14px;font-weight:600;">
|
||||
<i data-lucide="credit-card" style="width:18px;height:18px;display:block;margin:0 auto 4px;"></i>
|
||||
Thẻ/Chuyển khoản
|
||||
</button>
|
||||
</div>
|
||||
break;
|
||||
}
|
||||
</div>
|
||||
|
||||
@* ═══ NAVIGATION BUTTONS / NÚT ĐIỀU HƯỚNG ═══ *@
|
||||
<div style="display:flex;gap:12px;padding:12px 16px;border-top:1px solid var(--pos-border-subtle);flex-shrink:0;">
|
||||
<button style="flex:1;padding:14px;border-radius:var(--pos-radius);background:var(--pos-bg-interactive);
|
||||
border:none;color:var(--pos-text-primary);cursor:pointer;font-size:14px;font-weight:600;
|
||||
opacity:@(_activeStep == 0 ? "0.4" : "1");"
|
||||
disabled="@(_activeStep == 0)"
|
||||
@onclick="() => _activeStep = Math.Max(0, _activeStep - 1)">
|
||||
<i data-lucide="arrow-left" style="width:16px;height:16px;display:inline;"></i> Quay lại
|
||||
</button>
|
||||
<button style="flex:1;padding:14px;border-radius:var(--pos-radius);
|
||||
background:@(_activeStep == _steps.Length - 1 ? "var(--pos-success)" : "var(--pos-orange-primary)");
|
||||
border:none;color:#FFF;cursor:pointer;font-size:14px;font-weight:600;"
|
||||
@onclick="() => _activeStep = Math.Min(_steps.Length - 1, _activeStep + 1)">
|
||||
@(_activeStep == _steps.Length - 1 ? "Hoàn tất" : "Tiếp")
|
||||
<i data-lucide="arrow-right" style="width:16px;height:16px;display:inline;"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
// EN: Active step index / VI: Chỉ số bước hiện tại
|
||||
private int _activeStep;
|
||||
private int _guestCount = 8;
|
||||
private string _memberSearch = "0901234567";
|
||||
|
||||
// EN: Journey steps / VI: Các bước hành trình
|
||||
private readonly StepInfo[] _steps =
|
||||
{
|
||||
new("Đón khách", "users"),
|
||||
new("Chọn phòng", "door-open"),
|
||||
new("Mở phòng", "play"),
|
||||
new("Trong phòng", "music"),
|
||||
new("Đóng phòng", "lock"),
|
||||
new("Thanh toán", "credit-card"),
|
||||
};
|
||||
|
||||
private record StepInfo(string Label, string Icon);
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
@*
|
||||
EN: Karaoke Peak Warning — Peak hours pricing comparison, room type multipliers, cost estimator.
|
||||
VI: Cảnh báo giờ cao điểm Karaoke — So sánh giá giờ cao điểm, hệ số phòng, ước tính chi phí.
|
||||
*@
|
||||
@page "/pos/karaoke/peak-warning"
|
||||
@layout PosLayout
|
||||
@inherits PosBase
|
||||
|
||||
<div style="flex:1;display:flex;flex-direction:column;overflow:hidden;">
|
||||
@* ═══ HEADER / TIÊU ĐỀ ═══ *@
|
||||
<div style="display:flex;align-items:center;gap:12px;padding:12px 16px;border-bottom:1px solid var(--pos-border-subtle);flex-shrink:0;">
|
||||
<button style="background:var(--pos-bg-interactive);border:none;color:var(--pos-text-primary);
|
||||
padding:8px 12px;border-radius:8px;cursor:pointer;font-size:13px;"
|
||||
@onclick="@(() => NavigateTo("karaoke"))">
|
||||
<i data-lucide="arrow-left" style="width:16px;height:16px;display:inline;"></i>
|
||||
</button>
|
||||
<span style="font-size:16px;font-weight:700;">
|
||||
<i data-lucide="alert-triangle" style="width:18px;height:18px;display:inline;color:var(--pos-warning);"></i>
|
||||
Giờ cao điểm
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div style="flex:1;overflow-y:auto;padding:16px;">
|
||||
@* ═══ CURRENT TIME / THỜI GIAN HIỆN TẠI ═══ *@
|
||||
<div style="background:linear-gradient(135deg,rgba(245,158,11,.2),rgba(239,68,68,.15));
|
||||
border-radius:var(--pos-radius);padding:24px;margin-bottom:16px;text-align:center;
|
||||
border:1px solid rgba(245,158,11,.3);">
|
||||
<div style="font-size:12px;font-weight:600;color:var(--pos-warning);text-transform:uppercase;letter-spacing:1px;">
|
||||
KHUNG GIỜ HIỆN TẠI
|
||||
</div>
|
||||
<div style="font-size:36px;font-weight:700;color:var(--pos-warning);margin:8px 0;">
|
||||
20:30 — Thứ 7
|
||||
</div>
|
||||
<div style="font-size:13px;color:var(--pos-text-secondary);">
|
||||
Đang áp dụng giá <b style="color:var(--pos-warning);">cuối tuần</b>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@* ═══ PRICING TABLE / BẢNG GIÁ ═══ *@
|
||||
<div style="margin-bottom:20px;">
|
||||
<div style="font-size:14px;font-weight:600;margin-bottom:12px;">Bảng giá theo khung giờ (Standard)</div>
|
||||
<div style="display:flex;flex-direction:column;gap:8px;">
|
||||
@foreach (var rate in _pricingRates)
|
||||
{
|
||||
<div style="display:flex;align-items:center;gap:12px;padding:14px 16px;
|
||||
background:@(rate.IsActive ? "rgba(245,158,11,.12)" : "var(--pos-bg-elevated)");
|
||||
border-radius:var(--pos-radius);
|
||||
border:1px solid @(rate.IsActive ? "var(--pos-warning)" : "var(--pos-border-subtle)");">
|
||||
<div style="flex:1;">
|
||||
<div style="font-size:13px;font-weight:600;color:@(rate.IsActive ? "var(--pos-warning)" : "var(--pos-text-primary)");">
|
||||
@rate.Label
|
||||
</div>
|
||||
<div style="font-size:11px;color:var(--pos-text-tertiary);margin-top:2px;">@rate.TimeRange</div>
|
||||
</div>
|
||||
<div style="text-align:right;">
|
||||
<div style="font-size:14px;font-weight:700;color:@(rate.IsActive ? "var(--pos-warning)" : "var(--pos-text-primary)");">
|
||||
@FormatPrice(rate.Price)/giờ
|
||||
</div>
|
||||
<div style="font-size:11px;color:var(--pos-text-tertiary);">x@rate.Multiplier.ToString("0.0")</div>
|
||||
</div>
|
||||
@if (rate.IsActive)
|
||||
{
|
||||
<span style="font-size:11px;padding:3px 8px;border-radius:6px;font-weight:600;
|
||||
background:rgba(245,158,11,.2);color:var(--pos-warning);">Hiện tại</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@* ═══ ROOM TYPE SELECTOR / CHỌN LOẠI PHÒNG ═══ *@
|
||||
<div style="margin-bottom:20px;">
|
||||
<div style="font-size:14px;font-weight:600;margin-bottom:12px;">Loại phòng</div>
|
||||
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:10px;">
|
||||
@foreach (var room in _roomTypes)
|
||||
{
|
||||
<button @onclick="() => _selectedRoomType = room"
|
||||
style="padding:16px;border-radius:var(--pos-radius);text-align:center;cursor:pointer;
|
||||
background:@(_selectedRoomType.Name == room.Name ? "var(--pos-orange-primary)" : "var(--pos-bg-elevated)");
|
||||
color:@(_selectedRoomType.Name == room.Name ? "#FFF" : "var(--pos-text-primary)");
|
||||
border:1px solid @(_selectedRoomType.Name == room.Name ? "var(--pos-orange-primary)" : "var(--pos-border-subtle)");">
|
||||
<div style="font-size:14px;font-weight:700;">@room.Name</div>
|
||||
<div style="font-size:12px;margin-top:4px;opacity:0.7;">x@room.Multiplier.ToString("0.0")</div>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@* ═══ COST ESTIMATOR / ƯỚC TÍNH CHI PHÍ ═══ *@
|
||||
<div style="background:var(--pos-bg-elevated);border-radius:var(--pos-radius);padding:20px;">
|
||||
<div style="font-size:14px;font-weight:600;margin-bottom:12px;">Ước tính chi phí</div>
|
||||
<div style="display:flex;align-items:center;gap:12px;margin-bottom:16px;">
|
||||
<span style="font-size:13px;color:var(--pos-text-secondary);">Số giờ:</span>
|
||||
<button style="width:36px;height:36px;border-radius:8px;border:1px solid var(--pos-border-default);
|
||||
background:transparent;color:var(--pos-text-primary);cursor:pointer;font-size:16px;"
|
||||
@onclick="() => _estimateHours = Math.Max(1, _estimateHours - 1)">−</button>
|
||||
<span style="font-size:20px;font-weight:700;min-width:30px;text-align:center;">@_estimateHours</span>
|
||||
<button style="width:36px;height:36px;border-radius:8px;border:1px solid var(--pos-border-default);
|
||||
background:transparent;color:var(--pos-text-primary);cursor:pointer;font-size:16px;"
|
||||
@onclick="() => _estimateHours++">+</button>
|
||||
<span style="font-size:13px;color:var(--pos-text-tertiary);">giờ</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:13px;margin-bottom:6px;">
|
||||
<span style="color:var(--pos-text-secondary);">Giá hiện tại (@_selectedRoomType.Name)</span>
|
||||
<span>@FormatPrice(CurrentRatePrice)/giờ</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:13px;margin-bottom:6px;">
|
||||
<span style="color:var(--pos-text-secondary);">Số giờ</span>
|
||||
<span>@_estimateHours giờ</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:16px;font-weight:700;
|
||||
padding-top:10px;border-top:1px solid var(--pos-border-subtle);">
|
||||
<span>Tổng ước tính</span>
|
||||
<span style="color:var(--pos-orange-primary);">@FormatPrice(CurrentRatePrice * _estimateHours)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@* ═══ CONFIRM BUTTON / NÚT XÁC NHẬN ═══ *@
|
||||
<div style="padding:12px 16px;border-top:1px solid var(--pos-border-subtle);flex-shrink:0;">
|
||||
<button style="width:100%;padding:16px;border-radius:var(--pos-radius);background:var(--pos-orange-primary);
|
||||
border:none;color:#FFF;cursor:pointer;font-size:15px;font-weight:700;"
|
||||
@onclick="@(() => NavigateTo("karaoke/room-select"))">
|
||||
<i data-lucide="check" style="width:18px;height:18px;display:inline;"></i> Xác nhận giá
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
// EN: Estimate hours / VI: Số giờ ước tính
|
||||
private int _estimateHours = 2;
|
||||
|
||||
// EN: Pricing rates / VI: Bảng giá
|
||||
private readonly List<PricingRate> _pricingRates = new()
|
||||
{
|
||||
new("Giờ thường", "T2–T5, 10:00–17:00", 100_000, 1.0m, false),
|
||||
new("Giờ cao điểm", "T2–T5, 17:00–23:00", 150_000, 1.5m, false),
|
||||
new("Cuối tuần", "T6–CN", 180_000, 1.8m, true),
|
||||
new("Lễ/Tết", "Ngày lễ, Tết", 250_000, 2.5m, false),
|
||||
};
|
||||
|
||||
// EN: Room types / VI: Loại phòng
|
||||
private readonly RoomType[] _roomTypes =
|
||||
{
|
||||
new("Standard", 1.0m),
|
||||
new("Deluxe", 1.5m),
|
||||
new("VIP", 2.0m),
|
||||
};
|
||||
|
||||
private RoomType _selectedRoomType = null!;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_selectedRoomType = _roomTypes[0];
|
||||
}
|
||||
|
||||
// EN: Current active rate price adjusted for room type / VI: Giá hiện tại theo loại phòng
|
||||
private decimal CurrentRatePrice
|
||||
{
|
||||
get
|
||||
{
|
||||
var activeRate = _pricingRates.FirstOrDefault(r => r.IsActive) ?? _pricingRates[0];
|
||||
return activeRate.Price * _selectedRoomType.Multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
private record PricingRate(string Label, string TimeRange, decimal Price, decimal Multiplier, bool IsActive);
|
||||
private record RoomType(string Name, decimal Multiplier);
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
@*
|
||||
EN: Karaoke Room Extend — Extension dialog with time options, new end time preview, peak warning.
|
||||
VI: Gia hạn phòng Karaoke — Dialog gia hạn với tùy chọn thời gian, xem trước giờ kết thúc, cảnh báo giờ cao điểm.
|
||||
*@
|
||||
@page "/pos/karaoke/room-extend"
|
||||
@layout PosLayout
|
||||
@inherits PosBase
|
||||
|
||||
<div style="flex:1;display:flex;flex-direction:column;overflow:hidden;">
|
||||
@* ═══ HEADER / TIÊU ĐỀ ═══ *@
|
||||
<div style="display:flex;align-items:center;gap:12px;padding:12px 16px;border-bottom:1px solid var(--pos-border-subtle);flex-shrink:0;">
|
||||
<button style="background:var(--pos-bg-interactive);border:none;color:var(--pos-text-primary);
|
||||
padding:8px 12px;border-radius:8px;cursor:pointer;font-size:13px;"
|
||||
@onclick="@(() => NavigateTo("karaoke/room-session"))">
|
||||
<i data-lucide="arrow-left" style="width:16px;height:16px;display:inline;"></i>
|
||||
</button>
|
||||
<span style="font-size:16px;font-weight:700;">Gia hạn phòng</span>
|
||||
</div>
|
||||
|
||||
<div style="flex:1;overflow-y:auto;padding:16px;">
|
||||
@* ═══ CURRENT SESSION INFO / THÔNG TIN PHIÊN HIỆN TẠI ═══ *@
|
||||
<div style="background:var(--pos-bg-elevated);border-radius:var(--pos-radius);padding:20px;margin-bottom:16px;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:start;margin-bottom:12px;">
|
||||
<div>
|
||||
<div style="font-size:18px;font-weight:700;">Phòng VIP 2</div>
|
||||
<div style="font-size:12px;color:var(--pos-text-tertiary);">Deluxe • 20 người • Tầng 3</div>
|
||||
</div>
|
||||
<span style="font-size:12px;padding:4px 10px;border-radius:6px;font-weight:600;
|
||||
background:rgba(34,197,94,.15);color:#22C55E;">Đang hoạt động</span>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;">
|
||||
<div style="text-align:center;padding:12px;background:var(--pos-bg-interactive);border-radius:8px;">
|
||||
<div style="font-size:11px;color:var(--pos-text-tertiary);margin-bottom:4px;">Bắt đầu</div>
|
||||
<div style="font-size:16px;font-weight:700;">19:30</div>
|
||||
</div>
|
||||
<div style="text-align:center;padding:12px;background:var(--pos-bg-interactive);border-radius:8px;">
|
||||
<div style="font-size:11px;color:var(--pos-text-tertiary);margin-bottom:4px;">Đã dùng</div>
|
||||
<div style="font-size:16px;font-weight:700;color:var(--pos-orange-primary);">2h15</div>
|
||||
</div>
|
||||
<div style="text-align:center;padding:12px;background:var(--pos-bg-interactive);border-radius:8px;">
|
||||
<div style="font-size:11px;color:var(--pos-text-tertiary);margin-bottom:4px;">Giá/giờ</div>
|
||||
<div style="font-size:16px;font-weight:700;">@FormatPrice(200_000)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:13px;margin-top:12px;
|
||||
padding-top:10px;border-top:1px solid var(--pos-border-subtle);">
|
||||
<span style="color:var(--pos-text-secondary);">Tiền phòng hiện tại</span>
|
||||
<span style="font-weight:600;">@FormatPrice(450_000)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@* ═══ EXTENSION OPTIONS / TÙY CHỌN GIA HẠN ═══ *@
|
||||
<div style="margin-bottom:16px;">
|
||||
<div style="font-size:14px;font-weight:600;margin-bottom:12px;">Chọn thời gian gia hạn</div>
|
||||
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:10px;">
|
||||
@foreach (var opt in _extendOptions)
|
||||
{
|
||||
<button @onclick="() => SelectExtension(opt)"
|
||||
style="padding:20px 12px;border-radius:var(--pos-radius);text-align:center;cursor:pointer;
|
||||
background:@(_selectedOption?.Minutes == opt.Minutes ? "var(--pos-orange-primary)" : "var(--pos-bg-elevated)");
|
||||
color:@(_selectedOption?.Minutes == opt.Minutes ? "#FFF" : "var(--pos-text-primary)");
|
||||
border:1px solid @(_selectedOption?.Minutes == opt.Minutes ? "var(--pos-orange-primary)" : "var(--pos-border-subtle)");">
|
||||
<div style="font-size:16px;font-weight:700;">@opt.Label</div>
|
||||
<div style="font-size:13px;margin-top:4px;opacity:0.8;">+@FormatPrice(opt.Cost)</div>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@* EN: Custom input / VI: Nhập tùy chỉnh *@
|
||||
<div style="background:var(--pos-bg-elevated);border-radius:var(--pos-radius);padding:16px;margin-bottom:16px;">
|
||||
<div style="font-size:13px;font-weight:600;margin-bottom:8px;">Tùy chỉnh (phút)</div>
|
||||
<div style="display:flex;align-items:center;gap:12px;">
|
||||
<button style="width:36px;height:36px;border-radius:8px;border:1px solid var(--pos-border-default);
|
||||
background:transparent;color:var(--pos-text-primary);cursor:pointer;font-size:16px;"
|
||||
@onclick="() => AdjustCustom(-15)">−</button>
|
||||
<span style="font-size:20px;font-weight:700;min-width:50px;text-align:center;">@_customMinutes</span>
|
||||
<button style="width:36px;height:36px;border-radius:8px;border:1px solid var(--pos-border-default);
|
||||
background:transparent;color:var(--pos-text-primary);cursor:pointer;font-size:16px;"
|
||||
@onclick="() => AdjustCustom(15)">+</button>
|
||||
<span style="font-size:13px;color:var(--pos-text-tertiary);">phút</span>
|
||||
<button style="padding:8px 14px;border-radius:8px;background:var(--pos-bg-interactive);
|
||||
border:none;color:var(--pos-text-primary);cursor:pointer;font-size:12px;font-weight:600;"
|
||||
@onclick="ApplyCustom">
|
||||
Áp dụng
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@* ═══ PREVIEW / XEM TRƯỚC ═══ *@
|
||||
@if (_selectedOption is not null)
|
||||
{
|
||||
<div style="background:var(--pos-bg-elevated);border-radius:var(--pos-radius);padding:16px;margin-bottom:16px;">
|
||||
<div style="font-size:13px;font-weight:600;margin-bottom:10px;">Xem trước sau gia hạn</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:13px;margin-bottom:6px;">
|
||||
<span style="color:var(--pos-text-secondary);">Giờ kết thúc mới</span>
|
||||
<span style="font-weight:600;">@_newEndTime</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:13px;margin-bottom:6px;">
|
||||
<span style="color:var(--pos-text-secondary);">Thời gian thêm</span>
|
||||
<span style="font-weight:600;">+@_selectedOption.Label</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:13px;margin-bottom:6px;">
|
||||
<span style="color:var(--pos-text-secondary);">Phí gia hạn</span>
|
||||
<span style="font-weight:600;">+@FormatPrice(_selectedOption.Cost)</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:15px;font-weight:700;
|
||||
padding-top:10px;border-top:1px solid var(--pos-border-subtle);">
|
||||
<span>Tổng tiền phòng mới</span>
|
||||
<span style="color:var(--pos-orange-primary);">@FormatPrice(450_000 + _selectedOption.Cost)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@* EN: Peak warning if applicable / VI: Cảnh báo giờ cao điểm nếu có *@
|
||||
@if (_showPeakWarning)
|
||||
{
|
||||
<div style="background:rgba(245,158,11,.12);border-radius:var(--pos-radius);padding:14px 16px;
|
||||
margin-bottom:16px;display:flex;align-items:center;gap:10px;
|
||||
border:1px solid rgba(245,158,11,.3);">
|
||||
<i data-lucide="alert-triangle" style="width:20px;height:20px;color:var(--pos-warning);flex-shrink:0;"></i>
|
||||
<div>
|
||||
<div style="font-size:13px;font-weight:600;color:var(--pos-warning);">Cảnh báo giờ cao điểm</div>
|
||||
<div style="font-size:12px;color:var(--pos-text-tertiary);margin-top:2px;">
|
||||
Gia hạn vào khung giờ cao điểm (sau 22:00). Giá có thể tăng.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
@* ═══ ACTION BUTTONS / NÚT HÀNH ĐỘNG ═══ *@
|
||||
<div style="display:flex;gap:12px;padding:12px 16px;border-top:1px solid var(--pos-border-subtle);flex-shrink:0;">
|
||||
<button style="flex:1;padding:14px;border-radius:var(--pos-radius);background:var(--pos-bg-interactive);
|
||||
border:none;color:var(--pos-text-primary);cursor:pointer;font-size:14px;font-weight:600;"
|
||||
@onclick="@(() => NavigateTo("karaoke/room-session"))">
|
||||
Hủy
|
||||
</button>
|
||||
<button style="flex:1;padding:14px;border-radius:var(--pos-radius);background:var(--pos-orange-primary);
|
||||
border:none;color:#FFF;cursor:pointer;font-size:14px;font-weight:600;
|
||||
opacity:@(_selectedOption is null ? "0.4" : "1");"
|
||||
disabled="@(_selectedOption is null)"
|
||||
@onclick="@(() => NavigateTo("karaoke/room-session"))">
|
||||
<i data-lucide="check" style="width:16px;height:16px;display:inline;"></i> Xác nhận gia hạn
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
// EN: Extension options / VI: Tùy chọn gia hạn
|
||||
private readonly List<ExtendOption> _extendOptions = new()
|
||||
{
|
||||
new(30, "+30 phút", 100_000),
|
||||
new(60, "+1 giờ", 200_000),
|
||||
new(90, "+1.5 giờ", 300_000),
|
||||
new(120, "+2 giờ", 400_000),
|
||||
};
|
||||
|
||||
private ExtendOption? _selectedOption;
|
||||
private int _customMinutes = 45;
|
||||
private string _newEndTime = "22:00";
|
||||
private bool _showPeakWarning;
|
||||
|
||||
private void SelectExtension(ExtendOption opt)
|
||||
{
|
||||
_selectedOption = opt;
|
||||
UpdatePreview(opt.Minutes);
|
||||
}
|
||||
|
||||
private void AdjustCustom(int delta)
|
||||
{
|
||||
_customMinutes = Math.Max(15, _customMinutes + delta);
|
||||
}
|
||||
|
||||
private void ApplyCustom()
|
||||
{
|
||||
var cost = (decimal)_customMinutes / 60 * 200_000;
|
||||
_selectedOption = new(_customMinutes, $"+{_customMinutes} phút", Math.Round(cost, -3));
|
||||
UpdatePreview(_customMinutes);
|
||||
}
|
||||
|
||||
private void UpdatePreview(int minutes)
|
||||
{
|
||||
var baseEnd = new TimeOnly(22, 0);
|
||||
var newEnd = baseEnd.AddMinutes(minutes);
|
||||
_newEndTime = newEnd.ToString("HH:mm");
|
||||
_showPeakWarning = newEnd.Hour >= 22 || newEnd.Hour < 2;
|
||||
}
|
||||
|
||||
private record ExtendOption(int Minutes, string Label, decimal Cost);
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
@*
|
||||
EN: Karaoke Room Reset — Cleanup checklist after session ends, progress tracking, staff assignment.
|
||||
VI: Reset phòng Karaoke — Danh sách dọn dẹp sau phiên, theo dõi tiến độ, phân công nhân viên.
|
||||
*@
|
||||
@page "/pos/karaoke/room-reset"
|
||||
@layout PosLayout
|
||||
@inherits PosBase
|
||||
|
||||
<div style="flex:1;display:flex;flex-direction:column;overflow:hidden;">
|
||||
@* ═══ HEADER / TIÊU ĐỀ ═══ *@
|
||||
<div style="display:flex;align-items:center;gap:12px;padding:12px 16px;border-bottom:1px solid var(--pos-border-subtle);flex-shrink:0;">
|
||||
<button style="background:var(--pos-bg-interactive);border:none;color:var(--pos-text-primary);
|
||||
padding:8px 12px;border-radius:8px;cursor:pointer;font-size:13px;"
|
||||
@onclick="@(() => NavigateTo("karaoke"))">
|
||||
<i data-lucide="arrow-left" style="width:16px;height:16px;display:inline;"></i>
|
||||
</button>
|
||||
<span style="font-size:16px;font-weight:700;">Reset phòng</span>
|
||||
<span style="font-size:12px;padding:3px 10px;border-radius:6px;font-weight:600;
|
||||
background:@(AllChecked ? "rgba(34,197,94,.15)" : "rgba(245,158,11,.15)");
|
||||
color:@(AllChecked ? "#22C55E" : "var(--pos-warning)");">
|
||||
@(AllChecked ? "Sẵn sàng" : "Đang dọn")
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div style="flex:1;overflow-y:auto;padding:16px;">
|
||||
@* ═══ ROOM INFO / THÔNG TIN PHÒNG ═══ *@
|
||||
<div style="background:var(--pos-bg-elevated);border-radius:var(--pos-radius);padding:20px;margin-bottom:16px;">
|
||||
<div style="display:flex;justify-content:space-between;align-items:start;">
|
||||
<div>
|
||||
<div style="font-size:18px;font-weight:700;">Phòng VIP 2</div>
|
||||
<div style="font-size:12px;color:var(--pos-text-tertiary);margin-top:2px;">Deluxe • 20 người • Tầng 3</div>
|
||||
</div>
|
||||
<div style="text-align:right;">
|
||||
<div style="font-size:12px;color:var(--pos-text-tertiary);">Phiên trước kết thúc</div>
|
||||
<div style="font-size:16px;font-weight:700;">22:15</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@* ═══ STAFF & TIME / NHÂN VIÊN & THỜI GIAN ═══ *@
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:16px;">
|
||||
<div style="background:var(--pos-bg-elevated);border-radius:var(--pos-radius);padding:14px;
|
||||
display:flex;align-items:center;gap:10px;">
|
||||
<div style="width:36px;height:36px;border-radius:10px;background:rgba(59,130,246,.15);
|
||||
display:flex;align-items:center;justify-content:center;">
|
||||
<i data-lucide="user" style="width:18px;height:18px;color:#3B82F6;"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:11px;color:var(--pos-text-tertiary);">Nhân viên</div>
|
||||
<div style="font-size:13px;font-weight:600;">Trần Thị Hoa</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="background:var(--pos-bg-elevated);border-radius:var(--pos-radius);padding:14px;
|
||||
display:flex;align-items:center;gap:10px;">
|
||||
<div style="width:36px;height:36px;border-radius:10px;background:rgba(255,92,0,.15);
|
||||
display:flex;align-items:center;justify-content:center;">
|
||||
<i data-lucide="clock" style="width:18px;height:18px;color:var(--pos-orange-primary);"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:11px;color:var(--pos-text-tertiary);">Bắt đầu dọn</div>
|
||||
<div style="font-size:13px;font-weight:600;">22:18 • 12 phút</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@* ═══ PROGRESS BAR / THANH TIẾN ĐỘ ═══ *@
|
||||
<div style="background:var(--pos-bg-elevated);border-radius:var(--pos-radius);padding:16px;margin-bottom:16px;">
|
||||
<div style="display:flex;justify-content:space-between;font-size:13px;margin-bottom:8px;">
|
||||
<span style="font-weight:600;">Tiến độ</span>
|
||||
<span style="color:var(--pos-text-tertiary);">@CompletedCount/@_checkItems.Count hoàn thành</span>
|
||||
</div>
|
||||
<div style="height:8px;background:var(--pos-bg-interactive);border-radius:4px;overflow:hidden;">
|
||||
<div style="height:100%;width:@(ProgressPercent)%;border-radius:4px;transition:width .3s ease;
|
||||
background:@(AllChecked ? "var(--pos-success)" : "var(--pos-orange-primary)");"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@* ═══ CHECKLIST / DANH SÁCH KIỂM TRA ═══ *@
|
||||
<div style="display:flex;flex-direction:column;gap:8px;">
|
||||
@foreach (var item in _checkItems)
|
||||
{
|
||||
<div @onclick="() => item.Checked = !item.Checked"
|
||||
style="display:flex;align-items:center;gap:14px;padding:14px 16px;cursor:pointer;
|
||||
background:var(--pos-bg-elevated);border-radius:var(--pos-radius);
|
||||
border:1px solid @(item.Checked ? "rgba(34,197,94,.3)" : "var(--pos-border-subtle)");
|
||||
opacity:@(item.Checked ? "0.7" : "1");transition:all .2s ease;">
|
||||
<div style="width:28px;height:28px;border-radius:8px;display:flex;align-items:center;justify-content:center;
|
||||
flex-shrink:0;border:2px solid @(item.Checked ? "#22C55E" : "var(--pos-border-default)");
|
||||
background:@(item.Checked ? "rgba(34,197,94,.15)" : "transparent");">
|
||||
@if (item.Checked)
|
||||
{
|
||||
<i data-lucide="check" style="width:16px;height:16px;color:#22C55E;"></i>
|
||||
}
|
||||
</div>
|
||||
<div style="flex:1;">
|
||||
<div style="font-size:14px;font-weight:600;text-decoration:@(item.Checked ? "line-through" : "none");
|
||||
color:@(item.Checked ? "var(--pos-text-tertiary)" : "var(--pos-text-primary)");">
|
||||
@item.Label
|
||||
</div>
|
||||
<div style="font-size:12px;color:var(--pos-text-tertiary);margin-top:2px;">@item.Description</div>
|
||||
</div>
|
||||
<i data-lucide="@item.Icon" style="width:18px;height:18px;color:var(--pos-text-tertiary);flex-shrink:0;"></i>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@* ═══ COMPLETE BUTTON / NÚT HOÀN TẤT ═══ *@
|
||||
<div style="padding:12px 16px;border-top:1px solid var(--pos-border-subtle);flex-shrink:0;">
|
||||
<button style="width:100%;padding:16px;border-radius:var(--pos-radius);
|
||||
background:@(AllChecked ? "var(--pos-success)" : "var(--pos-bg-interactive)");
|
||||
border:none;color:@(AllChecked ? "#FFF" : "var(--pos-text-tertiary)");
|
||||
cursor:@(AllChecked ? "pointer" : "not-allowed");font-size:15px;font-weight:700;"
|
||||
disabled="@(!AllChecked)"
|
||||
@onclick="@(() => NavigateTo("karaoke"))">
|
||||
<i data-lucide="@(AllChecked ? "check-circle" : "loader")" style="width:18px;height:18px;display:inline;"></i>
|
||||
Hoàn tất reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
// EN: Checklist items / VI: Các mục kiểm tra
|
||||
private readonly List<CheckItem> _checkItems = new()
|
||||
{
|
||||
new("Dọn bàn ghế", "Clean tables/chairs", "armchair", false),
|
||||
new("Vệ sinh micro", "Clean microphones", "mic", false),
|
||||
new("Kiểm tra remote", "Check remote controls", "tv", false),
|
||||
new("Bổ sung nước uống", "Restock beverages", "cup-soda", false),
|
||||
new("Kiểm tra ánh sáng", "Check lighting", "lightbulb", false),
|
||||
new("Hệ thống âm thanh", "Sound system check", "volume-2", false),
|
||||
new("Kiểm tra thiết bị", "Equipment check", "monitor-speaker", false),
|
||||
new("Vệ sinh toilet", "Clean restroom", "bath", false),
|
||||
};
|
||||
|
||||
private int CompletedCount => _checkItems.Count(i => i.Checked);
|
||||
private bool AllChecked => _checkItems.All(i => i.Checked);
|
||||
private int ProgressPercent => _checkItems.Count > 0 ? CompletedCount * 100 / _checkItems.Count : 0;
|
||||
|
||||
private class CheckItem(string label, string description, string icon, bool isChecked)
|
||||
{
|
||||
public string Label { get; set; } = label;
|
||||
public string Description { get; set; } = description;
|
||||
public string Icon { get; set; } = icon;
|
||||
public bool Checked { get; set; } = isChecked;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user