@using Indotalent.ConfigBackEnd.Extensions @using Indotalent.Features.Performance.Appraisal @using Indotalent.Features.Performance.Appraisal.Cqrs @using Microsoft.AspNetCore.Components.Web @using Microsoft.JSInterop @using MudBlazor @using Features.Root.Shared @using ClosedXML.Excel @using System.IO @inject AppraisalService AppraisalService @inject IDialogService DialogService @inject ISnackbar Snackbar @inject IJSRuntime JSRuntime
Salary & Benefit Appraisal Manage salary increments, bonuses, and compensation reviews based on performance.
/ Performance / Appraisal
Search
@if (_isExporting) { Processing... } else { Excel } @if (_isRefreshing) { Refreshing... } else { Refresh } @if (_selectedAppraisal != null) { View Detail Manage Appraisal } else { New Appraisal }
Employee Last Rating Current Salary Increment (%) New Salary Status
@GetInitials(context.EmployeeName!)
@context.EmployeeName @context.EmployeeCode
@context.LastRating @context.CurrentSalary.ToString("N0") +@context.IncrementPercentage% @context.NewSalary.ToString("N0") @context.Status
Rows per page: Showing @(GetFilteredData().Count() == 0 ? 0 : _skip + 1)-@Math.Min(_skip + _top, GetFilteredData().Count()) of @GetFilteredData().Count()
Prev @{ var totalPages = _totalPage == 0 ? 1 : _totalPage; var maxVisible = 5; var startPage = Math.Max(1, _currentPage - maxVisible / 2); var endPage = Math.Min(totalPages, startPage + maxVisible - 1); if (endPage - startPage < maxVisible - 1) { startPage = Math.Max(1, endPage - maxVisible + 1); } } @for (int i = startPage; i <= endPage; i++) { var pageNum = i; var isActive = pageNum == _currentPage; @pageNum } Next
@code { [Parameter] public EventCallback OnAdd { get; set; } [Parameter] public EventCallback OnEdit { get; set; } [Parameter] public EventCallback OnView { get; set; } private List _appraisals = new(); private GetAppraisalListResponse? _selectedAppraisal; private string _searchString = ""; private int _currentPage = 1; private int _top = 5; private int _skip => (_currentPage - 1) * _top; private int _totalPage => GetFilteredData().Count() == 0 ? 0 : (int)Math.Ceiling((double)GetFilteredData().Count() / _top); private bool _isRefreshing = false; private bool _isExporting = false; protected override async Task OnInitializedAsync() => await LoadData(); private async Task LoadData() { _isRefreshing = true; _selectedAppraisal = null; StateHasChanged(); try { await Task.Delay(800); var response = await AppraisalService.GetAppraisalListAsync(); if (response != null && response.IsSuccess) { _appraisals = response.Value ?? new(); } } finally { _isRefreshing = false; StateHasChanged(); } } private IEnumerable GetFilteredData() { if (string.IsNullOrWhiteSpace(_searchString)) return _appraisals; return _appraisals.Where(x => (x.EmployeeName?.Contains(_searchString, StringComparison.OrdinalIgnoreCase) ?? false) || (x.EmployeeCode?.Contains(_searchString, StringComparison.OrdinalIgnoreCase) ?? false) || (x.LastRating?.Contains(_searchString, StringComparison.OrdinalIgnoreCase) ?? false) || (x.Status?.Contains(_searchString, StringComparison.OrdinalIgnoreCase) ?? false) ); } private IEnumerable GetPagedData() => GetFilteredData().Skip(_skip).Take(_top); private async Task ExportToExcel() { _isExporting = true; StateHasChanged(); try { await Task.Delay(1000); using (var workbook = new XLWorkbook()) { var worksheet = workbook.Worksheets.Add("Appraisals"); var currentRow = 1; worksheet.Cell(currentRow, 1).Value = "Employee Code"; worksheet.Cell(currentRow, 2).Value = "Employee Name"; worksheet.Cell(currentRow, 3).Value = "Last Rating"; worksheet.Cell(currentRow, 4).Value = "Current Salary"; worksheet.Cell(currentRow, 5).Value = "Increment %"; worksheet.Cell(currentRow, 6).Value = "New Salary"; worksheet.Cell(currentRow, 7).Value = "Status"; var headerRange = worksheet.Range(1, 1, 1, 7); headerRange.Style.Font.Bold = true; headerRange.Style.Fill.BackgroundColor = XLColor.FromHtml("#0D47A1"); headerRange.Style.Font.FontColor = XLColor.White; foreach (var item in GetFilteredData()) { currentRow++; worksheet.Cell(currentRow, 1).Value = item.EmployeeCode; worksheet.Cell(currentRow, 2).Value = item.EmployeeName; worksheet.Cell(currentRow, 3).Value = item.LastRating; worksheet.Cell(currentRow, 4).Value = item.CurrentSalary; worksheet.Cell(currentRow, 5).Value = item.IncrementPercentage; worksheet.Cell(currentRow, 6).Value = item.NewSalary; worksheet.Cell(currentRow, 7).Value = item.Status; } worksheet.Columns().AdjustToContents(); using (var stream = new MemoryStream()) { workbook.SaveAs(stream); var content = Convert.ToBase64String(stream.ToArray()); await JSRuntime.InvokeVoidAsync("downloadFile", "Salary_Appraisal_Report.xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", content); Snackbar.Add("Excel exported successfully", Severity.Success); } } } catch (Exception ex) { Snackbar.Add($"Export failed: {ex.Message}", Severity.Error); } finally { _isExporting = false; StateHasChanged(); } } private void OnSearchClick() { _currentPage = 1; _selectedAppraisal = null; StateHasChanged(); } private void HandleSearchKeyDown(KeyboardEventArgs e) { if (e.Key == "Enter") OnSearchClick(); } private void OnPageChanged(int page) { if (page >= 1 && page <= _totalPage) { _currentPage = page; _selectedAppraisal = null; StateHasChanged(); } } private void OnPageSizeChanged(int size) { _top = size; _currentPage = 1; _selectedAppraisal = null; StateHasChanged(); } private string GetInitials(string name) { if (string.IsNullOrWhiteSpace(name)) return "??"; var parts = name.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries); if (parts.Length > 1) return $"{parts[0][0]}{parts[1][0]}".ToUpper(); return name.Length >= 2 ? name.Substring(0, 2).ToUpper() : name.ToUpper(); } private Color GetRandomColor(string name) { int hash = name.GetHashCode(); var colors = new[] { Color.Primary, Color.Secondary, Color.Tertiary, Color.Info, Color.Success, Color.Warning, Color.Dark }; return colors[Math.Abs(hash) % colors.Length]; } private Color GetStatusColor(string status) => status switch { "Processed" => Color.Success, "Approved" => Color.Info, "Pending" => Color.Warning, _ => Color.Default }; private async Task InvokeEdit() { if (_selectedAppraisal != null) { var res = await AppraisalService.GetAppraisalByIdAsync(_selectedAppraisal.Id!); if (res?.Value != null) await OnEdit.InvokeAsync(MapToUpdate(res.Value)); } } private async Task InvokeView() { if (_selectedAppraisal != null) { var res = await AppraisalService.GetAppraisalByIdAsync(_selectedAppraisal.Id!); if (res?.Value != null) await OnView.InvokeAsync(MapToUpdate(res.Value)); } } private UpdateAppraisalRequest MapToUpdate(GetAppraisalByIdResponse d) => new UpdateAppraisalRequest { Id = d.Id, EmployeeId = d.EmployeeId!, EmployeeCode = d.EmployeeCode, EmployeeName = d.EmployeeName, LastRating = d.LastRating!, CurrentSalary = d.CurrentSalary, IncrementPercentage = d.IncrementPercentage, NewSalary = d.NewSalary, AppraisalType = d.AppraisalType!, EffectiveDate = d.EffectiveDate, AppraisalNote = d.AppraisalNote!, Status = d.Status!, CreatedAt = d.CreatedAt, CreatedBy = d.CreatedBy, UpdatedAt = d.UpdatedAt, UpdatedBy = d.UpdatedBy }; private async Task OnDelete() { if (_selectedAppraisal == null) return; var dialog = await DialogService.ShowAsync("", new DialogParameters { { x => x.ContentText, $"Appraisal for {_selectedAppraisal.EmployeeName}" } }); if (!(await dialog.Result).Canceled) { if (await AppraisalService.DeleteAppraisalByIdAsync(_selectedAppraisal.Id!)) { await LoadData(); Snackbar.Add("Appraisal record removed", Severity.Success); } } } }