fix: schedule pages — real API data, role display, time formatting
- Rewrite StaffSchedule.razor from hardcoded stub to real API integration (profile → shop schedules → filter by staffId) - Fix admin ShopSchedule role column: use staff role from merchant data instead of showing "—" - Add FormatTime() helper to strip seconds from time display (08:00:00 → 08:00) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -82,10 +82,10 @@
|
||||
var schedStaffName = schedStaff != null && (!string.IsNullOrWhiteSpace(schedStaff.LastName) || !string.IsNullOrWhiteSpace(schedStaff.FirstName)) ? $"{schedStaff.LastName} {schedStaff.FirstName}".Trim() : (s.EmployeeCode ?? s.StaffId.ToString()[..8]);
|
||||
<tr style="border-top:1px solid var(--admin-border-subtle);">
|
||||
<td style="padding:12px 16px;font-weight:600;">@schedStaffName</td>
|
||||
<td style="padding:12px 16px;font-size:13px;color:var(--admin-text-tertiary);">@(s.Role ?? "—")</td>
|
||||
<td style="padding:12px 16px;font-size:13px;color:var(--admin-text-tertiary);">@(schedStaff?.Role ?? s.Role ?? "—")</td>
|
||||
<td style="padding:12px 16px;text-align:center;font-weight:600;color:var(--admin-orange-primary);">@ShopHelpers.DayLabel(s.DayOfWeek)</td>
|
||||
<td style="padding:12px 16px;text-align:center;">@s.StartTime</td>
|
||||
<td style="padding:12px 16px;text-align:center;">@s.EndTime</td>
|
||||
<td style="padding:12px 16px;text-align:center;">@FormatTime(s.StartTime)</td>
|
||||
<td style="padding:12px 16px;text-align:center;">@FormatTime(s.EndTime)</td>
|
||||
<td style="padding:12px 16px;text-align:center;"><button @onclick='() => DeleteScheduleItem(s.Id)' style="background:rgba(239,68,68,0.1);border:none;border-radius:6px;width:28px;height:28px;display:flex;align-items:center;justify-content:center;cursor:pointer;"><i data-lucide="trash-2" style="color:#EF4444;width:14px;height:14px;"></i></button></td>
|
||||
</tr>
|
||||
}
|
||||
@@ -278,4 +278,15 @@ else if (SubSection == "shifts")
|
||||
}
|
||||
catch (Exception ex) { Console.Error.WriteLine($"Không thể xóa lịch làm việc: {ex.Message}"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Format time string — remove seconds if present (08:00:00 → 08:00).
|
||||
/// VI: Định dạng giờ — bỏ giây nếu có (08:00:00 → 08:00).
|
||||
/// </summary>
|
||||
private static string FormatTime(string? time)
|
||||
{
|
||||
if (string.IsNullOrEmpty(time)) return "—";
|
||||
var parts = time.Split(':');
|
||||
return parts.Length >= 2 ? $"{parts[0]}:{parts[1]}" : time;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
@page "/staff/schedule"
|
||||
@layout StaffLayout
|
||||
@inject WebClientTpos.Client.Services.PosDataService DataService
|
||||
@using WebClientTpos.Client.Services
|
||||
@inject PosDataService DataService
|
||||
@inject AuthService AuthSvc
|
||||
@inject AuthStateService AuthState
|
||||
@inject IJSRuntime JS
|
||||
|
||||
@*
|
||||
EN: Staff schedule page — view weekly work schedule.
|
||||
VI: Trang lịch làm việc — xem lịch làm việc hàng tuần.
|
||||
EN: Staff schedule page — view weekly work schedule from real API data.
|
||||
VI: Trang lịch làm việc — xem lịch làm việc hàng tuần từ dữ liệu API thật.
|
||||
*@
|
||||
|
||||
<PageTitle>Lịch làm việc</PageTitle>
|
||||
@@ -16,60 +19,124 @@
|
||||
<p class="staff-page-subtitle">Lịch trình làm việc của bạn</p>
|
||||
</div>
|
||||
|
||||
@* ═══ WEEK VIEW ═══ *@
|
||||
<div style="display:grid;grid-template-columns:repeat(7,1fr);gap:8px;margin-bottom:24px;">
|
||||
@for (int i = 0; i < 7; i++)
|
||||
{
|
||||
var day = _weekStart.AddDays(i);
|
||||
var isToday = day.Date == DateTime.Now.Date;
|
||||
var isWeekend = day.DayOfWeek == DayOfWeek.Sunday;
|
||||
<div style="background:var(--admin-bg-elevated);border:1px solid @(isToday ? "#22C55E" : "var(--admin-border-default)");border-radius:var(--admin-radius-lg);padding:16px;text-align:center;">
|
||||
<div style="font-size:11px;color:var(--admin-text-tertiary);margin-bottom:4px;">@GetDayName(day)</div>
|
||||
<div style="font-size:20px;font-weight:700;color:@(isToday ? "#22C55E" : "var(--admin-text-primary)");margin-bottom:8px;">@day.Day</div>
|
||||
@if (isWeekend)
|
||||
{
|
||||
<span class="staff-status staff-status--neutral">Nghỉ</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div style="font-size:12px;color:var(--admin-text-secondary);">08:00 - 17:00</div>
|
||||
}
|
||||
@if (_loading)
|
||||
{
|
||||
<div style="text-align:center;padding:60px 20px;">
|
||||
<div class="staff-spinner"></div>
|
||||
<p style="margin-top:12px;color:var(--admin-text-tertiary);">Đang tải lịch...</p>
|
||||
</div>
|
||||
}
|
||||
else if (!_mySchedules.Any())
|
||||
{
|
||||
<div style="text-align:center;padding:60px 20px;">
|
||||
<div style="width:80px;height:80px;border-radius:24px;background:rgba(139,92,246,0.1);display:flex;align-items:center;justify-content:center;margin:0 auto 20px;">
|
||||
<i data-lucide="calendar-clock" style="width:36px;height:36px;color:#8B5CF6;"></i>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<h2 style="font-size:20px;font-weight:700;margin:0 0 8px;color:var(--admin-text-primary);">Chưa có lịch làm việc</h2>
|
||||
<p style="font-size:14px;color:var(--admin-text-tertiary);margin:0;">Quản lý chưa phân lịch cho bạn</p>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@* ═══ WEEK VIEW ═══ *@
|
||||
<div style="display:grid;grid-template-columns:repeat(7,1fr);gap:8px;margin-bottom:24px;">
|
||||
@for (int i = 0; i < 7; i++)
|
||||
{
|
||||
var day = _weekStart.AddDays(i);
|
||||
var isToday = day.Date == DateTime.Now.Date;
|
||||
var dow = (int)day.DayOfWeek;
|
||||
var daySchedules = _mySchedules.Where(s => s.DayOfWeek == dow).OrderBy(s => s.StartTime).ToList();
|
||||
var hasSchedule = daySchedules.Any();
|
||||
<div style="background:var(--admin-bg-elevated);border:1px solid @(isToday ? "#22C55E" : "var(--admin-border-default)");border-radius:var(--admin-radius-lg);padding:16px;text-align:center;">
|
||||
<div style="font-size:11px;color:var(--admin-text-tertiary);margin-bottom:4px;">@GetDayName(day)</div>
|
||||
<div style="font-size:20px;font-weight:700;color:@(isToday ? "#22C55E" : "var(--admin-text-primary)");margin-bottom:8px;">@day.Day</div>
|
||||
@if (hasSchedule)
|
||||
{
|
||||
@foreach (var sched in daySchedules)
|
||||
{
|
||||
<div style="font-size:12px;color:var(--admin-text-secondary);margin-bottom:2px;">@FormatTime(sched.StartTime) - @FormatTime(sched.EndTime)</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="staff-status staff-status--neutral">Nghỉ</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@* ═══ UPCOMING SHIFTS ═══ *@
|
||||
<div class="staff-table-card">
|
||||
<div class="staff-table-card__header">
|
||||
<span class="staff-table-card__title">Thông tin ca làm việc</span>
|
||||
</div>
|
||||
<div style="padding:20px;">
|
||||
<div style="display:flex;flex-direction:column;gap:12px;">
|
||||
<div style="display:flex;align-items:center;gap:12px;padding:12px 16px;background:var(--admin-bg-interactive);border-radius:var(--admin-radius-md);">
|
||||
<i data-lucide="clock" style="width:20px;height:20px;color:#22C55E;"></i>
|
||||
<div>
|
||||
<div style="font-size:14px;font-weight:600;color:var(--admin-text-primary);">Ca sáng: 08:00 - 12:00</div>
|
||||
<div style="font-size:12px;color:var(--admin-text-tertiary);">Thu 2 - Thu 6</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:12px;padding:12px 16px;background:var(--admin-bg-interactive);border-radius:var(--admin-radius-md);">
|
||||
<i data-lucide="clock" style="width:20px;height:20px;color:#3B82F6;"></i>
|
||||
<div>
|
||||
<div style="font-size:14px;font-weight:600;color:var(--admin-text-primary);">Ca chiều: 13:00 - 17:00</div>
|
||||
<div style="font-size:12px;color:var(--admin-text-tertiary);">Thu 2 - Thu 6</div>
|
||||
</div>
|
||||
@* ═══ SHIFT DETAILS ═══ *@
|
||||
<div class="staff-table-card">
|
||||
<div class="staff-table-card__header">
|
||||
<span class="staff-table-card__title">Thông tin ca làm việc</span>
|
||||
</div>
|
||||
<div style="padding:20px;">
|
||||
<div style="display:flex;flex-direction:column;gap:12px;">
|
||||
@foreach (var group in _mySchedules.GroupBy(s => new { s.StartTime, s.EndTime }).OrderBy(g => g.Key.StartTime))
|
||||
{
|
||||
var isMorning = group.Key.StartTime?.CompareTo("12:00") < 0;
|
||||
var isEvening = group.Key.StartTime?.CompareTo("18:00") >= 0;
|
||||
var color = isEvening ? "#F59E0B" : isMorning ? "#22C55E" : "#3B82F6";
|
||||
var label = isEvening ? "Ca tối" : isMorning ? "Ca sáng" : "Ca chiều";
|
||||
var days = string.Join(", ", group.OrderBy(s => s.DayOfWeek == 0 ? 7 : s.DayOfWeek).Select(s => DayLabel(s.DayOfWeek)));
|
||||
<div style="display:flex;align-items:center;gap:12px;padding:12px 16px;background:var(--admin-bg-interactive);border-radius:var(--admin-radius-md);">
|
||||
<i data-lucide="clock" style="width:20px;height:20px;color:@color;"></i>
|
||||
<div>
|
||||
<div style="font-size:14px;font-weight:600;color:var(--admin-text-primary);">@label: @FormatTime(group.Key.StartTime) - @FormatTime(group.Key.EndTime)</div>
|
||||
<div style="font-size:12px;color:var(--admin-text-tertiary);">@days</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private bool _loading = true;
|
||||
private List<PosDataService.ScheduleInfo> _mySchedules = new();
|
||||
private DateTime _weekStart = DateTime.Now.Date.AddDays(-(int)DateTime.Now.DayOfWeek + (int)DayOfWeek.Monday);
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
try { await JS.InvokeVoidAsync("lucide.createIcons"); } catch { }
|
||||
|
||||
if (firstRender)
|
||||
{
|
||||
// EN: Restore staff session and load schedule data
|
||||
// VI: Khôi phục session nhân viên và tải dữ liệu lịch
|
||||
if (!AuthState.IsAuthenticated)
|
||||
{
|
||||
try { await AuthSvc.TryRestoreSessionAsync("staff"); } catch { }
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var profile = await DataService.GetMyStaffProfileAsync();
|
||||
if (profile?.ShopId != null)
|
||||
{
|
||||
var allSchedules = await DataService.GetStaffSchedulesAsync(profile.ShopId.Value);
|
||||
_mySchedules = allSchedules.Where(s => s.StaffId == profile.StaffId).ToList();
|
||||
}
|
||||
}
|
||||
catch { /* API unavailable */ }
|
||||
|
||||
_loading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Format time string — remove seconds if present (08:00:00 → 08:00).
|
||||
/// VI: Định dạng giờ — bỏ giây nếu có (08:00:00 → 08:00).
|
||||
/// </summary>
|
||||
private static string FormatTime(string? time)
|
||||
{
|
||||
if (string.IsNullOrEmpty(time)) return "—";
|
||||
// Handle "08:00:00" -> "08:00"
|
||||
var parts = time.Split(':');
|
||||
return parts.Length >= 2 ? $"{parts[0]}:{parts[1]}" : time;
|
||||
}
|
||||
|
||||
private static string GetDayName(DateTime d) => d.DayOfWeek switch
|
||||
@@ -83,4 +150,16 @@
|
||||
DayOfWeek.Sunday => "CN",
|
||||
_ => ""
|
||||
};
|
||||
|
||||
private static string DayLabel(int dow) => dow switch
|
||||
{
|
||||
1 => "Thứ 2",
|
||||
2 => "Thứ 3",
|
||||
3 => "Thứ 4",
|
||||
4 => "Thứ 5",
|
||||
5 => "Thứ 6",
|
||||
6 => "Thứ 7",
|
||||
0 => "CN",
|
||||
_ => ""
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user