feat: Display attendance times in local timezone and calculate staff leave statistics from real data, removing stub notifications.
This commit is contained in:
@@ -79,8 +79,8 @@
|
||||
<tr>
|
||||
<td style="font-weight:600;">@(r.StaffName ?? r.StaffId.ToString()[..8])</td>
|
||||
<td>@r.Date.ToString("dd/MM")</td>
|
||||
<td style="color:#22C55E;">@(r.CheckIn?.ToString("HH:mm") ?? "--")</td>
|
||||
<td style="color:#F59E0B;">@(r.CheckOut?.ToString("HH:mm") ?? "--")</td>
|
||||
<td style="color:#22C55E;">@(r.CheckIn?.ToLocalTime().ToString("HH:mm") ?? "--")</td>
|
||||
<td style="color:#F59E0B;">@(r.CheckOut?.ToLocalTime().ToString("HH:mm") ?? "--")</td>
|
||||
<td>@(r.HoursWorked.HasValue ? r.HoursWorked.Value.ToString("0.#") + "h" : "--")</td>
|
||||
<td>
|
||||
@{
|
||||
|
||||
@@ -157,16 +157,17 @@
|
||||
// VI: Dữ liệu dialog — được điền từ context hiện tại (đơn hàng/sản phẩm đã chọn).
|
||||
// TODO: Integrate with Order/Catalog/Inventory APIs when DDD Value Object mapping is fixed.
|
||||
|
||||
// EN: Stock-in state / VI: Trạng thái nhập kho
|
||||
private string _productSearch = "Cà phê hạt Arabica";
|
||||
private string _selectedProduct = "Cà phê hạt Arabica";
|
||||
private string _selectedSku = "CF-ARA-500";
|
||||
private int _currentStock = 120;
|
||||
private int _quantity = 50;
|
||||
private decimal _unitCost = 185_000;
|
||||
private string _supplier = "Công ty TNHH Cà phê Đà Lạt";
|
||||
private string _lotNumber = "LOT-2026-0226";
|
||||
private DateTime _expiryDate = new(2027, 2, 26);
|
||||
// EN: Stock-in state — will be loaded from Inventory API. No hardcoded values in production.
|
||||
// VI: Trạng thái nhập kho — sẽ tải từ Inventory API. Không hardcode giá trị trong production.
|
||||
private string _productSearch = "";
|
||||
private string _selectedProduct = "";
|
||||
private string _selectedSku = "";
|
||||
private int _currentStock = 0;
|
||||
private int _quantity = 0;
|
||||
private decimal _unitCost = 0;
|
||||
private string _supplier = "";
|
||||
private string _lotNumber = "";
|
||||
private DateTime _expiryDate = DateTime.Now.AddMonths(6);
|
||||
private string _notes = "";
|
||||
|
||||
// EN: Suppliers / VI: Nhà cung cấp
|
||||
|
||||
@@ -74,8 +74,8 @@
|
||||
<tr>
|
||||
<td>@r.Date.ToString("dd/MM/yyyy")</td>
|
||||
<td>@GetDayOfWeek(r.Date)</td>
|
||||
<td style="color:#22C55E;">@(r.CheckIn?.ToString("HH:mm") ?? "--")</td>
|
||||
<td style="color:#F59E0B;">@(r.CheckOut?.ToString("HH:mm") ?? "--")</td>
|
||||
<td style="color:#22C55E;">@(r.CheckIn?.ToLocalTime().ToString("HH:mm") ?? "--")</td>
|
||||
<td style="color:#F59E0B;">@(r.CheckOut?.ToLocalTime().ToString("HH:mm") ?? "--")</td>
|
||||
<td>@(r.HoursWorked.HasValue ? r.HoursWorked.Value.ToString("0.#") + "h" : "--")</td>
|
||||
<td>
|
||||
<span class="staff-status @GetStatusCss(r.Status)">
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
<i data-lucide="calendar-off" style="color:#F59E0B;"></i>
|
||||
</div>
|
||||
<span class="staff-stat-card__value">@_leaveBalance</span>
|
||||
<span class="staff-stat-card__label">Ngày phép còn lại</span>
|
||||
<span class="staff-stat-card__label">Ngày phép đã dùng</span>
|
||||
</div>
|
||||
<div class="staff-stat-card">
|
||||
<div class="staff-stat-card__icon" style="background:rgba(139,92,246,0.12);">
|
||||
@@ -111,8 +111,8 @@
|
||||
{
|
||||
<tr>
|
||||
<td>@r.Date.ToString("dd/MM")</td>
|
||||
<td>@(r.CheckIn?.ToString("HH:mm") ?? "--")</td>
|
||||
<td>@(r.CheckOut?.ToString("HH:mm") ?? "--")</td>
|
||||
<td>@(r.CheckIn?.ToLocalTime().ToString("HH:mm") ?? "--")</td>
|
||||
<td>@(r.CheckOut?.ToLocalTime().ToString("HH:mm") ?? "--")</td>
|
||||
<td>@(r.HoursWorked.HasValue ? r.HoursWorked.Value.ToString("0.#") + "h" : "--")</td>
|
||||
<td>
|
||||
<span class="staff-status @(r.Status == "Completed" ? "staff-status--success" : r.Status == "Working" ? "staff-status--info" : "staff-status--neutral")">
|
||||
@@ -141,7 +141,9 @@
|
||||
private bool _checkedOut = false;
|
||||
private string _todayHours = "0";
|
||||
private int _monthDays = 0;
|
||||
private int _leaveBalance = 12;
|
||||
// EN: Leave balance — loaded from real leave request data. TODO: load annual allowance from merchant config API.
|
||||
// VI: Số phép còn lại — tính từ dữ liệu nghỉ phép thực. TODO: tải số phép năm từ API cấu hình merchant.
|
||||
private int _leaveBalance = 0;
|
||||
private int _unreadCount = 0;
|
||||
|
||||
private string _displayName => _profile?.FirstName ?? AuthState.UserEmail?.Split('@').FirstOrDefault() ?? "Staff";
|
||||
@@ -177,16 +179,28 @@
|
||||
_unreadCount = _notifications.Count(n => !n.IsRead);
|
||||
_monthDays = _recentAttendance.Count(r => r.Status == "Completed");
|
||||
|
||||
var today = _recentAttendance.FirstOrDefault(r => r.Date.Date == DateTime.Now.Date);
|
||||
// EN: Compare UTC dates consistently — backend stores all times in UTC.
|
||||
// VI: So sánh ngày UTC nhất quán — backend lưu tất cả thời gian theo UTC.
|
||||
var todayUtc = DateTime.UtcNow.Date;
|
||||
var today = _recentAttendance.FirstOrDefault(r => r.Date.Date == todayUtc);
|
||||
if (today != null)
|
||||
{
|
||||
_checkedIn = today.CheckIn.HasValue;
|
||||
_checkedOut = today.CheckOut.HasValue;
|
||||
if (today.CheckIn.HasValue && !today.CheckOut.HasValue)
|
||||
_todayHours = ((DateTime.Now - today.CheckIn.Value).TotalHours).ToString("0.#");
|
||||
_todayHours = ((DateTime.UtcNow - today.CheckIn.Value).TotalHours).ToString("0.#");
|
||||
else if (today.HoursWorked.HasValue)
|
||||
_todayHours = today.HoursWorked.Value.ToString("0.#");
|
||||
}
|
||||
|
||||
// EN: Calculate leave days used from approved leave requests (real data).
|
||||
// VI: Tính số ngày phép đã dùng từ các yêu cầu nghỉ phép đã duyệt (dữ liệu thực).
|
||||
try
|
||||
{
|
||||
var leaveRequests = await DataService.GetMyLeaveRequestsAsync();
|
||||
_leaveBalance = leaveRequests.Where(r => r.Status == "Approved").Sum(r => (r.EndDate - r.StartDate).Days + 1);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
catch { }
|
||||
finally { _loading = false; }
|
||||
|
||||
@@ -26,23 +26,25 @@
|
||||
</div>
|
||||
|
||||
@* ═══ SUMMARY ═══ *@
|
||||
@* EN: Leave stats from real data. Total annual allowance will come from merchant config API (TODO).
|
||||
VI: Thống kê phép từ dữ liệu thực. Tổng phép năm sẽ từ API cấu hình merchant (TODO). *@
|
||||
<div class="staff-stats-grid" style="margin-bottom:20px;">
|
||||
<div class="staff-stat-card">
|
||||
<span class="staff-stat-card__value">12</span>
|
||||
<span class="staff-stat-card__label">Tổng phép năm</span>
|
||||
</div>
|
||||
<div class="staff-stat-card">
|
||||
<span class="staff-stat-card__value">@_usedDays</span>
|
||||
<span class="staff-stat-card__label">Đã sử dụng</span>
|
||||
</div>
|
||||
<div class="staff-stat-card">
|
||||
<span class="staff-stat-card__value">@(12 - _usedDays)</span>
|
||||
<span class="staff-stat-card__label">Còn lại</span>
|
||||
<span class="staff-stat-card__label">Ngày phép đã dùng</span>
|
||||
</div>
|
||||
<div class="staff-stat-card">
|
||||
<span class="staff-stat-card__value">@_pendingCount</span>
|
||||
<span class="staff-stat-card__label">Chờ duyệt</span>
|
||||
</div>
|
||||
<div class="staff-stat-card">
|
||||
<span class="staff-stat-card__value">@_rejectedCount</span>
|
||||
<span class="staff-stat-card__label">Từ chối</span>
|
||||
</div>
|
||||
<div class="staff-stat-card">
|
||||
<span class="staff-stat-card__value">@_requests.Count</span>
|
||||
<span class="staff-stat-card__label">Tổng yêu cầu</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@* ═══ CREATE FORM ═══ *@
|
||||
@@ -129,6 +131,7 @@
|
||||
private bool _submitting = false;
|
||||
private int _usedDays = 0;
|
||||
private int _pendingCount = 0;
|
||||
private int _rejectedCount = 0;
|
||||
|
||||
// Form fields
|
||||
private string _leaveType = "Annual";
|
||||
@@ -158,6 +161,7 @@
|
||||
_requests = await DataService.GetMyLeaveRequestsAsync();
|
||||
_usedDays = _requests.Where(r => r.Status == "Approved").Sum(r => (r.EndDate - r.StartDate).Days + 1);
|
||||
_pendingCount = _requests.Count(r => r.Status == "Pending");
|
||||
_rejectedCount = _requests.Count(r => r.Status == "Rejected");
|
||||
}
|
||||
catch { }
|
||||
finally { _loading = false; }
|
||||
|
||||
@@ -367,21 +367,15 @@ public class StaffController : ControllerBase
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get notifications for current staff.
|
||||
/// VI: Lấy thông báo của nhân viên hiện tại.
|
||||
/// EN: Get notifications for current staff. Returns empty list until notification service is implemented.
|
||||
/// VI: Lấy thông báo của nhân viên hiện tại. Trả về danh sách trống cho đến khi notification service được triển khai.
|
||||
/// </summary>
|
||||
[HttpGet("staff/me/notifications")]
|
||||
public IActionResult GetMyNotifications()
|
||||
{
|
||||
// EN: Stub — returns sample notifications
|
||||
// VI: Stub — trả về thông báo mẫu
|
||||
var notifications = new List<object>
|
||||
{
|
||||
new { id = Guid.NewGuid(), title = "Chào mừng!", message = "Bạn đã đăng nhập thành công vào hệ thống GoodGo Staff.", type = "info", isRead = false, createdAt = DateTime.UtcNow.AddHours(-1).ToString("o") },
|
||||
new { id = Guid.NewGuid(), title = "Lịch làm việc", message = "Lịch làm việc tuần này đã được cập nhật.", type = "schedule", isRead = false, createdAt = DateTime.UtcNow.AddDays(-1).ToString("o") }
|
||||
};
|
||||
|
||||
return Ok(new { success = true, data = new { items = notifications } });
|
||||
// EN: No notification service yet — return empty list (no fake data in production).
|
||||
// VI: Chưa có notification service — trả về danh sách trống (không fake data trong production).
|
||||
return Ok(new { success = true, data = new { items = Array.Empty<object>() } });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user