Various CSS work
This commit is contained in:
parent
ef174af0e1
commit
15579acba8
52 changed files with 8001 additions and 944 deletions
|
|
@ -6,9 +6,9 @@
|
|||
}
|
||||
|
||||
<!-- Sticky Header (Stats + Tabs) -->
|
||||
<swp-cash-sticky-header>
|
||||
<swp-sticky-header>
|
||||
<!-- Context Stats (changes based on active tab) -->
|
||||
<swp-cash-header>
|
||||
<swp-header-content>
|
||||
<!-- Stats for Oversigt tab -->
|
||||
<swp-cash-stats data-for-tab="oversigt" class="active">
|
||||
<swp-cash-stat>
|
||||
|
|
@ -48,20 +48,20 @@
|
|||
<swp-cash-stat-label localize="cash.stats.openedRegister">Åbnede kassen 29. dec 09:05</swp-cash-stat-label>
|
||||
</swp-cash-stat>
|
||||
</swp-cash-stats>
|
||||
</swp-cash-header>
|
||||
</swp-header-content>
|
||||
|
||||
<!-- Tab Bar -->
|
||||
<swp-tab-bar>
|
||||
<swp-tab class="active" data-tab="oversigt">
|
||||
<i class="ph ph-list-checks"></i>
|
||||
<span localize="cash.tabs.overview">Oversigt</span>
|
||||
</swp-tab>
|
||||
<swp-tab data-tab="afstemning">
|
||||
<i class="ph ph-cash-register"></i>
|
||||
<span localize="cash.tabs.reconciliation">Kasseafstemning</span>
|
||||
</swp-tab>
|
||||
</swp-tab-bar>
|
||||
</swp-cash-sticky-header>
|
||||
<swp-tab class="active" data-tab="oversigt">
|
||||
<i class="ph ph-list-checks"></i>
|
||||
<span localize="cash.tabs.overview">Oversigt</span>
|
||||
</swp-tab>
|
||||
<swp-tab data-tab="afstemning">
|
||||
<i class="ph ph-cash-register"></i>
|
||||
<span localize="cash.tabs.reconciliation">Kasseafstemning</span>
|
||||
</swp-tab>
|
||||
</swp-tab-bar>
|
||||
</swp-sticky-header>
|
||||
|
||||
<!-- Tab Content: Oversigt -->
|
||||
<swp-tab-content data-tab="oversigt" class="active">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,227 @@
|
|||
namespace PlanTempus.Application.Features.Employees.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Shared catalog for employee detail data.
|
||||
/// Used by all EmployeeDetail* ViewComponents.
|
||||
/// </summary>
|
||||
public static class EmployeeDetailCatalog
|
||||
{
|
||||
private static readonly Dictionary<string, EmployeeDetailRecord> Employees = new()
|
||||
{
|
||||
["employee-1"] = new EmployeeDetailRecord
|
||||
{
|
||||
Key = "employee-1",
|
||||
Initials = "MJ",
|
||||
Name = "Maria Jensen",
|
||||
Email = "maria@salonbeauty.dk",
|
||||
Phone = "+45 12 34 56 78",
|
||||
Role = "owner",
|
||||
RoleKey = "employees.roles.owner",
|
||||
Status = "active",
|
||||
StatusKey = "employees.status.active",
|
||||
BookingsThisYear = "312",
|
||||
RevenueThisYear = "245.800 kr",
|
||||
Rating = "4.9",
|
||||
EmployedSince = "2018",
|
||||
Address = "Hovedgaden 12",
|
||||
PostalCity = "2100 København Ø",
|
||||
EmploymentDate = "1. januar 2018",
|
||||
Position = "Ejer",
|
||||
EmploymentType = "Fuldtid",
|
||||
HoursPerWeek = "37",
|
||||
BirthDate = "12. maj 1985",
|
||||
EmergencyContact = "Peter Jensen (ægtefælle)",
|
||||
EmergencyPhone = "+45 98 76 54 32",
|
||||
BankAccount = "1234-5678901234",
|
||||
TaxCard = "Hovedkort",
|
||||
HourlyRate = "250 kr",
|
||||
MonthlyFixedSalary = "45.000 kr",
|
||||
Tags = new() { new("Master Stylist", "master"), new("Farvecertificeret", "cert") }
|
||||
},
|
||||
["employee-2"] = new EmployeeDetailRecord
|
||||
{
|
||||
Key = "employee-2",
|
||||
Initials = "AS",
|
||||
Name = "Anna Sørensen",
|
||||
Email = "anna@salonbeauty.dk",
|
||||
Phone = "+45 23 45 67 89",
|
||||
Role = "admin",
|
||||
RoleKey = "employees.roles.admin",
|
||||
Status = "active",
|
||||
StatusKey = "employees.status.active",
|
||||
AvatarColor = "purple",
|
||||
BookingsThisYear = "248",
|
||||
RevenueThisYear = "186.450 kr",
|
||||
Rating = "4.9",
|
||||
EmployedSince = "2019",
|
||||
Address = "Vestergade 15, 3. tv",
|
||||
PostalCity = "8000 Aarhus C",
|
||||
EmploymentDate = "1. august 2019",
|
||||
Position = "Master Stylist",
|
||||
EmploymentType = "Fuldtid",
|
||||
HoursPerWeek = "37",
|
||||
BirthDate = "15. marts 1992",
|
||||
EmergencyContact = "Peter Sørensen (ægtefælle)",
|
||||
EmergencyPhone = "+45 87 65 43 21",
|
||||
BankAccount = "2345-6789012345",
|
||||
TaxCard = "Hovedkort",
|
||||
HourlyRate = "220 kr",
|
||||
MonthlyFixedSalary = "38.000 kr",
|
||||
Tags = new() { new("Master Stylist", "master"), new("Farvecertificeret", "cert"), new("Balayage", "cert") }
|
||||
},
|
||||
["employee-3"] = new EmployeeDetailRecord
|
||||
{
|
||||
Key = "employee-3",
|
||||
Initials = "LP",
|
||||
Name = "Louise Pedersen",
|
||||
Email = "louise@salonbeauty.dk",
|
||||
Phone = "+45 34 56 78 90",
|
||||
Role = "leader",
|
||||
RoleKey = "employees.roles.leader",
|
||||
Status = "active",
|
||||
StatusKey = "employees.status.active",
|
||||
AvatarColor = "blue",
|
||||
BookingsThisYear = "198",
|
||||
RevenueThisYear = "156.200 kr",
|
||||
Rating = "4.7",
|
||||
EmployedSince = "2020",
|
||||
Address = "Nørrebrogade 45",
|
||||
PostalCity = "2200 København N",
|
||||
EmploymentDate = "15. marts 2020",
|
||||
Position = "Senior Stylist",
|
||||
EmploymentType = "Fuldtid",
|
||||
HoursPerWeek = "37",
|
||||
BirthDate = "22. november 1988",
|
||||
EmergencyContact = "Hans Pedersen (far)",
|
||||
EmergencyPhone = "+45 76 54 32 10",
|
||||
BankAccount = "3456-7890123456",
|
||||
TaxCard = "Hovedkort",
|
||||
HourlyRate = "200 kr",
|
||||
MonthlyFixedSalary = "35.000 kr",
|
||||
Tags = new() { new("Senior Stylist", "senior"), new("Farvecertificeret", "cert") }
|
||||
},
|
||||
["employee-4"] = new EmployeeDetailRecord
|
||||
{
|
||||
Key = "employee-4",
|
||||
Initials = "KN",
|
||||
Name = "Katrine Nielsen",
|
||||
Email = "katrine@salonbeauty.dk",
|
||||
Phone = "+45 45 67 89 01",
|
||||
Role = "employee",
|
||||
RoleKey = "employees.roles.employee",
|
||||
Status = "active",
|
||||
StatusKey = "employees.status.active",
|
||||
AvatarColor = "amber",
|
||||
BookingsThisYear = "165",
|
||||
RevenueThisYear = "124.300 kr",
|
||||
Rating = "4.8",
|
||||
EmployedSince = "2021",
|
||||
Address = "Frederiksberggade 28",
|
||||
PostalCity = "1459 København K",
|
||||
EmploymentDate = "1. juni 2021",
|
||||
Position = "Stylist",
|
||||
EmploymentType = "Fuldtid",
|
||||
HoursPerWeek = "32",
|
||||
BirthDate = "8. august 1995",
|
||||
EmergencyContact = "Mette Nielsen (mor)",
|
||||
EmergencyPhone = "+45 65 43 21 09",
|
||||
BankAccount = "4567-8901234567",
|
||||
TaxCard = "Hovedkort",
|
||||
HourlyRate = "180 kr",
|
||||
MonthlyFixedSalary = "32.000 kr",
|
||||
Tags = new() { new("Stylist", "default") }
|
||||
},
|
||||
["employee-5"] = new EmployeeDetailRecord
|
||||
{
|
||||
Key = "employee-5",
|
||||
Initials = "SH",
|
||||
Name = "Sofie Hansen",
|
||||
Email = "sofie@salonbeauty.dk",
|
||||
Phone = "+45 56 78 90 12",
|
||||
Role = "employee",
|
||||
RoleKey = "employees.roles.employee",
|
||||
Status = "invited",
|
||||
StatusKey = "employees.status.invited",
|
||||
AvatarColor = "purple",
|
||||
BookingsThisYear = "0",
|
||||
RevenueThisYear = "0 kr",
|
||||
Rating = "-",
|
||||
EmployedSince = "2025",
|
||||
Address = "-",
|
||||
PostalCity = "-",
|
||||
EmploymentDate = "1. januar 2025",
|
||||
Position = "Junior Stylist",
|
||||
EmploymentType = "Fuldtid",
|
||||
HoursPerWeek = "37",
|
||||
BirthDate = "-",
|
||||
EmergencyContact = "-",
|
||||
EmergencyPhone = "-",
|
||||
BankAccount = "-",
|
||||
TaxCard = "-",
|
||||
HourlyRate = "150 kr",
|
||||
MonthlyFixedSalary = "28.000 kr",
|
||||
Tags = new() { new("Junior Stylist", "junior") }
|
||||
}
|
||||
};
|
||||
|
||||
public static EmployeeDetailRecord Get(string key)
|
||||
{
|
||||
if (!Employees.TryGetValue(key, out var employee))
|
||||
throw new KeyNotFoundException($"Employee with key '{key}' not found");
|
||||
return employee;
|
||||
}
|
||||
|
||||
public static IEnumerable<string> AllKeys => Employees.Keys;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Complete employee detail record used across all detail ViewComponents.
|
||||
/// </summary>
|
||||
public record EmployeeDetailRecord
|
||||
{
|
||||
// Identity
|
||||
public required string Key { get; init; }
|
||||
public required string Initials { get; init; }
|
||||
public required string Name { get; init; }
|
||||
public string? AvatarColor { get; init; }
|
||||
|
||||
// Contact
|
||||
public required string Email { get; init; }
|
||||
public required string Phone { get; init; }
|
||||
public required string Address { get; init; }
|
||||
public required string PostalCity { get; init; }
|
||||
|
||||
// Role & Status
|
||||
public required string Role { get; init; }
|
||||
public required string RoleKey { get; init; }
|
||||
public required string Status { get; init; }
|
||||
public required string StatusKey { get; init; }
|
||||
|
||||
// Stats
|
||||
public required string BookingsThisYear { get; init; }
|
||||
public required string RevenueThisYear { get; init; }
|
||||
public required string Rating { get; init; }
|
||||
public required string EmployedSince { get; init; }
|
||||
|
||||
// Employment
|
||||
public required string EmploymentDate { get; init; }
|
||||
public required string Position { get; init; }
|
||||
public required string EmploymentType { get; init; }
|
||||
public required string HoursPerWeek { get; init; }
|
||||
|
||||
// Personal
|
||||
public required string BirthDate { get; init; }
|
||||
public required string EmergencyContact { get; init; }
|
||||
public required string EmergencyPhone { get; init; }
|
||||
|
||||
// Salary
|
||||
public required string BankAccount { get; init; }
|
||||
public required string TaxCard { get; init; }
|
||||
public required string HourlyRate { get; init; }
|
||||
public required string MonthlyFixedSalary { get; init; }
|
||||
|
||||
// Tags (certifications, specialties)
|
||||
public List<EmployeeTag> Tags { get; init; } = new();
|
||||
}
|
||||
|
||||
public record EmployeeTag(string Text, string CssClass);
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
@model PlanTempus.Application.Features.Employees.Components.EmployeeDetailGeneralViewModel
|
||||
|
||||
<swp-detail-grid>
|
||||
<!-- Left column -->
|
||||
<div>
|
||||
<!-- Contact Card -->
|
||||
<swp-card>
|
||||
<swp-section-label>@Model.LabelContact</swp-section-label>
|
||||
<swp-edit-section>
|
||||
<swp-edit-row>
|
||||
<swp-edit-label>@Model.LabelFullName</swp-edit-label>
|
||||
<swp-edit-value contenteditable="true">@Model.Name</swp-edit-value>
|
||||
</swp-edit-row>
|
||||
<swp-edit-row>
|
||||
<swp-edit-label>@Model.LabelEmail</swp-edit-label>
|
||||
<swp-edit-value contenteditable="true">@Model.Email</swp-edit-value>
|
||||
</swp-edit-row>
|
||||
<swp-edit-row>
|
||||
<swp-edit-label>@Model.LabelPhone</swp-edit-label>
|
||||
<swp-edit-value contenteditable="true">@Model.Phone</swp-edit-value>
|
||||
</swp-edit-row>
|
||||
<swp-edit-row>
|
||||
<swp-edit-label>@Model.LabelAddress</swp-edit-label>
|
||||
<swp-edit-value contenteditable="true">@Model.Address</swp-edit-value>
|
||||
</swp-edit-row>
|
||||
<swp-edit-row>
|
||||
<swp-edit-label>@Model.LabelPostalCity</swp-edit-label>
|
||||
<swp-edit-value contenteditable="true">@Model.PostalCity</swp-edit-value>
|
||||
</swp-edit-row>
|
||||
</swp-edit-section>
|
||||
</swp-card>
|
||||
|
||||
<!-- Personal Card -->
|
||||
<swp-card>
|
||||
<swp-section-label>@Model.LabelPersonal</swp-section-label>
|
||||
<swp-edit-section>
|
||||
<swp-edit-row>
|
||||
<swp-edit-label>@Model.LabelBirthDate</swp-edit-label>
|
||||
<swp-edit-value contenteditable="true">@Model.BirthDate</swp-edit-value>
|
||||
</swp-edit-row>
|
||||
<swp-edit-row>
|
||||
<swp-edit-label>@Model.LabelEmergencyContact</swp-edit-label>
|
||||
<swp-edit-value contenteditable="true">@Model.EmergencyContact</swp-edit-value>
|
||||
</swp-edit-row>
|
||||
<swp-edit-row>
|
||||
<swp-edit-label>@Model.LabelEmergencyPhone</swp-edit-label>
|
||||
<swp-edit-value contenteditable="true">@Model.EmergencyPhone</swp-edit-value>
|
||||
</swp-edit-row>
|
||||
</swp-edit-section>
|
||||
</swp-card>
|
||||
</div>
|
||||
|
||||
<!-- Right column -->
|
||||
<div>
|
||||
<!-- Employment Card -->
|
||||
<swp-card>
|
||||
<swp-section-label>@Model.LabelEmployment</swp-section-label>
|
||||
<swp-edit-section>
|
||||
<swp-edit-row>
|
||||
<swp-edit-label>@Model.LabelEmploymentDate</swp-edit-label>
|
||||
<swp-edit-value contenteditable="true">@Model.EmploymentDate</swp-edit-value>
|
||||
</swp-edit-row>
|
||||
<swp-edit-row>
|
||||
<swp-edit-label>@Model.LabelPosition</swp-edit-label>
|
||||
<swp-edit-value contenteditable="true">@Model.Position</swp-edit-value>
|
||||
</swp-edit-row>
|
||||
<swp-edit-row>
|
||||
<swp-edit-label>@Model.LabelEmploymentType</swp-edit-label>
|
||||
<swp-edit-value contenteditable="true">@Model.EmploymentType</swp-edit-value>
|
||||
</swp-edit-row>
|
||||
<swp-edit-row>
|
||||
<swp-edit-label>@Model.LabelHoursPerWeek</swp-edit-label>
|
||||
<swp-edit-value contenteditable="true">@Model.HoursPerWeek</swp-edit-value>
|
||||
</swp-edit-row>
|
||||
</swp-edit-section>
|
||||
</swp-card>
|
||||
|
||||
<!-- Settings Card -->
|
||||
<swp-card>
|
||||
<swp-section-label>@Model.LabelSettings</swp-section-label>
|
||||
<swp-toggle-row>
|
||||
<div>
|
||||
<swp-toggle-label>@Model.SettingShowInBooking</swp-toggle-label>
|
||||
<swp-toggle-description>@Model.SettingShowInBookingDesc</swp-toggle-description>
|
||||
</div>
|
||||
<swp-toggle-slider data-value="yes">
|
||||
<swp-toggle-option>@Model.ToggleYes</swp-toggle-option>
|
||||
<swp-toggle-option>@Model.ToggleNo</swp-toggle-option>
|
||||
</swp-toggle-slider>
|
||||
</swp-toggle-row>
|
||||
<swp-toggle-row>
|
||||
<div>
|
||||
<swp-toggle-label>@Model.SettingSmsReminders</swp-toggle-label>
|
||||
<swp-toggle-description>@Model.SettingSmsRemindersDesc</swp-toggle-description>
|
||||
</div>
|
||||
<swp-toggle-slider data-value="yes">
|
||||
<swp-toggle-option>@Model.ToggleYes</swp-toggle-option>
|
||||
<swp-toggle-option>@Model.ToggleNo</swp-toggle-option>
|
||||
</swp-toggle-slider>
|
||||
</swp-toggle-row>
|
||||
<swp-toggle-row>
|
||||
<div>
|
||||
<swp-toggle-label>@Model.SettingEditCalendar</swp-toggle-label>
|
||||
<swp-toggle-description>@Model.SettingEditCalendarDesc</swp-toggle-description>
|
||||
</div>
|
||||
<swp-toggle-slider data-value="yes">
|
||||
<swp-toggle-option>@Model.ToggleYes</swp-toggle-option>
|
||||
<swp-toggle-option>@Model.ToggleNo</swp-toggle-option>
|
||||
</swp-toggle-slider>
|
||||
</swp-toggle-row>
|
||||
</swp-card>
|
||||
|
||||
<!-- Notifications Card -->
|
||||
<swp-card>
|
||||
<swp-section-label>@Model.LabelNotifications</swp-section-label>
|
||||
<swp-notification-intro>@Model.NotificationsIntro</swp-notification-intro>
|
||||
<swp-checkbox-list>
|
||||
<swp-checkbox-row class="checked">
|
||||
<swp-checkbox-box>
|
||||
<svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
|
||||
</swp-checkbox-box>
|
||||
<swp-checkbox-text>@Model.NotifOnlineBooking</swp-checkbox-text>
|
||||
</swp-checkbox-row>
|
||||
<swp-checkbox-row class="checked">
|
||||
<swp-checkbox-box>
|
||||
<svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
|
||||
</swp-checkbox-box>
|
||||
<swp-checkbox-text>@Model.NotifManualBooking</swp-checkbox-text>
|
||||
</swp-checkbox-row>
|
||||
<swp-checkbox-row>
|
||||
<swp-checkbox-box>
|
||||
<svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
|
||||
</swp-checkbox-box>
|
||||
<swp-checkbox-text>@Model.NotifCancellation</swp-checkbox-text>
|
||||
</swp-checkbox-row>
|
||||
<swp-checkbox-row>
|
||||
<swp-checkbox-box>
|
||||
<svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
|
||||
</swp-checkbox-box>
|
||||
<swp-checkbox-text>@Model.NotifWaitlist</swp-checkbox-text>
|
||||
</swp-checkbox-row>
|
||||
<swp-checkbox-row class="checked">
|
||||
<swp-checkbox-box>
|
||||
<svg viewBox="0 0 24 24"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
|
||||
</swp-checkbox-box>
|
||||
<swp-checkbox-text>@Model.NotifDailySummary</swp-checkbox-text>
|
||||
</swp-checkbox-row>
|
||||
</swp-checkbox-list>
|
||||
</swp-card>
|
||||
</div>
|
||||
</swp-detail-grid>
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using PlanTempus.Application.Features.Localization.Services;
|
||||
|
||||
namespace PlanTempus.Application.Features.Employees.Components;
|
||||
|
||||
public class EmployeeDetailGeneralViewComponent : ViewComponent
|
||||
{
|
||||
private readonly ILocalizationService _localization;
|
||||
|
||||
public EmployeeDetailGeneralViewComponent(ILocalizationService localization)
|
||||
{
|
||||
_localization = localization;
|
||||
}
|
||||
|
||||
public IViewComponentResult Invoke(string key)
|
||||
{
|
||||
var employee = EmployeeDetailCatalog.Get(key);
|
||||
|
||||
var model = new EmployeeDetailGeneralViewModel
|
||||
{
|
||||
// Contact
|
||||
Name = employee.Name,
|
||||
Email = employee.Email,
|
||||
Phone = employee.Phone,
|
||||
Address = employee.Address,
|
||||
PostalCity = employee.PostalCity,
|
||||
|
||||
// Personal
|
||||
BirthDate = employee.BirthDate,
|
||||
EmergencyContact = employee.EmergencyContact,
|
||||
EmergencyPhone = employee.EmergencyPhone,
|
||||
|
||||
// Employment
|
||||
EmploymentDate = employee.EmploymentDate,
|
||||
Position = employee.Position,
|
||||
EmploymentType = employee.EmploymentType,
|
||||
HoursPerWeek = employee.HoursPerWeek,
|
||||
|
||||
// Labels
|
||||
LabelContact = _localization.Get("employees.detail.contact"),
|
||||
LabelPersonal = _localization.Get("employees.detail.personal"),
|
||||
LabelEmployment = _localization.Get("employees.detail.employment"),
|
||||
LabelFullName = _localization.Get("employees.detail.fullname"),
|
||||
LabelEmail = _localization.Get("employees.detail.email"),
|
||||
LabelPhone = _localization.Get("employees.detail.phone"),
|
||||
LabelAddress = _localization.Get("employees.detail.address"),
|
||||
LabelPostalCity = _localization.Get("employees.detail.postalcity"),
|
||||
LabelBirthDate = _localization.Get("employees.detail.birthdate"),
|
||||
LabelEmergencyContact = _localization.Get("employees.detail.emergencycontact"),
|
||||
LabelEmergencyPhone = _localization.Get("employees.detail.emergencyphone"),
|
||||
LabelEmploymentDate = _localization.Get("employees.detail.employmentdate"),
|
||||
LabelPosition = _localization.Get("employees.detail.position"),
|
||||
LabelEmploymentType = _localization.Get("employees.detail.employmenttype"),
|
||||
LabelHoursPerWeek = _localization.Get("employees.detail.hoursperweek"),
|
||||
|
||||
// Settings
|
||||
LabelSettings = _localization.Get("employees.detail.settings.label"),
|
||||
SettingShowInBooking = _localization.Get("employees.detail.settings.showinbooking.label"),
|
||||
SettingShowInBookingDesc = _localization.Get("employees.detail.settings.showinbooking.desc"),
|
||||
SettingSmsReminders = _localization.Get("employees.detail.settings.smsreminders.label"),
|
||||
SettingSmsRemindersDesc = _localization.Get("employees.detail.settings.smsreminders.desc"),
|
||||
SettingEditCalendar = _localization.Get("employees.detail.settings.editcalendar.label"),
|
||||
SettingEditCalendarDesc = _localization.Get("employees.detail.settings.editcalendar.desc"),
|
||||
ToggleYes = _localization.Get("common.yes"),
|
||||
ToggleNo = _localization.Get("common.no"),
|
||||
|
||||
// Notifications
|
||||
LabelNotifications = _localization.Get("employees.detail.notifications.label"),
|
||||
NotificationsIntro = _localization.Get("employees.detail.notifications.intro"),
|
||||
NotifOnlineBooking = _localization.Get("employees.detail.notifications.onlinebooking"),
|
||||
NotifManualBooking = _localization.Get("employees.detail.notifications.manualbooking"),
|
||||
NotifCancellation = _localization.Get("employees.detail.notifications.cancellation"),
|
||||
NotifWaitlist = _localization.Get("employees.detail.notifications.waitlist"),
|
||||
NotifDailySummary = _localization.Get("employees.detail.notifications.dailysummary")
|
||||
};
|
||||
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
||||
public class EmployeeDetailGeneralViewModel
|
||||
{
|
||||
// Contact
|
||||
public required string Name { get; init; }
|
||||
public required string Email { get; init; }
|
||||
public required string Phone { get; init; }
|
||||
public required string Address { get; init; }
|
||||
public required string PostalCity { get; init; }
|
||||
|
||||
// Personal
|
||||
public required string BirthDate { get; init; }
|
||||
public required string EmergencyContact { get; init; }
|
||||
public required string EmergencyPhone { get; init; }
|
||||
|
||||
// Employment
|
||||
public required string EmploymentDate { get; init; }
|
||||
public required string Position { get; init; }
|
||||
public required string EmploymentType { get; init; }
|
||||
public required string HoursPerWeek { get; init; }
|
||||
|
||||
// Labels
|
||||
public required string LabelContact { get; init; }
|
||||
public required string LabelPersonal { get; init; }
|
||||
public required string LabelEmployment { get; init; }
|
||||
public required string LabelFullName { get; init; }
|
||||
public required string LabelEmail { get; init; }
|
||||
public required string LabelPhone { get; init; }
|
||||
public required string LabelAddress { get; init; }
|
||||
public required string LabelPostalCity { get; init; }
|
||||
public required string LabelBirthDate { get; init; }
|
||||
public required string LabelEmergencyContact { get; init; }
|
||||
public required string LabelEmergencyPhone { get; init; }
|
||||
public required string LabelEmploymentDate { get; init; }
|
||||
public required string LabelPosition { get; init; }
|
||||
public required string LabelEmploymentType { get; init; }
|
||||
public required string LabelHoursPerWeek { get; init; }
|
||||
|
||||
// Settings
|
||||
public required string LabelSettings { get; init; }
|
||||
public required string SettingShowInBooking { get; init; }
|
||||
public required string SettingShowInBookingDesc { get; init; }
|
||||
public required string SettingSmsReminders { get; init; }
|
||||
public required string SettingSmsRemindersDesc { get; init; }
|
||||
public required string SettingEditCalendar { get; init; }
|
||||
public required string SettingEditCalendarDesc { get; init; }
|
||||
public required string ToggleYes { get; init; }
|
||||
public required string ToggleNo { get; init; }
|
||||
|
||||
// Notifications
|
||||
public required string LabelNotifications { get; init; }
|
||||
public required string NotificationsIntro { get; init; }
|
||||
public required string NotifOnlineBooking { get; init; }
|
||||
public required string NotifManualBooking { get; init; }
|
||||
public required string NotifCancellation { get; init; }
|
||||
public required string NotifWaitlist { get; init; }
|
||||
public required string NotifDailySummary { get; init; }
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
@model PlanTempus.Application.Features.Employees.Components.EmployeeDetailHRViewModel
|
||||
|
||||
<swp-detail-grid>
|
||||
<swp-card>
|
||||
<swp-section-label>@Model.LabelDocuments</swp-section-label>
|
||||
<swp-document-list>
|
||||
<swp-document-item>
|
||||
<i class="ph ph-file-pdf"></i>
|
||||
<swp-document-name>@Model.LabelContract</swp-document-name>
|
||||
<swp-document-date>15. aug 2019</swp-document-date>
|
||||
</swp-document-item>
|
||||
<swp-document-item>
|
||||
<i class="ph ph-file-pdf"></i>
|
||||
<swp-document-name>Lønaftale 2024</swp-document-name>
|
||||
<swp-document-date>1. jan 2024</swp-document-date>
|
||||
</swp-document-item>
|
||||
</swp-document-list>
|
||||
</swp-card>
|
||||
|
||||
<swp-card>
|
||||
<swp-section-label>@Model.LabelVacation</swp-section-label>
|
||||
<swp-edit-section>
|
||||
<swp-edit-row>
|
||||
<swp-edit-label>Optjent ferie</swp-edit-label>
|
||||
<swp-edit-value>25 dage</swp-edit-value>
|
||||
</swp-edit-row>
|
||||
<swp-edit-row>
|
||||
<swp-edit-label>Afholdt ferie</swp-edit-label>
|
||||
<swp-edit-value>12 dage</swp-edit-value>
|
||||
</swp-edit-row>
|
||||
<swp-edit-row>
|
||||
<swp-edit-label>Resterende</swp-edit-label>
|
||||
<swp-edit-value>13 dage</swp-edit-value>
|
||||
</swp-edit-row>
|
||||
</swp-edit-section>
|
||||
</swp-card>
|
||||
|
||||
<swp-card>
|
||||
<swp-section-label>@Model.LabelNotes</swp-section-label>
|
||||
<swp-notes-area contenteditable="true">
|
||||
Ingen noter tilføjet endnu...
|
||||
</swp-notes-area>
|
||||
</swp-card>
|
||||
</swp-detail-grid>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using PlanTempus.Application.Features.Localization.Services;
|
||||
|
||||
namespace PlanTempus.Application.Features.Employees.Components;
|
||||
|
||||
public class EmployeeDetailHRViewComponent : ViewComponent
|
||||
{
|
||||
private readonly ILocalizationService _localization;
|
||||
|
||||
public EmployeeDetailHRViewComponent(ILocalizationService localization)
|
||||
{
|
||||
_localization = localization;
|
||||
}
|
||||
|
||||
public IViewComponentResult Invoke(string key)
|
||||
{
|
||||
var model = new EmployeeDetailHRViewModel
|
||||
{
|
||||
LabelDocuments = _localization.Get("employees.detail.hr.documents"),
|
||||
LabelContract = _localization.Get("employees.detail.hr.contract"),
|
||||
LabelVacation = _localization.Get("employees.detail.hr.vacation"),
|
||||
LabelSickLeave = _localization.Get("employees.detail.hr.sickleave"),
|
||||
LabelNotes = _localization.Get("employees.detail.hr.notes")
|
||||
};
|
||||
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
||||
public class EmployeeDetailHRViewModel
|
||||
{
|
||||
public required string LabelDocuments { get; init; }
|
||||
public required string LabelContract { get; init; }
|
||||
public required string LabelVacation { get; init; }
|
||||
public required string LabelSickLeave { get; init; }
|
||||
public required string LabelNotes { get; init; }
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
@model PlanTempus.Application.Features.Employees.Components.EmployeeDetailHeaderViewModel
|
||||
|
||||
<swp-employee-detail-header>
|
||||
<swp-employee-avatar-large class="@Model.AvatarColor">@Model.Initials</swp-employee-avatar-large>
|
||||
<swp-employee-info>
|
||||
<swp-employee-name-row>
|
||||
<swp-employee-name contenteditable="true">@Model.Name</swp-employee-name>
|
||||
@if (Model.Tags.Any())
|
||||
{
|
||||
<swp-tags-row>
|
||||
@foreach (var tag in Model.Tags)
|
||||
{
|
||||
<swp-tag class="@tag.CssClass">@tag.Text</swp-tag>
|
||||
}
|
||||
</swp-tags-row>
|
||||
}
|
||||
<swp-employee-status data-active="@Model.IsActive.ToString().ToLower()">
|
||||
<span class="icon">●</span>
|
||||
<span class="text">@Model.StatusText</span>
|
||||
</swp-employee-status>
|
||||
</swp-employee-name-row>
|
||||
<swp-fact-boxes-inline>
|
||||
<swp-fact-inline>
|
||||
<swp-fact-inline-value>@Model.BookingsThisYear</swp-fact-inline-value>
|
||||
<swp-fact-inline-label>@Model.LabelBookings</swp-fact-inline-label>
|
||||
</swp-fact-inline>
|
||||
<swp-fact-inline>
|
||||
<swp-fact-inline-value>@Model.RevenueThisYear</swp-fact-inline-value>
|
||||
<swp-fact-inline-label>@Model.LabelRevenue</swp-fact-inline-label>
|
||||
</swp-fact-inline>
|
||||
<swp-fact-inline>
|
||||
<swp-fact-inline-value>@Model.Rating</swp-fact-inline-value>
|
||||
<swp-fact-inline-label>@Model.LabelRating</swp-fact-inline-label>
|
||||
</swp-fact-inline>
|
||||
<swp-fact-inline>
|
||||
<swp-fact-inline-value>@Model.EmployedSince</swp-fact-inline-value>
|
||||
<swp-fact-inline-label>@Model.LabelEmployedSince</swp-fact-inline-label>
|
||||
</swp-fact-inline>
|
||||
</swp-fact-boxes-inline>
|
||||
</swp-employee-info>
|
||||
</swp-employee-detail-header>
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using PlanTempus.Application.Features.Localization.Services;
|
||||
|
||||
namespace PlanTempus.Application.Features.Employees.Components;
|
||||
|
||||
public class EmployeeDetailHeaderViewComponent : ViewComponent
|
||||
{
|
||||
private readonly ILocalizationService _localization;
|
||||
|
||||
public EmployeeDetailHeaderViewComponent(ILocalizationService localization)
|
||||
{
|
||||
_localization = localization;
|
||||
}
|
||||
|
||||
public IViewComponentResult Invoke(string key)
|
||||
{
|
||||
var employee = EmployeeDetailCatalog.Get(key);
|
||||
|
||||
var model = new EmployeeDetailHeaderViewModel
|
||||
{
|
||||
Initials = employee.Initials,
|
||||
Name = employee.Name,
|
||||
AvatarColor = employee.AvatarColor,
|
||||
Role = employee.Role,
|
||||
RoleText = _localization.Get(employee.RoleKey),
|
||||
Status = employee.Status,
|
||||
StatusText = _localization.Get(employee.StatusKey),
|
||||
BookingsThisYear = employee.BookingsThisYear,
|
||||
RevenueThisYear = employee.RevenueThisYear,
|
||||
Rating = employee.Rating,
|
||||
EmployedSince = employee.EmployedSince,
|
||||
LabelBookings = _localization.Get("employees.detail.bookings"),
|
||||
LabelRevenue = _localization.Get("employees.detail.revenue"),
|
||||
LabelRating = _localization.Get("employees.detail.rating"),
|
||||
LabelEmployedSince = _localization.Get("employees.detail.employedsince"),
|
||||
Tags = employee.Tags.Select(t => new EmployeeTagViewModel { Text = t.Text, CssClass = t.CssClass }).ToList()
|
||||
};
|
||||
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
||||
public class EmployeeDetailHeaderViewModel
|
||||
{
|
||||
public required string Initials { get; init; }
|
||||
public required string Name { get; init; }
|
||||
public string? AvatarColor { get; init; }
|
||||
public required string Role { get; init; }
|
||||
public required string RoleText { get; init; }
|
||||
public required string Status { get; init; }
|
||||
public required string StatusText { get; init; }
|
||||
public bool IsActive => Status == "active";
|
||||
public required string BookingsThisYear { get; init; }
|
||||
public required string RevenueThisYear { get; init; }
|
||||
public required string Rating { get; init; }
|
||||
public required string EmployedSince { get; init; }
|
||||
public required string LabelBookings { get; init; }
|
||||
public required string LabelRevenue { get; init; }
|
||||
public required string LabelRating { get; init; }
|
||||
public required string LabelEmployedSince { get; init; }
|
||||
public List<EmployeeTagViewModel> Tags { get; init; } = new();
|
||||
}
|
||||
|
||||
public class EmployeeTagViewModel
|
||||
{
|
||||
public required string Text { get; init; }
|
||||
public required string CssClass { get; init; }
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
@model PlanTempus.Application.Features.Employees.Components.EmployeeDetailHoursViewModel
|
||||
|
||||
<swp-detail-grid>
|
||||
<swp-card>
|
||||
<swp-section-label>@Model.LabelWeeklySchedule</swp-section-label>
|
||||
<swp-schedule-grid>
|
||||
<swp-schedule-row>
|
||||
<swp-schedule-day>@Model.LabelMonday</swp-schedule-day>
|
||||
<swp-schedule-time>09:00 - 17:00</swp-schedule-time>
|
||||
</swp-schedule-row>
|
||||
<swp-schedule-row>
|
||||
<swp-schedule-day>@Model.LabelTuesday</swp-schedule-day>
|
||||
<swp-schedule-time>09:00 - 17:00</swp-schedule-time>
|
||||
</swp-schedule-row>
|
||||
<swp-schedule-row>
|
||||
<swp-schedule-day>@Model.LabelWednesday</swp-schedule-day>
|
||||
<swp-schedule-time>09:00 - 17:00</swp-schedule-time>
|
||||
</swp-schedule-row>
|
||||
<swp-schedule-row>
|
||||
<swp-schedule-day>@Model.LabelThursday</swp-schedule-day>
|
||||
<swp-schedule-time>09:00 - 19:00</swp-schedule-time>
|
||||
</swp-schedule-row>
|
||||
<swp-schedule-row>
|
||||
<swp-schedule-day>@Model.LabelFriday</swp-schedule-day>
|
||||
<swp-schedule-time>09:00 - 16:00</swp-schedule-time>
|
||||
</swp-schedule-row>
|
||||
<swp-schedule-row class="off">
|
||||
<swp-schedule-day>@Model.LabelSaturday</swp-schedule-day>
|
||||
<swp-schedule-time>Fri</swp-schedule-time>
|
||||
</swp-schedule-row>
|
||||
<swp-schedule-row class="off">
|
||||
<swp-schedule-day>@Model.LabelSunday</swp-schedule-day>
|
||||
<swp-schedule-time>Fri</swp-schedule-time>
|
||||
</swp-schedule-row>
|
||||
</swp-schedule-grid>
|
||||
</swp-card>
|
||||
</swp-detail-grid>
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using PlanTempus.Application.Features.Localization.Services;
|
||||
|
||||
namespace PlanTempus.Application.Features.Employees.Components;
|
||||
|
||||
public class EmployeeDetailHoursViewComponent : ViewComponent
|
||||
{
|
||||
private readonly ILocalizationService _localization;
|
||||
|
||||
public EmployeeDetailHoursViewComponent(ILocalizationService localization)
|
||||
{
|
||||
_localization = localization;
|
||||
}
|
||||
|
||||
public IViewComponentResult Invoke(string key)
|
||||
{
|
||||
var model = new EmployeeDetailHoursViewModel
|
||||
{
|
||||
LabelWeeklySchedule = _localization.Get("employees.detail.hours.weekly"),
|
||||
LabelMonday = _localization.Get("employees.detail.hours.monday"),
|
||||
LabelTuesday = _localization.Get("employees.detail.hours.tuesday"),
|
||||
LabelWednesday = _localization.Get("employees.detail.hours.wednesday"),
|
||||
LabelThursday = _localization.Get("employees.detail.hours.thursday"),
|
||||
LabelFriday = _localization.Get("employees.detail.hours.friday"),
|
||||
LabelSaturday = _localization.Get("employees.detail.hours.saturday"),
|
||||
LabelSunday = _localization.Get("employees.detail.hours.sunday")
|
||||
};
|
||||
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
||||
public class EmployeeDetailHoursViewModel
|
||||
{
|
||||
public required string LabelWeeklySchedule { get; init; }
|
||||
public required string LabelMonday { get; init; }
|
||||
public required string LabelTuesday { get; init; }
|
||||
public required string LabelWednesday { get; init; }
|
||||
public required string LabelThursday { get; init; }
|
||||
public required string LabelFriday { get; init; }
|
||||
public required string LabelSaturday { get; init; }
|
||||
public required string LabelSunday { get; init; }
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
@model PlanTempus.Application.Features.Employees.Components.EmployeeDetailSalaryViewModel
|
||||
|
||||
<swp-detail-grid>
|
||||
<swp-card>
|
||||
<swp-section-label>@Model.LabelPaymentInfo</swp-section-label>
|
||||
<swp-edit-section>
|
||||
<swp-edit-row>
|
||||
<swp-edit-label>@Model.LabelBankAccount</swp-edit-label>
|
||||
<swp-edit-value contenteditable="true">@Model.BankAccount</swp-edit-value>
|
||||
</swp-edit-row>
|
||||
<swp-edit-row>
|
||||
<swp-edit-label>@Model.LabelTaxCard</swp-edit-label>
|
||||
<swp-edit-value contenteditable="true">@Model.TaxCard</swp-edit-value>
|
||||
</swp-edit-row>
|
||||
</swp-edit-section>
|
||||
</swp-card>
|
||||
|
||||
<swp-card>
|
||||
<swp-section-label>@Model.LabelSalarySettings</swp-section-label>
|
||||
<swp-edit-section>
|
||||
<swp-edit-row>
|
||||
<swp-edit-label>@Model.LabelHourlyRate</swp-edit-label>
|
||||
<swp-edit-value contenteditable="true">@Model.HourlyRate</swp-edit-value>
|
||||
</swp-edit-row>
|
||||
<swp-edit-row>
|
||||
<swp-edit-label>@Model.LabelMonthlyFixed</swp-edit-label>
|
||||
<swp-edit-value contenteditable="true">@Model.MonthlyFixedSalary</swp-edit-value>
|
||||
</swp-edit-row>
|
||||
<swp-edit-row>
|
||||
<swp-edit-label>@Model.LabelCommission</swp-edit-label>
|
||||
<swp-edit-value contenteditable="true">10%</swp-edit-value>
|
||||
</swp-edit-row>
|
||||
<swp-edit-row>
|
||||
<swp-edit-label>@Model.LabelProductCommission</swp-edit-label>
|
||||
<swp-edit-value contenteditable="true">5%</swp-edit-value>
|
||||
</swp-edit-row>
|
||||
</swp-edit-section>
|
||||
</swp-card>
|
||||
</swp-detail-grid>
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using PlanTempus.Application.Features.Localization.Services;
|
||||
|
||||
namespace PlanTempus.Application.Features.Employees.Components;
|
||||
|
||||
public class EmployeeDetailSalaryViewComponent : ViewComponent
|
||||
{
|
||||
private readonly ILocalizationService _localization;
|
||||
|
||||
public EmployeeDetailSalaryViewComponent(ILocalizationService localization)
|
||||
{
|
||||
_localization = localization;
|
||||
}
|
||||
|
||||
public IViewComponentResult Invoke(string key)
|
||||
{
|
||||
var employee = EmployeeDetailCatalog.Get(key);
|
||||
|
||||
var model = new EmployeeDetailSalaryViewModel
|
||||
{
|
||||
BankAccount = employee.BankAccount,
|
||||
TaxCard = employee.TaxCard,
|
||||
HourlyRate = employee.HourlyRate,
|
||||
MonthlyFixedSalary = employee.MonthlyFixedSalary,
|
||||
LabelPaymentInfo = _localization.Get("employees.detail.salary.paymentinfo"),
|
||||
LabelBankAccount = _localization.Get("employees.detail.salary.bankaccount"),
|
||||
LabelTaxCard = _localization.Get("employees.detail.salary.taxcard"),
|
||||
LabelSalarySettings = _localization.Get("employees.detail.salary.settings"),
|
||||
LabelHourlyRate = _localization.Get("employees.detail.salary.hourlyrate"),
|
||||
LabelMonthlyFixed = _localization.Get("employees.detail.salary.monthlyfixed"),
|
||||
LabelCommission = _localization.Get("employees.detail.salary.commission"),
|
||||
LabelProductCommission = _localization.Get("employees.detail.salary.productcommission")
|
||||
};
|
||||
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
||||
public class EmployeeDetailSalaryViewModel
|
||||
{
|
||||
public required string BankAccount { get; init; }
|
||||
public required string TaxCard { get; init; }
|
||||
public required string HourlyRate { get; init; }
|
||||
public required string MonthlyFixedSalary { get; init; }
|
||||
public required string LabelPaymentInfo { get; init; }
|
||||
public required string LabelBankAccount { get; init; }
|
||||
public required string LabelTaxCard { get; init; }
|
||||
public required string LabelSalarySettings { get; init; }
|
||||
public required string LabelHourlyRate { get; init; }
|
||||
public required string LabelMonthlyFixed { get; init; }
|
||||
public required string LabelCommission { get; init; }
|
||||
public required string LabelProductCommission { get; init; }
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
@model PlanTempus.Application.Features.Employees.Components.EmployeeDetailServicesViewModel
|
||||
|
||||
<swp-detail-grid>
|
||||
<swp-card>
|
||||
<swp-section-label>@Model.LabelAssignedServices</swp-section-label>
|
||||
<swp-service-list>
|
||||
<swp-service-item>
|
||||
<swp-service-name>Dameklip</swp-service-name>
|
||||
<swp-service-duration>45 min</swp-service-duration>
|
||||
<swp-service-price>450 kr</swp-service-price>
|
||||
</swp-service-item>
|
||||
<swp-service-item>
|
||||
<swp-service-name>Herreklip</swp-service-name>
|
||||
<swp-service-duration>30 min</swp-service-duration>
|
||||
<swp-service-price>350 kr</swp-service-price>
|
||||
</swp-service-item>
|
||||
<swp-service-item>
|
||||
<swp-service-name>Farvning</swp-service-name>
|
||||
<swp-service-duration>90 min</swp-service-duration>
|
||||
<swp-service-price>850 kr</swp-service-price>
|
||||
</swp-service-item>
|
||||
<swp-service-item>
|
||||
<swp-service-name>Balayage</swp-service-name>
|
||||
<swp-service-duration>120 min</swp-service-duration>
|
||||
<swp-service-price>1.200 kr</swp-service-price>
|
||||
</swp-service-item>
|
||||
<swp-service-item>
|
||||
<swp-service-name>Highlights</swp-service-name>
|
||||
<swp-service-duration>90 min</swp-service-duration>
|
||||
<swp-service-price>950 kr</swp-service-price>
|
||||
</swp-service-item>
|
||||
</swp-service-list>
|
||||
</swp-card>
|
||||
</swp-detail-grid>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using PlanTempus.Application.Features.Localization.Services;
|
||||
|
||||
namespace PlanTempus.Application.Features.Employees.Components;
|
||||
|
||||
public class EmployeeDetailServicesViewComponent : ViewComponent
|
||||
{
|
||||
private readonly ILocalizationService _localization;
|
||||
|
||||
public EmployeeDetailServicesViewComponent(ILocalizationService localization)
|
||||
{
|
||||
_localization = localization;
|
||||
}
|
||||
|
||||
public IViewComponentResult Invoke(string key)
|
||||
{
|
||||
var model = new EmployeeDetailServicesViewModel
|
||||
{
|
||||
LabelAssignedServices = _localization.Get("employees.detail.services.assigned")
|
||||
};
|
||||
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
||||
public class EmployeeDetailServicesViewModel
|
||||
{
|
||||
public required string LabelAssignedServices { get; init; }
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
@model PlanTempus.Application.Features.Employees.Components.EmployeeDetailStatsViewModel
|
||||
|
||||
<swp-detail-grid>
|
||||
<swp-card>
|
||||
<swp-section-label>@Model.LabelPerformance</swp-section-label>
|
||||
<swp-stats-row>
|
||||
<swp-stat-card class="teal">
|
||||
<swp-stat-value>@Model.BookingsThisYear</swp-stat-value>
|
||||
<swp-stat-label>@Model.LabelBookingsThisYear</swp-stat-label>
|
||||
</swp-stat-card>
|
||||
<swp-stat-card class="purple">
|
||||
<swp-stat-value>@Model.RevenueThisYear</swp-stat-value>
|
||||
<swp-stat-label>@Model.LabelRevenueThisYear</swp-stat-label>
|
||||
</swp-stat-card>
|
||||
<swp-stat-card class="amber">
|
||||
<swp-stat-value>@Model.Rating</swp-stat-value>
|
||||
<swp-stat-label>@Model.LabelAvgRating</swp-stat-label>
|
||||
</swp-stat-card>
|
||||
<swp-stat-card>
|
||||
<swp-stat-value>87%</swp-stat-value>
|
||||
<swp-stat-label>@Model.LabelOccupancy</swp-stat-label>
|
||||
</swp-stat-card>
|
||||
</swp-stats-row>
|
||||
</swp-card>
|
||||
</swp-detail-grid>
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using PlanTempus.Application.Features.Localization.Services;
|
||||
|
||||
namespace PlanTempus.Application.Features.Employees.Components;
|
||||
|
||||
public class EmployeeDetailStatsViewComponent : ViewComponent
|
||||
{
|
||||
private readonly ILocalizationService _localization;
|
||||
|
||||
public EmployeeDetailStatsViewComponent(ILocalizationService localization)
|
||||
{
|
||||
_localization = localization;
|
||||
}
|
||||
|
||||
public IViewComponentResult Invoke(string key)
|
||||
{
|
||||
var employee = EmployeeDetailCatalog.Get(key);
|
||||
|
||||
var model = new EmployeeDetailStatsViewModel
|
||||
{
|
||||
BookingsThisYear = employee.BookingsThisYear,
|
||||
RevenueThisYear = employee.RevenueThisYear,
|
||||
Rating = employee.Rating,
|
||||
LabelPerformance = _localization.Get("employees.detail.stats.performance"),
|
||||
LabelBookingsThisYear = _localization.Get("employees.detail.stats.bookingsyear"),
|
||||
LabelRevenueThisYear = _localization.Get("employees.detail.stats.revenueyear"),
|
||||
LabelAvgRating = _localization.Get("employees.detail.stats.avgrating"),
|
||||
LabelOccupancy = _localization.Get("employees.detail.stats.occupancy")
|
||||
};
|
||||
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
||||
public class EmployeeDetailStatsViewModel
|
||||
{
|
||||
public required string BookingsThisYear { get; init; }
|
||||
public required string RevenueThisYear { get; init; }
|
||||
public required string Rating { get; init; }
|
||||
public required string LabelPerformance { get; init; }
|
||||
public required string LabelBookingsThisYear { get; init; }
|
||||
public required string LabelRevenueThisYear { get; init; }
|
||||
public required string LabelAvgRating { get; init; }
|
||||
public required string LabelOccupancy { get; init; }
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
@model PlanTempus.Application.Features.Employees.Components.EmployeeDetailViewViewModel
|
||||
|
||||
<swp-employee-detail-view id="employee-detail-view" data-employee="@Model.EmployeeKey">
|
||||
<!-- Sticky Header (generic from page.css) -->
|
||||
<swp-sticky-header>
|
||||
<swp-header-content>
|
||||
<!-- Page Header with Back Button -->
|
||||
<swp-page-header>
|
||||
<swp-page-title>
|
||||
<swp-back-link data-employee-back>
|
||||
<i class="ph ph-arrow-left"></i>
|
||||
@Model.BackText
|
||||
</swp-back-link>
|
||||
</swp-page-title>
|
||||
<swp-page-actions>
|
||||
<swp-btn class="primary">
|
||||
<i class="ph ph-floppy-disk"></i>
|
||||
@Model.SaveButtonText
|
||||
</swp-btn>
|
||||
</swp-page-actions>
|
||||
</swp-page-header>
|
||||
|
||||
<!-- Employee Header -->
|
||||
@await Component.InvokeAsync("EmployeeDetailHeader", Model.EmployeeKey)
|
||||
</swp-header-content>
|
||||
|
||||
<!-- Tabs (outside header-content, inside sticky-header) -->
|
||||
<swp-tab-bar>
|
||||
<swp-tab class="active" data-tab="general">@Model.TabGeneral</swp-tab>
|
||||
<swp-tab data-tab="hours">@Model.TabHours</swp-tab>
|
||||
<swp-tab data-tab="services">@Model.TabServices</swp-tab>
|
||||
<swp-tab data-tab="salary">@Model.TabSalary</swp-tab>
|
||||
<swp-tab data-tab="hr">@Model.TabHR</swp-tab>
|
||||
<swp-tab data-tab="stats">@Model.TabStats</swp-tab>
|
||||
</swp-tab-bar>
|
||||
</swp-sticky-header>
|
||||
|
||||
<!-- Tab Contents -->
|
||||
<swp-tab-content data-tab="general" class="active">
|
||||
<swp-page-container>
|
||||
@await Component.InvokeAsync("EmployeeDetailGeneral", Model.EmployeeKey)
|
||||
</swp-page-container>
|
||||
</swp-tab-content>
|
||||
|
||||
<swp-tab-content data-tab="hours">
|
||||
<swp-page-container>
|
||||
@await Component.InvokeAsync("EmployeeDetailHours", Model.EmployeeKey)
|
||||
</swp-page-container>
|
||||
</swp-tab-content>
|
||||
|
||||
<swp-tab-content data-tab="services">
|
||||
<swp-page-container>
|
||||
@await Component.InvokeAsync("EmployeeDetailServices", Model.EmployeeKey)
|
||||
</swp-page-container>
|
||||
</swp-tab-content>
|
||||
|
||||
<swp-tab-content data-tab="salary">
|
||||
<swp-page-container>
|
||||
@await Component.InvokeAsync("EmployeeDetailSalary", Model.EmployeeKey)
|
||||
</swp-page-container>
|
||||
</swp-tab-content>
|
||||
|
||||
<swp-tab-content data-tab="hr">
|
||||
<swp-page-container>
|
||||
@await Component.InvokeAsync("EmployeeDetailHR", Model.EmployeeKey)
|
||||
</swp-page-container>
|
||||
</swp-tab-content>
|
||||
|
||||
<swp-tab-content data-tab="stats">
|
||||
<swp-page-container>
|
||||
@await Component.InvokeAsync("EmployeeDetailStats", Model.EmployeeKey)
|
||||
</swp-page-container>
|
||||
</swp-tab-content>
|
||||
</swp-employee-detail-view>
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using PlanTempus.Application.Features.Localization.Services;
|
||||
|
||||
namespace PlanTempus.Application.Features.Employees.Components;
|
||||
|
||||
public class EmployeeDetailViewViewComponent : ViewComponent
|
||||
{
|
||||
private readonly ILocalizationService _localization;
|
||||
|
||||
public EmployeeDetailViewViewComponent(ILocalizationService localization)
|
||||
{
|
||||
_localization = localization;
|
||||
}
|
||||
|
||||
public IViewComponentResult Invoke(string key)
|
||||
{
|
||||
var employee = EmployeeDetailCatalog.Get(key);
|
||||
|
||||
var model = new EmployeeDetailViewViewModel
|
||||
{
|
||||
EmployeeKey = employee.Key,
|
||||
BackText = _localization.Get("employees.detail.back"),
|
||||
SaveButtonText = _localization.Get("employees.detail.save"),
|
||||
TabGeneral = _localization.Get("employees.detail.tabs.general"),
|
||||
TabHours = _localization.Get("employees.detail.tabs.hours"),
|
||||
TabServices = _localization.Get("employees.detail.tabs.services"),
|
||||
TabSalary = _localization.Get("employees.detail.tabs.salary"),
|
||||
TabHR = _localization.Get("employees.detail.tabs.hr"),
|
||||
TabStats = _localization.Get("employees.detail.tabs.stats")
|
||||
};
|
||||
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
||||
public class EmployeeDetailViewViewModel
|
||||
{
|
||||
public required string EmployeeKey { get; init; }
|
||||
public required string BackText { get; init; }
|
||||
public required string SaveButtonText { get; init; }
|
||||
public required string TabGeneral { get; init; }
|
||||
public required string TabHours { get; init; }
|
||||
public required string TabServices { get; init; }
|
||||
public required string TabSalary { get; init; }
|
||||
public required string TabHR { get; init; }
|
||||
public required string TabStats { get; init; }
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
@model PlanTempus.Application.Features.Employees.Components.EmployeeRowViewModel
|
||||
|
||||
<swp-employee-row data-employee-detail="@Model.Key">
|
||||
<swp-employee-cell>
|
||||
<swp-user-info>
|
||||
<swp-user-avatar class="@Model.AvatarColor">@Model.Initials</swp-user-avatar>
|
||||
<swp-user-details>
|
||||
<swp-user-name>@Model.Name</swp-user-name>
|
||||
<swp-user-email>@Model.Email</swp-user-email>
|
||||
</swp-user-details>
|
||||
</swp-user-info>
|
||||
</swp-employee-cell>
|
||||
<swp-employee-cell>
|
||||
<swp-status-badge class="@Model.Role">@Model.RoleText</swp-status-badge>
|
||||
</swp-employee-cell>
|
||||
<swp-employee-cell>
|
||||
<swp-status-badge class="@Model.Status">@Model.StatusText</swp-status-badge>
|
||||
</swp-employee-cell>
|
||||
<swp-employee-cell>@Model.LastActive</swp-employee-cell>
|
||||
<swp-employee-cell>
|
||||
<swp-row-toggle>
|
||||
<i class="ph ph-caret-right"></i>
|
||||
</swp-row-toggle>
|
||||
</swp-employee-cell>
|
||||
</swp-employee-row>
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using PlanTempus.Application.Features.Localization.Services;
|
||||
|
||||
namespace PlanTempus.Application.Features.Employees.Components;
|
||||
|
||||
public class EmployeeRowViewComponent : ViewComponent
|
||||
{
|
||||
private readonly ILocalizationService _localization;
|
||||
|
||||
public EmployeeRowViewComponent(ILocalizationService localization)
|
||||
{
|
||||
_localization = localization;
|
||||
}
|
||||
|
||||
public IViewComponentResult Invoke(string key)
|
||||
{
|
||||
var model = EmployeeRowCatalog.Get(key, _localization);
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
||||
public class EmployeeRowViewModel
|
||||
{
|
||||
public required string Key { get; init; }
|
||||
public required string Initials { get; init; }
|
||||
public required string Name { get; init; }
|
||||
public required string Email { get; init; }
|
||||
public required string Role { get; init; }
|
||||
public required string RoleText { get; init; }
|
||||
public required string Status { get; init; }
|
||||
public required string StatusText { get; init; }
|
||||
public required string LastActive { get; init; }
|
||||
public string? AvatarColor { get; init; }
|
||||
public bool IsOwner { get; init; }
|
||||
public bool IsInvited { get; init; }
|
||||
}
|
||||
|
||||
internal class EmployeeRowData
|
||||
{
|
||||
public required string Key { get; init; }
|
||||
public required string Initials { get; init; }
|
||||
public required string Name { get; init; }
|
||||
public required string Email { get; init; }
|
||||
public required string Role { get; init; }
|
||||
public required string RoleKey { get; init; }
|
||||
public required string Status { get; init; }
|
||||
public required string StatusKey { get; init; }
|
||||
public required string LastActive { get; init; }
|
||||
public string? AvatarColor { get; init; }
|
||||
}
|
||||
|
||||
public static class EmployeeRowCatalog
|
||||
{
|
||||
private static readonly Dictionary<string, EmployeeRowData> Employees = new()
|
||||
{
|
||||
["employee-1"] = new EmployeeRowData
|
||||
{
|
||||
Key = "employee-1",
|
||||
Initials = "MJ",
|
||||
Name = "Maria Jensen",
|
||||
Email = "maria@salonbeauty.dk",
|
||||
Role = "owner",
|
||||
RoleKey = "employees.roles.owner",
|
||||
Status = "active",
|
||||
StatusKey = "employees.status.active",
|
||||
LastActive = "I dag, 14:32"
|
||||
},
|
||||
["employee-2"] = new EmployeeRowData
|
||||
{
|
||||
Key = "employee-2",
|
||||
Initials = "AS",
|
||||
Name = "Anna Sørensen",
|
||||
Email = "anna@salonbeauty.dk",
|
||||
Role = "admin",
|
||||
RoleKey = "employees.roles.admin",
|
||||
Status = "active",
|
||||
StatusKey = "employees.status.active",
|
||||
LastActive = "I dag, 12:15",
|
||||
AvatarColor = "purple"
|
||||
},
|
||||
["employee-3"] = new EmployeeRowData
|
||||
{
|
||||
Key = "employee-3",
|
||||
Initials = "LP",
|
||||
Name = "Louise Pedersen",
|
||||
Email = "louise@salonbeauty.dk",
|
||||
Role = "leader",
|
||||
RoleKey = "employees.roles.leader",
|
||||
Status = "active",
|
||||
StatusKey = "employees.status.active",
|
||||
LastActive = "I går, 17:45",
|
||||
AvatarColor = "blue"
|
||||
},
|
||||
["employee-4"] = new EmployeeRowData
|
||||
{
|
||||
Key = "employee-4",
|
||||
Initials = "KN",
|
||||
Name = "Katrine Nielsen",
|
||||
Email = "katrine@salonbeauty.dk",
|
||||
Role = "employee",
|
||||
RoleKey = "employees.roles.employee",
|
||||
Status = "active",
|
||||
StatusKey = "employees.status.active",
|
||||
LastActive = "27. dec, 09:30",
|
||||
AvatarColor = "amber"
|
||||
},
|
||||
["employee-5"] = new EmployeeRowData
|
||||
{
|
||||
Key = "employee-5",
|
||||
Initials = "SH",
|
||||
Name = "Sofie Hansen",
|
||||
Email = "sofie@salonbeauty.dk",
|
||||
Role = "employee",
|
||||
RoleKey = "employees.roles.employee",
|
||||
Status = "invited",
|
||||
StatusKey = "employees.status.invited",
|
||||
LastActive = "-",
|
||||
AvatarColor = "purple"
|
||||
}
|
||||
};
|
||||
|
||||
public static EmployeeRowViewModel Get(string key, ILocalizationService localization)
|
||||
{
|
||||
if (!Employees.TryGetValue(key, out var employee))
|
||||
throw new KeyNotFoundException($"Employee with key '{key}' not found");
|
||||
|
||||
return new EmployeeRowViewModel
|
||||
{
|
||||
Key = employee.Key,
|
||||
Initials = employee.Initials,
|
||||
Name = employee.Name,
|
||||
Email = employee.Email,
|
||||
Role = employee.Role,
|
||||
RoleText = localization.Get(employee.RoleKey),
|
||||
Status = employee.Status,
|
||||
StatusText = localization.Get(employee.StatusKey),
|
||||
LastActive = employee.LastActive,
|
||||
AvatarColor = employee.AvatarColor,
|
||||
IsOwner = employee.Role == "owner",
|
||||
IsInvited = employee.Status == "invited"
|
||||
};
|
||||
}
|
||||
|
||||
public static IEnumerable<string> AllKeys => Employees.Keys;
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
@model PlanTempus.Application.Features.Employees.Components.EmployeeStatCardViewModel
|
||||
|
||||
<swp-stat-card data-key="@Model.Key" class="@Model.Variant">
|
||||
<swp-stat-value>@Model.Value</swp-stat-value>
|
||||
<swp-stat-label>@Model.Label</swp-stat-label>
|
||||
</swp-stat-card>
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using PlanTempus.Application.Features.Localization.Services;
|
||||
|
||||
namespace PlanTempus.Application.Features.Employees.Components;
|
||||
|
||||
public class EmployeeStatCardViewComponent : ViewComponent
|
||||
{
|
||||
private readonly ILocalizationService _localization;
|
||||
|
||||
public EmployeeStatCardViewComponent(ILocalizationService localization)
|
||||
{
|
||||
_localization = localization;
|
||||
}
|
||||
|
||||
public IViewComponentResult Invoke(string key)
|
||||
{
|
||||
var model = EmployeeStatCardCatalog.Get(key, _localization);
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
||||
public class EmployeeStatCardViewModel
|
||||
{
|
||||
public required string Key { get; init; }
|
||||
public required string Value { get; init; }
|
||||
public required string Label { get; init; }
|
||||
public string? Variant { get; init; }
|
||||
}
|
||||
|
||||
internal class EmployeeStatCardData
|
||||
{
|
||||
public required string Key { get; init; }
|
||||
public required string Value { get; init; }
|
||||
public required string LabelKey { get; init; }
|
||||
public string? Variant { get; init; }
|
||||
}
|
||||
|
||||
public static class EmployeeStatCardCatalog
|
||||
{
|
||||
private static readonly Dictionary<string, EmployeeStatCardData> Cards = new()
|
||||
{
|
||||
["active-employees"] = new EmployeeStatCardData
|
||||
{
|
||||
Key = "active-employees",
|
||||
Value = "4",
|
||||
LabelKey = "employees.stats.activeEmployees",
|
||||
Variant = "teal"
|
||||
},
|
||||
["pending-invitations"] = new EmployeeStatCardData
|
||||
{
|
||||
Key = "pending-invitations",
|
||||
Value = "1",
|
||||
LabelKey = "employees.stats.pendingInvitations",
|
||||
Variant = "amber"
|
||||
},
|
||||
["roles-defined"] = new EmployeeStatCardData
|
||||
{
|
||||
Key = "roles-defined",
|
||||
Value = "4",
|
||||
LabelKey = "employees.stats.rolesDefined",
|
||||
Variant = "purple"
|
||||
}
|
||||
};
|
||||
|
||||
public static EmployeeStatCardViewModel Get(string key, ILocalizationService localization)
|
||||
{
|
||||
if (!Cards.TryGetValue(key, out var card))
|
||||
throw new KeyNotFoundException($"EmployeeStatCard with key '{key}' not found");
|
||||
|
||||
return new EmployeeStatCardViewModel
|
||||
{
|
||||
Key = card.Key,
|
||||
Value = card.Value,
|
||||
Label = localization.Get(card.LabelKey),
|
||||
Variant = card.Variant
|
||||
};
|
||||
}
|
||||
|
||||
public static IEnumerable<string> AllKeys => Cards.Keys;
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
@model PlanTempus.Application.Features.Employees.Components.EmployeeTableViewModel
|
||||
|
||||
<swp-users-header>
|
||||
<swp-users-count>
|
||||
<strong>@Model.CurrentCount af @Model.MaxCount</strong> @Model.CountLabel
|
||||
<swp-users-progress>
|
||||
<swp-users-progress-bar style="width: @Model.ProgressPercent.ToString("F1", System.Globalization.CultureInfo.InvariantCulture)%"></swp-users-progress-bar>
|
||||
</swp-users-progress>
|
||||
</swp-users-count>
|
||||
<swp-btn class="primary">
|
||||
<i class="ph ph-user-plus"></i>
|
||||
@Model.InviteButtonText
|
||||
</swp-btn>
|
||||
</swp-users-header>
|
||||
|
||||
<swp-employee-table-card>
|
||||
<swp-employee-table>
|
||||
<swp-employee-table-header>
|
||||
<swp-employee-row>
|
||||
<swp-employee-cell>@Model.ColumnUser</swp-employee-cell>
|
||||
<swp-employee-cell>@Model.ColumnRole</swp-employee-cell>
|
||||
<swp-employee-cell>@Model.ColumnStatus</swp-employee-cell>
|
||||
<swp-employee-cell>@Model.ColumnLastActive</swp-employee-cell>
|
||||
<swp-employee-cell></swp-employee-cell>
|
||||
</swp-employee-row>
|
||||
</swp-employee-table-header>
|
||||
<swp-employee-table-body>
|
||||
@foreach (var employeeKey in Model.EmployeeKeys)
|
||||
{
|
||||
@await Component.InvokeAsync("EmployeeRow", employeeKey)
|
||||
}
|
||||
</swp-employee-table-body>
|
||||
</swp-employee-table>
|
||||
</swp-employee-table-card>
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using PlanTempus.Application.Features.Localization.Services;
|
||||
|
||||
namespace PlanTempus.Application.Features.Employees.Components;
|
||||
|
||||
public class EmployeeTableViewComponent : ViewComponent
|
||||
{
|
||||
private readonly ILocalizationService _localization;
|
||||
|
||||
public EmployeeTableViewComponent(ILocalizationService localization)
|
||||
{
|
||||
_localization = localization;
|
||||
}
|
||||
|
||||
public IViewComponentResult Invoke(string key)
|
||||
{
|
||||
var model = EmployeeTableCatalog.Get(key, _localization);
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
||||
public class EmployeeTableViewModel
|
||||
{
|
||||
public required string Key { get; init; }
|
||||
public required int CurrentCount { get; init; }
|
||||
public required int MaxCount { get; init; }
|
||||
public required string CountLabel { get; init; }
|
||||
public required string InviteButtonText { get; init; }
|
||||
public required string ColumnUser { get; init; }
|
||||
public required string ColumnRole { get; init; }
|
||||
public required string ColumnStatus { get; init; }
|
||||
public required string ColumnLastActive { get; init; }
|
||||
public required IReadOnlyList<string> EmployeeKeys { get; init; }
|
||||
public double ProgressPercent => MaxCount > 0 ? (double)CurrentCount / MaxCount * 100 : 0;
|
||||
}
|
||||
|
||||
internal class EmployeeTableData
|
||||
{
|
||||
public required string Key { get; init; }
|
||||
public required int CurrentCount { get; init; }
|
||||
public required int MaxCount { get; init; }
|
||||
public required IReadOnlyList<string> EmployeeKeys { get; init; }
|
||||
}
|
||||
|
||||
public static class EmployeeTableCatalog
|
||||
{
|
||||
private static readonly Dictionary<string, EmployeeTableData> Tables = new()
|
||||
{
|
||||
["all-employees"] = new EmployeeTableData
|
||||
{
|
||||
Key = "all-employees",
|
||||
CurrentCount = 5,
|
||||
MaxCount = 8,
|
||||
EmployeeKeys = ["employee-1", "employee-2", "employee-3", "employee-4", "employee-5"]
|
||||
}
|
||||
};
|
||||
|
||||
public static EmployeeTableViewModel Get(string key, ILocalizationService localization)
|
||||
{
|
||||
if (!Tables.TryGetValue(key, out var table))
|
||||
throw new KeyNotFoundException($"EmployeeTable with key '{key}' not found");
|
||||
|
||||
return new EmployeeTableViewModel
|
||||
{
|
||||
Key = table.Key,
|
||||
CurrentCount = table.CurrentCount,
|
||||
MaxCount = table.MaxCount,
|
||||
CountLabel = localization.Get("employees.users.count"),
|
||||
InviteButtonText = localization.Get("employees.users.inviteUser"),
|
||||
ColumnUser = localization.Get("employees.users.columns.user"),
|
||||
ColumnRole = localization.Get("employees.users.columns.role"),
|
||||
ColumnStatus = localization.Get("employees.users.columns.status"),
|
||||
ColumnLastActive = localization.Get("employees.users.columns.lastActive"),
|
||||
EmployeeKeys = table.EmployeeKeys
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
@model PlanTempus.Application.Features.Employees.Components.PermissionsMatrixViewModel
|
||||
|
||||
<swp-permissions-matrix>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th localize="employees.permissions.title">Rettighed</th>
|
||||
@foreach (var role in Model.Roles)
|
||||
{
|
||||
<th>@role.Name</th>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var permission in Model.Permissions)
|
||||
{
|
||||
<tr>
|
||||
<td>
|
||||
<span class="permission-name">
|
||||
<i class="ph @permission.Icon"></i>
|
||||
@permission.Name
|
||||
</span>
|
||||
</td>
|
||||
@foreach (var role in Model.Roles)
|
||||
{
|
||||
<td>
|
||||
@if (permission.RoleAccess.TryGetValue(role.Key, out var hasAccess) && hasAccess)
|
||||
{
|
||||
<i class="ph ph-check-circle check"></i>
|
||||
}
|
||||
else
|
||||
{
|
||||
<i class="ph ph-minus no-access"></i>
|
||||
}
|
||||
</td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</swp-permissions-matrix>
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using PlanTempus.Application.Features.Localization.Services;
|
||||
|
||||
namespace PlanTempus.Application.Features.Employees.Components;
|
||||
|
||||
public class PermissionsMatrixViewComponent : ViewComponent
|
||||
{
|
||||
private readonly ILocalizationService _localization;
|
||||
|
||||
public PermissionsMatrixViewComponent(ILocalizationService localization)
|
||||
{
|
||||
_localization = localization;
|
||||
}
|
||||
|
||||
public IViewComponentResult Invoke(string key)
|
||||
{
|
||||
var model = PermissionsMatrixCatalog.Get(key, _localization);
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
||||
public class PermissionsMatrixViewModel
|
||||
{
|
||||
public required string Key { get; init; }
|
||||
public required IReadOnlyList<RoleHeader> Roles { get; init; }
|
||||
public required IReadOnlyList<PermissionRow> Permissions { get; init; }
|
||||
}
|
||||
|
||||
public class RoleHeader
|
||||
{
|
||||
public required string Key { get; init; }
|
||||
public required string Name { get; init; }
|
||||
}
|
||||
|
||||
public class PermissionRow
|
||||
{
|
||||
public required string Key { get; init; }
|
||||
public required string Name { get; init; }
|
||||
public required string Icon { get; init; }
|
||||
public required IReadOnlyDictionary<string, bool> RoleAccess { get; init; }
|
||||
}
|
||||
|
||||
internal class PermissionsMatrixData
|
||||
{
|
||||
public required string Key { get; init; }
|
||||
public required IReadOnlyList<string> RoleKeys { get; init; }
|
||||
public required IReadOnlyList<PermissionData> Permissions { get; init; }
|
||||
}
|
||||
|
||||
internal class PermissionData
|
||||
{
|
||||
public required string Key { get; init; }
|
||||
public required string NameKey { get; init; }
|
||||
public required string Icon { get; init; }
|
||||
public required IReadOnlyDictionary<string, bool> RoleAccess { get; init; }
|
||||
}
|
||||
|
||||
public static class PermissionsMatrixCatalog
|
||||
{
|
||||
private static readonly Dictionary<string, string> RoleNameKeys = new()
|
||||
{
|
||||
["owner"] = "employees.roles.owner",
|
||||
["admin"] = "employees.roles.admin",
|
||||
["leader"] = "employees.roles.leader",
|
||||
["employee"] = "employees.roles.employee"
|
||||
};
|
||||
|
||||
private static readonly Dictionary<string, PermissionsMatrixData> Matrices = new()
|
||||
{
|
||||
["default"] = new PermissionsMatrixData
|
||||
{
|
||||
Key = "default",
|
||||
RoleKeys = ["owner", "admin", "leader", "employee"],
|
||||
Permissions =
|
||||
[
|
||||
new PermissionData
|
||||
{
|
||||
Key = "calendar",
|
||||
NameKey = "employees.permissions.calendar",
|
||||
Icon = "ph-calendar",
|
||||
RoleAccess = new Dictionary<string, bool>
|
||||
{
|
||||
["owner"] = true,
|
||||
["admin"] = true,
|
||||
["leader"] = true,
|
||||
["employee"] = true
|
||||
}
|
||||
},
|
||||
new PermissionData
|
||||
{
|
||||
Key = "employees",
|
||||
NameKey = "employees.permissions.employees",
|
||||
Icon = "ph-users",
|
||||
RoleAccess = new Dictionary<string, bool>
|
||||
{
|
||||
["owner"] = true,
|
||||
["admin"] = true,
|
||||
["leader"] = true,
|
||||
["employee"] = false
|
||||
}
|
||||
},
|
||||
new PermissionData
|
||||
{
|
||||
Key = "customers",
|
||||
NameKey = "employees.permissions.customers",
|
||||
Icon = "ph-address-book",
|
||||
RoleAccess = new Dictionary<string, bool>
|
||||
{
|
||||
["owner"] = true,
|
||||
["admin"] = true,
|
||||
["leader"] = true,
|
||||
["employee"] = true
|
||||
}
|
||||
},
|
||||
new PermissionData
|
||||
{
|
||||
Key = "reports",
|
||||
NameKey = "employees.permissions.reports",
|
||||
Icon = "ph-chart-bar",
|
||||
RoleAccess = new Dictionary<string, bool>
|
||||
{
|
||||
["owner"] = true,
|
||||
["admin"] = true,
|
||||
["leader"] = false,
|
||||
["employee"] = false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
public static PermissionsMatrixViewModel Get(string key, ILocalizationService localization)
|
||||
{
|
||||
if (!Matrices.TryGetValue(key, out var matrix))
|
||||
throw new KeyNotFoundException($"PermissionsMatrix with key '{key}' not found");
|
||||
|
||||
var roles = matrix.RoleKeys.Select(roleKey => new RoleHeader
|
||||
{
|
||||
Key = roleKey,
|
||||
Name = localization.Get(RoleNameKeys[roleKey])
|
||||
}).ToList();
|
||||
|
||||
var permissions = matrix.Permissions.Select(p => new PermissionRow
|
||||
{
|
||||
Key = p.Key,
|
||||
Name = localization.Get(p.NameKey),
|
||||
Icon = p.Icon,
|
||||
RoleAccess = p.RoleAccess
|
||||
}).ToList();
|
||||
|
||||
return new PermissionsMatrixViewModel
|
||||
{
|
||||
Key = matrix.Key,
|
||||
Roles = roles,
|
||||
Permissions = permissions
|
||||
};
|
||||
}
|
||||
}
|
||||
57
PlanTempus.Application/Features/Employees/Pages/Index.cshtml
Normal file
57
PlanTempus.Application/Features/Employees/Pages/Index.cshtml
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
@page "/medarbejdere"
|
||||
@using PlanTempus.Application.Features.Employees.Components
|
||||
@model PlanTempus.Application.Features.Employees.Pages.IndexModel
|
||||
@{
|
||||
ViewData["Title"] = "Medarbejdere";
|
||||
}
|
||||
|
||||
<!-- List View (default) -->
|
||||
<swp-employees-list-view id="employees-list-view">
|
||||
<!-- Sticky Header (Header + Tabs) -->
|
||||
<swp-sticky-header>
|
||||
<!-- Header with page title and stats (has border-bottom) -->
|
||||
<swp-header-content>
|
||||
<swp-page-header>
|
||||
<swp-page-title>
|
||||
<h1 localize="employees.title">Medarbejdere</h1>
|
||||
<p localize="employees.subtitle">Administrer brugere, roller og rettigheder</p>
|
||||
</swp-page-title>
|
||||
</swp-page-header>
|
||||
|
||||
<swp-stats-row>
|
||||
@await Component.InvokeAsync("EmployeeStatCard", "active-employees")
|
||||
@await Component.InvokeAsync("EmployeeStatCard", "pending-invitations")
|
||||
@await Component.InvokeAsync("EmployeeStatCard", "roles-defined")
|
||||
</swp-stats-row>
|
||||
</swp-header-content>
|
||||
|
||||
<!-- Tab bar (outside header, so line is above tabs) -->
|
||||
<swp-tab-bar>
|
||||
<swp-tab class="active" data-tab="users">
|
||||
<i class="ph ph-users"></i>
|
||||
<span localize="employees.tabs.users">Brugere</span>
|
||||
</swp-tab>
|
||||
<swp-tab data-tab="roles">
|
||||
<i class="ph ph-shield-check"></i>
|
||||
<span localize="employees.tabs.roles">Roller</span>
|
||||
</swp-tab>
|
||||
</swp-tab-bar>
|
||||
</swp-sticky-header>
|
||||
|
||||
<!-- Tab: Users -->
|
||||
<swp-tab-content data-tab="users" class="active">
|
||||
<swp-page-container>
|
||||
@await Component.InvokeAsync("EmployeeTable", "all-employees")
|
||||
</swp-page-container>
|
||||
</swp-tab-content>
|
||||
|
||||
<!-- Tab: Roles -->
|
||||
<swp-tab-content data-tab="roles">
|
||||
<swp-page-container>
|
||||
@await Component.InvokeAsync("PermissionsMatrix", "default")
|
||||
</swp-page-container>
|
||||
</swp-tab-content>
|
||||
</swp-employees-list-view>
|
||||
|
||||
<!-- Detail View (hidden by default, shown when row clicked) -->
|
||||
@await Component.InvokeAsync("EmployeeDetailView", "employee-1")
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace PlanTempus.Application.Features.Employees.Pages;
|
||||
|
||||
public class IndexModel : PageModel
|
||||
{
|
||||
public void OnGet()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -29,7 +29,9 @@
|
|||
"to": "Til",
|
||||
"all": "Alle",
|
||||
"reset": "Nulstil",
|
||||
"status": "Status"
|
||||
"status": "Status",
|
||||
"yes": "Ja",
|
||||
"no": "Nej"
|
||||
},
|
||||
"sidebar": {
|
||||
"lockScreen": "Lås skærm",
|
||||
|
|
@ -216,5 +218,144 @@
|
|||
"pending": "Afventer",
|
||||
"overdue": "Forfalden"
|
||||
}
|
||||
},
|
||||
"employees": {
|
||||
"title": "Medarbejdere",
|
||||
"subtitle": "Administrer brugere, roller og rettigheder",
|
||||
"stats": {
|
||||
"activeEmployees": "Aktive medarbejdere",
|
||||
"pendingInvitations": "Afventer invitation",
|
||||
"rolesDefined": "Roller defineret"
|
||||
},
|
||||
"tabs": {
|
||||
"users": "Brugere",
|
||||
"roles": "Roller"
|
||||
},
|
||||
"users": {
|
||||
"count": "brugere",
|
||||
"inviteUser": "Inviter bruger",
|
||||
"columns": {
|
||||
"user": "Bruger",
|
||||
"role": "Rolle",
|
||||
"status": "Status",
|
||||
"lastActive": "Sidst aktiv"
|
||||
}
|
||||
},
|
||||
"roles": {
|
||||
"owner": "Ejer",
|
||||
"admin": "Admin",
|
||||
"leader": "Leder",
|
||||
"employee": "Medarbejder"
|
||||
},
|
||||
"status": {
|
||||
"active": "Aktiv",
|
||||
"invited": "Invitation sendt"
|
||||
},
|
||||
"permissions": {
|
||||
"title": "Rettighed",
|
||||
"calendar": "Kalender",
|
||||
"employees": "Medarbejdere",
|
||||
"customers": "Kunder",
|
||||
"reports": "Rapporter & Økonomi"
|
||||
},
|
||||
"actions": {
|
||||
"edit": "Rediger",
|
||||
"remove": "Fjern bruger",
|
||||
"resend": "Send invitation igen",
|
||||
"cancel": "Annuller invitation"
|
||||
},
|
||||
"detail": {
|
||||
"title": "Medarbejderdetaljer",
|
||||
"back": "Tilbage til medarbejdere",
|
||||
"save": "Gem ændringer",
|
||||
"tabs": {
|
||||
"general": "Generelt",
|
||||
"hours": "Arbejdstid",
|
||||
"services": "Services",
|
||||
"salary": "Løn",
|
||||
"hr": "HR",
|
||||
"stats": "Statistik"
|
||||
},
|
||||
"contact": "Kontaktoplysninger",
|
||||
"personal": "Personlige oplysninger",
|
||||
"employment": "Ansættelse",
|
||||
"fullname": "Fulde navn",
|
||||
"email": "E-mail",
|
||||
"phone": "Telefon",
|
||||
"address": "Adresse",
|
||||
"postalcity": "Postnr. & By",
|
||||
"birthdate": "Fødselsdato",
|
||||
"emergencycontact": "Nødkontakt",
|
||||
"emergencyphone": "Nødkontakt tlf.",
|
||||
"employmentdate": "Ansættelsesdato",
|
||||
"position": "Stilling",
|
||||
"employmenttype": "Ansættelsestype",
|
||||
"hoursperweek": "Timer/uge",
|
||||
"bookings": "bookinger i år",
|
||||
"revenue": "omsætning i år",
|
||||
"rating": "rating",
|
||||
"employedsince": "ansat siden",
|
||||
"hours": {
|
||||
"weekly": "Ugentlig arbejdstid",
|
||||
"monday": "Mandag",
|
||||
"tuesday": "Tirsdag",
|
||||
"wednesday": "Onsdag",
|
||||
"thursday": "Torsdag",
|
||||
"friday": "Fredag",
|
||||
"saturday": "Lørdag",
|
||||
"sunday": "Søndag"
|
||||
},
|
||||
"services": {
|
||||
"assigned": "Tildelte services"
|
||||
},
|
||||
"salary": {
|
||||
"paymentinfo": "Betalingsoplysninger",
|
||||
"bankaccount": "Bankkonto",
|
||||
"taxcard": "Skattekort",
|
||||
"settings": "Lønindstillinger",
|
||||
"hourlyrate": "Timesats",
|
||||
"monthlyfixed": "Fast månedsløn",
|
||||
"commission": "Provision (services)",
|
||||
"productcommission": "Provision (produkter)"
|
||||
},
|
||||
"hr": {
|
||||
"documents": "Dokumenter",
|
||||
"contract": "Ansættelseskontrakt",
|
||||
"vacation": "Ferie",
|
||||
"sickleave": "Sygefravær",
|
||||
"notes": "Noter"
|
||||
},
|
||||
"stats": {
|
||||
"performance": "Performance",
|
||||
"bookingsyear": "Bookinger i år",
|
||||
"revenueyear": "Omsætning i år",
|
||||
"avgrating": "Gns. rating",
|
||||
"occupancy": "Belægningsgrad"
|
||||
},
|
||||
"settings": {
|
||||
"label": "Indstillinger",
|
||||
"showinbooking": {
|
||||
"label": "Vis i online booking",
|
||||
"desc": "Kunder kan vælge denne medarbejder"
|
||||
},
|
||||
"smsreminders": {
|
||||
"label": "Modtag SMS-påmindelser",
|
||||
"desc": "Få besked om nye bookinger"
|
||||
},
|
||||
"editcalendar": {
|
||||
"label": "Kan redigere egen kalender",
|
||||
"desc": "Tillad ændringer i egne bookinger"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"label": "Notifikationer",
|
||||
"intro": "Vælg hvilke email-notifikationer medarbejderen skal modtage.",
|
||||
"onlinebooking": "Modtag email ved online booking",
|
||||
"manualbooking": "Modtag email ved manuel booking",
|
||||
"cancellation": "Modtag email ved aflysning",
|
||||
"waitlist": "Modtag email ved opskrivning til venteliste",
|
||||
"dailysummary": "Modtag daglig oversigt over morgendagens bookinger"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@
|
|||
"to": "To",
|
||||
"all": "All",
|
||||
"reset": "Reset",
|
||||
"status": "Status"
|
||||
"status": "Status",
|
||||
"yes": "Yes",
|
||||
"no": "No"
|
||||
},
|
||||
"sidebar": {
|
||||
"lockScreen": "Lock screen",
|
||||
|
|
@ -216,5 +218,107 @@
|
|||
"pending": "Pending",
|
||||
"overdue": "Overdue"
|
||||
}
|
||||
},
|
||||
"employees": {
|
||||
"title": "Employees",
|
||||
"subtitle": "Manage users, roles and permissions",
|
||||
"stats": {
|
||||
"activeEmployees": "Active employees",
|
||||
"pendingInvitations": "Pending invitations",
|
||||
"rolesDefined": "Roles defined"
|
||||
},
|
||||
"tabs": {
|
||||
"users": "Users",
|
||||
"roles": "Roles"
|
||||
},
|
||||
"users": {
|
||||
"count": "users",
|
||||
"inviteUser": "Invite user",
|
||||
"columns": {
|
||||
"user": "User",
|
||||
"role": "Role",
|
||||
"status": "Status",
|
||||
"lastActive": "Last active"
|
||||
}
|
||||
},
|
||||
"roles": {
|
||||
"owner": "Owner",
|
||||
"admin": "Admin",
|
||||
"leader": "Manager",
|
||||
"employee": "Employee"
|
||||
},
|
||||
"status": {
|
||||
"active": "Active",
|
||||
"invited": "Invitation sent"
|
||||
},
|
||||
"permissions": {
|
||||
"title": "Permission",
|
||||
"calendar": "Calendar",
|
||||
"employees": "Employees",
|
||||
"customers": "Customers",
|
||||
"reports": "Reports & Finance"
|
||||
},
|
||||
"actions": {
|
||||
"edit": "Edit",
|
||||
"remove": "Remove user",
|
||||
"resend": "Resend invitation",
|
||||
"cancel": "Cancel invitation"
|
||||
},
|
||||
"detail": {
|
||||
"title": "Employee details",
|
||||
"back": "Back to employees",
|
||||
"save": "Save changes",
|
||||
"tabs": {
|
||||
"general": "General",
|
||||
"hours": "Working hours",
|
||||
"services": "Services",
|
||||
"salary": "Salary",
|
||||
"hr": "HR",
|
||||
"stats": "Statistics"
|
||||
},
|
||||
"contact": "Contact information",
|
||||
"personal": "Personal information",
|
||||
"employment": "Employment",
|
||||
"fullname": "Full name",
|
||||
"email": "Email",
|
||||
"phone": "Phone",
|
||||
"address": "Address",
|
||||
"postalcity": "Postal code & City",
|
||||
"birthdate": "Date of birth",
|
||||
"emergencycontact": "Emergency contact",
|
||||
"emergencyphone": "Emergency phone",
|
||||
"employmentdate": "Employment date",
|
||||
"position": "Position",
|
||||
"employmenttype": "Employment type",
|
||||
"hoursperweek": "Hours/week",
|
||||
"bookings": "bookings this year",
|
||||
"revenue": "revenue this year",
|
||||
"rating": "rating",
|
||||
"employedsince": "employed since",
|
||||
"settings": {
|
||||
"label": "Settings",
|
||||
"showinbooking": {
|
||||
"label": "Show in online booking",
|
||||
"desc": "Customers can select this employee"
|
||||
},
|
||||
"smsreminders": {
|
||||
"label": "Receive SMS reminders",
|
||||
"desc": "Get notified about new bookings"
|
||||
},
|
||||
"editcalendar": {
|
||||
"label": "Can edit own calendar",
|
||||
"desc": "Allow changes to own bookings"
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"label": "Notifications",
|
||||
"intro": "Choose which email notifications the employee should receive.",
|
||||
"onlinebooking": "Receive email for online booking",
|
||||
"manualbooking": "Receive email for manual booking",
|
||||
"cancellation": "Receive email for cancellation",
|
||||
"waitlist": "Receive email for waitlist signup",
|
||||
"dailysummary": "Receive daily summary of tomorrow's bookings"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,8 +126,8 @@ public class MockMenuService : IMenuService
|
|||
{
|
||||
Id = "employees",
|
||||
Label = "Medarbejdere",
|
||||
Icon = "ph-user",
|
||||
Url = "/poc-medarbejdere.html",
|
||||
Icon = "ph-users-three",
|
||||
Url = "/medarbejdere",
|
||||
MinimumRole = UserRole.Manager,
|
||||
SortOrder = 4
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,9 +24,11 @@
|
|||
<link rel="stylesheet" href="~/css/quick-stats.css">
|
||||
<link rel="stylesheet" href="~/css/waitlist.css">
|
||||
<link rel="stylesheet" href="~/css/tabs.css">
|
||||
<link rel="stylesheet" href="~/css/controls.css">
|
||||
<link rel="stylesheet" href="~/css/cash.css">
|
||||
<link rel="stylesheet" href="~/css/auth.css">
|
||||
<link rel="stylesheet" href="~/css/account.css">
|
||||
<link rel="stylesheet" href="~/css/employees.css">
|
||||
@await RenderSectionAsync("Styles", required: false)
|
||||
</head>
|
||||
<body class="has-demo-banner">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue