Enhances service details with employees and addon sections
Adds new components for service employees and addons Introduces detailed views with selectable employees and add-ons Updates localization translations for new sections Implements time range slider functionality for availability
This commit is contained in:
parent
5e3811347c
commit
7643a6ab82
20 changed files with 830 additions and 336 deletions
|
|
@ -1,4 +1,4 @@
|
|||
<swp-payment-card>
|
||||
<swp-card class="payment">
|
||||
<swp-payment-method>
|
||||
<swp-payment-icon>
|
||||
<i class="ph ph-credit-card"></i>
|
||||
|
|
@ -37,4 +37,4 @@
|
|||
<i class="ph ph-arrows-clockwise"></i>
|
||||
<span localize="account.payment.switchToYearly">Skift til årlig betaling (spar 15%)</span>
|
||||
</swp-btn>
|
||||
</swp-payment-card>
|
||||
</swp-card>
|
||||
|
|
|
|||
|
|
@ -10,44 +10,44 @@
|
|||
<!-- Context Stats (changes based on active tab) -->
|
||||
<swp-header-content>
|
||||
<!-- Stats for Oversigt tab -->
|
||||
<swp-cash-stats data-for-tab="oversigt" class="active">
|
||||
<swp-cash-stat>
|
||||
<swp-cash-stat-value>12</swp-cash-stat-value>
|
||||
<swp-cash-stat-label localize="cash.stats.reconciliationsInPeriod">Afstemninger i periode</swp-cash-stat-label>
|
||||
</swp-cash-stat>
|
||||
<swp-cash-stat class="highlight">
|
||||
<swp-cash-stat-value>186.450 kr</swp-cash-stat-value>
|
||||
<swp-cash-stat-label localize="cash.stats.totalRevenue">Total omsætning</swp-cash-stat-label>
|
||||
</swp-cash-stat>
|
||||
<swp-cash-stat>
|
||||
<swp-cash-stat-value>42.340 kr</swp-cash-stat-value>
|
||||
<swp-cash-stat-label localize="cash.stats.cashSales">Kontantsalg</swp-cash-stat-label>
|
||||
</swp-cash-stat>
|
||||
<swp-cash-stat class="warning">
|
||||
<swp-cash-stat-value>-75 kr</swp-cash-stat-value>
|
||||
<swp-cash-stat-label localize="cash.stats.totalDifference">Samlet difference</swp-cash-stat-label>
|
||||
</swp-cash-stat>
|
||||
</swp-cash-stats>
|
||||
<swp-stats-row class="cols-4 active" data-for-tab="oversigt">
|
||||
<swp-stat-card>
|
||||
<swp-stat-value>12</swp-stat-value>
|
||||
<swp-stat-label localize="cash.stats.reconciliationsInPeriod">Afstemninger i periode</swp-stat-label>
|
||||
</swp-stat-card>
|
||||
<swp-stat-card class="highlight">
|
||||
<swp-stat-value>186.450 kr</swp-stat-value>
|
||||
<swp-stat-label localize="cash.stats.totalRevenue">Total omsætning</swp-stat-label>
|
||||
</swp-stat-card>
|
||||
<swp-stat-card>
|
||||
<swp-stat-value>42.340 kr</swp-stat-value>
|
||||
<swp-stat-label localize="cash.stats.cashSales">Kontantsalg</swp-stat-label>
|
||||
</swp-stat-card>
|
||||
<swp-stat-card class="warning">
|
||||
<swp-stat-value>-75 kr</swp-stat-value>
|
||||
<swp-stat-label localize="cash.stats.totalDifference">Samlet difference</swp-stat-label>
|
||||
</swp-stat-card>
|
||||
</swp-stats-row>
|
||||
|
||||
<!-- Stats for Kasseafstemning tab -->
|
||||
<swp-cash-stats data-for-tab="afstemning">
|
||||
<swp-cash-stat>
|
||||
<swp-cash-stat-value>47</swp-cash-stat-value>
|
||||
<swp-cash-stat-label localize="cash.stats.transactionsToday">Transaktioner i dag</swp-cash-stat-label>
|
||||
</swp-cash-stat>
|
||||
<swp-cash-stat class="highlight">
|
||||
<swp-cash-stat-value>18.865 kr</swp-cash-stat-value>
|
||||
<swp-cash-stat-label localize="cash.stats.revenueToday">Omsætning i dag</swp-cash-stat-label>
|
||||
</swp-cash-stat>
|
||||
<swp-cash-stat>
|
||||
<swp-cash-stat-value>29. dec 17:45</swp-cash-stat-value>
|
||||
<swp-cash-stat-label localize="cash.stats.lastReconciliation">Sidste afstemning</swp-cash-stat-label>
|
||||
</swp-cash-stat>
|
||||
<swp-cash-stat>
|
||||
<swp-cash-stat-value>Anna J.</swp-cash-stat-value>
|
||||
<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-stats-row class="cols-4" data-for-tab="afstemning">
|
||||
<swp-stat-card>
|
||||
<swp-stat-value>47</swp-stat-value>
|
||||
<swp-stat-label localize="cash.stats.transactionsToday">Transaktioner i dag</swp-stat-label>
|
||||
</swp-stat-card>
|
||||
<swp-stat-card class="highlight">
|
||||
<swp-stat-value>18.865 kr</swp-stat-value>
|
||||
<swp-stat-label localize="cash.stats.revenueToday">Omsætning i dag</swp-stat-label>
|
||||
</swp-stat-card>
|
||||
<swp-stat-card>
|
||||
<swp-stat-value>29. dec 17:45</swp-stat-value>
|
||||
<swp-stat-label localize="cash.stats.lastReconciliation">Sidste afstemning</swp-stat-label>
|
||||
</swp-stat-card>
|
||||
<swp-stat-card>
|
||||
<swp-stat-value>Anna J.</swp-stat-value>
|
||||
<swp-stat-label localize="cash.stats.openedRegister">Åbnede kassen 29. dec 09:05</swp-stat-label>
|
||||
</swp-stat-card>
|
||||
</swp-stats-row>
|
||||
</swp-header-content>
|
||||
|
||||
<!-- Tab Bar -->
|
||||
|
|
|
|||
|
|
@ -322,6 +322,16 @@
|
|||
"showPrice": "Vis pris",
|
||||
"showDuration": "Vis varighed"
|
||||
},
|
||||
"employees": {
|
||||
"employeesForService": "Medarbejdere der udfører denne service",
|
||||
"selectAll": "Vælg alle / Fravælg alle",
|
||||
"availability": "Tilgængelighed",
|
||||
"duration": "Varighed"
|
||||
},
|
||||
"addons": {
|
||||
"addonsForService": "Tilvalg til denne service",
|
||||
"addExistingAddon": "Tilføj eksisterende tilvalg"
|
||||
},
|
||||
"header": {
|
||||
"duration": "min varighed",
|
||||
"fromPrice": "fra pris",
|
||||
|
|
|
|||
|
|
@ -322,6 +322,16 @@
|
|||
"showPrice": "Show price",
|
||||
"showDuration": "Show duration"
|
||||
},
|
||||
"employees": {
|
||||
"employeesForService": "Employees performing this service",
|
||||
"selectAll": "Select all / Deselect all",
|
||||
"availability": "Availability",
|
||||
"duration": "Duration"
|
||||
},
|
||||
"addons": {
|
||||
"addonsForService": "Add-ons for this service",
|
||||
"addExistingAddon": "Add existing add-on"
|
||||
},
|
||||
"header": {
|
||||
"duration": "min duration",
|
||||
"fromPrice": "from price",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
@model PlanTempus.Application.Features.Services.Components.ServiceDetailAddonsViewModel
|
||||
|
||||
<swp-card>
|
||||
<swp-section-label>@Model.LabelAddonsForService</swp-section-label>
|
||||
|
||||
<swp-selectable-list>
|
||||
@foreach (var addon in Model.Addons)
|
||||
{
|
||||
<swp-selectable-item class="no-avatar @(addon.Selected ? "selected" : "")">
|
||||
<swp-selectable-checkbox>
|
||||
<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-selectable-checkbox>
|
||||
<swp-selectable-info>
|
||||
<swp-selectable-name>@addon.Name</swp-selectable-name>
|
||||
<swp-selectable-meta>
|
||||
<span>@addon.Price</span>
|
||||
<span>@addon.Duration</span>
|
||||
<swp-selectable-type class="@(addon.Required ? "required" : "")">@addon.Type</swp-selectable-type>
|
||||
</swp-selectable-meta>
|
||||
</swp-selectable-info>
|
||||
</swp-selectable-item>
|
||||
}
|
||||
</swp-selectable-list>
|
||||
|
||||
<swp-add-button>
|
||||
<i class="ph ph-plus"></i>
|
||||
@Model.LabelAddExistingAddon
|
||||
</swp-add-button>
|
||||
</swp-card>
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using PlanTempus.Application.Features.Localization.Services;
|
||||
|
||||
namespace PlanTempus.Application.Features.Services.Components;
|
||||
|
||||
public class ServiceDetailAddonsViewComponent : ViewComponent
|
||||
{
|
||||
private readonly ILocalizationService _localization;
|
||||
|
||||
public ServiceDetailAddonsViewComponent(ILocalizationService localization)
|
||||
{
|
||||
_localization = localization;
|
||||
}
|
||||
|
||||
public IViewComponentResult Invoke(string key)
|
||||
{
|
||||
var service = ServiceDetailCatalog.Get(key);
|
||||
|
||||
var model = new ServiceDetailAddonsViewModel
|
||||
{
|
||||
// Data
|
||||
Addons = service.Addons,
|
||||
|
||||
// Labels
|
||||
LabelAddonsForService = _localization.Get("services.detail.addons.addonsForService"),
|
||||
LabelAddExistingAddon = _localization.Get("services.detail.addons.addExistingAddon")
|
||||
};
|
||||
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
||||
public class ServiceDetailAddonsViewModel
|
||||
{
|
||||
// Data
|
||||
public required List<ServiceAddon> Addons { get; init; }
|
||||
|
||||
// Labels
|
||||
public required string LabelAddonsForService { get; init; }
|
||||
public required string LabelAddExistingAddon { get; init; }
|
||||
}
|
||||
|
|
@ -62,7 +62,35 @@ public static class ServiceDetailCatalog
|
|||
ShowInOnlineBookingRules = true,
|
||||
AllowEmployeeSelection = true,
|
||||
ShowPrice = true,
|
||||
ShowDuration = true
|
||||
ShowDuration = true,
|
||||
// Medarbejdere
|
||||
Employees = new()
|
||||
{
|
||||
new("emp-1", "Anna Sørensen", "AS", "Master Stylist", "master", true, "Standard"),
|
||||
new("emp-2", "Mette Jensen", "MJ", "Senior Stylist", "senior", true, "+15 min"),
|
||||
new("emp-3", "Louise Nielsen", "LN", "Senior Stylist", "senior", true, "Standard"),
|
||||
new("emp-4", "Katrine Pedersen", "KP", "Stylist", "junior", true, "+15 min"),
|
||||
new("emp-5", "Sofie Andersen", "SA", "Junior Stylist", "junior", false, "—", "Ikke certificeret")
|
||||
},
|
||||
Availability = new()
|
||||
{
|
||||
new("Mandag", true, "08:00", "18:00"),
|
||||
new("Tirsdag", true, "08:00", "18:00"),
|
||||
new("Onsdag", true, "08:00", "18:00"),
|
||||
new("Torsdag", true, "08:00", "12:00"),
|
||||
new("Fredag", true, "08:00", "18:00"),
|
||||
new("Lørdag", false, "08:00", "18:00"),
|
||||
new("Søndag", false, "08:00", "18:00")
|
||||
},
|
||||
// Tilvalg
|
||||
Addons = new()
|
||||
{
|
||||
new("addon-1", "Olaplex Behandling", "+250 kr", "+15 min", "Valgfri", true),
|
||||
new("addon-2", "Kerastase Hårkur", "+195 kr", "+10 min", "Valgfri", true),
|
||||
new("addon-3", "Styling / Curls", "+150 kr", "+20 min", "Valgfri", true),
|
||||
new("addon-4", "Hovedbundsmassage", "+75 kr", "+5 min", "Valgfri", true),
|
||||
new("addon-5", "Patch Test", "Gratis", "48t før", "Påkrævet (nye)", false, true)
|
||||
}
|
||||
},
|
||||
["service-2"] = new ServiceDetailRecord
|
||||
{
|
||||
|
|
@ -233,6 +261,13 @@ public record ServiceDetailRecord
|
|||
public bool AllowEmployeeSelection { get; init; } = true;
|
||||
public bool ShowPrice { get; init; } = true;
|
||||
public bool ShowDuration { get; init; } = true;
|
||||
|
||||
// Medarbejdere tab
|
||||
public List<ServiceEmployee> Employees { get; init; } = new();
|
||||
public List<ServiceAvailability> Availability { get; init; } = new();
|
||||
|
||||
// Tilvalg tab
|
||||
public List<ServiceAddon> Addons { get; init; } = new();
|
||||
}
|
||||
|
||||
public enum PriceMode { Simple, Matrix }
|
||||
|
|
@ -242,3 +277,28 @@ public record PriceMatrixRow(string Level, string ShortHair, string MediumHair,
|
|||
public record DurationVariant(string Name, int Minutes);
|
||||
|
||||
public record ServiceTag(string Text, string CssClass);
|
||||
|
||||
public record ServiceEmployee(
|
||||
string Id,
|
||||
string Name,
|
||||
string Initials,
|
||||
string Level,
|
||||
string LevelCssClass,
|
||||
bool Selected,
|
||||
string OverrideValue,
|
||||
string? Warning = null);
|
||||
|
||||
public record ServiceAddon(
|
||||
string Id,
|
||||
string Name,
|
||||
string Price,
|
||||
string Duration,
|
||||
string Type,
|
||||
bool Selected,
|
||||
bool Required = false);
|
||||
|
||||
public record ServiceAvailability(
|
||||
string Day,
|
||||
bool Enabled,
|
||||
string StartTime,
|
||||
string EndTime);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
@model PlanTempus.Application.Features.Services.Components.ServiceDetailEmployeesViewModel
|
||||
|
||||
<swp-detail-grid>
|
||||
<div>
|
||||
<swp-card>
|
||||
<swp-section-label>@Model.LabelEmployeesForService</swp-section-label>
|
||||
|
||||
<swp-selectable-list>
|
||||
@foreach (var employee in Model.Employees)
|
||||
{
|
||||
<swp-selectable-item class="@(employee.Selected ? "selected" : "") @(employee.Warning != null ? "disabled" : "")">
|
||||
<swp-selectable-checkbox>
|
||||
<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-selectable-checkbox>
|
||||
<swp-avatar class="@employee.LevelCssClass">@employee.Initials</swp-avatar>
|
||||
<swp-selectable-info>
|
||||
<swp-selectable-name>
|
||||
@employee.Name
|
||||
@if (employee.Warning != null)
|
||||
{
|
||||
<swp-selectable-warning>@employee.Warning</swp-selectable-warning>
|
||||
}
|
||||
</swp-selectable-name>
|
||||
<swp-tag class="@employee.LevelCssClass">@employee.Level</swp-tag>
|
||||
</swp-selectable-info>
|
||||
<swp-selectable-override>
|
||||
@Model.LabelDuration: <swp-selectable-override-value>@employee.OverrideValue</swp-selectable-override-value>
|
||||
</swp-selectable-override>
|
||||
</swp-selectable-item>
|
||||
}
|
||||
</swp-selectable-list>
|
||||
|
||||
<swp-see-all>@Model.LabelSelectAll</swp-see-all>
|
||||
</swp-card>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<swp-card>
|
||||
<swp-section-label>@Model.LabelAvailability</swp-section-label>
|
||||
|
||||
<swp-availability-list>
|
||||
@foreach (var day in Model.Availability)
|
||||
{
|
||||
<swp-availability-row data-enabled="@(day.Enabled ? "true" : "false")">
|
||||
<swp-availability-day>@day.Day</swp-availability-day>
|
||||
<swp-toggle-slider data-value="@(day.Enabled ? "yes" : "no")">
|
||||
<swp-toggle-option>@Model.ToggleYes</swp-toggle-option>
|
||||
<swp-toggle-option>@Model.ToggleNo</swp-toggle-option>
|
||||
</swp-toggle-slider>
|
||||
<swp-availability-time>
|
||||
<swp-time-range>
|
||||
<swp-time-range-slider>
|
||||
<swp-time-range-track></swp-time-range-track>
|
||||
<swp-time-range-fill></swp-time-range-fill>
|
||||
<input type="range" class="range-start" min="0" max="60" value="8" step="1" @(day.Enabled ? "" : "disabled")>
|
||||
<input type="range" class="range-end" min="0" max="60" value="48" step="1" @(day.Enabled ? "" : "disabled")>
|
||||
</swp-time-range-slider>
|
||||
<swp-time-range-label>@day.StartTime – @day.EndTime</swp-time-range-label>
|
||||
</swp-time-range>
|
||||
</swp-availability-time>
|
||||
</swp-availability-row>
|
||||
}
|
||||
</swp-availability-list>
|
||||
</swp-card>
|
||||
</div>
|
||||
</swp-detail-grid>
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using PlanTempus.Application.Features.Localization.Services;
|
||||
|
||||
namespace PlanTempus.Application.Features.Services.Components;
|
||||
|
||||
public class ServiceDetailEmployeesViewComponent : ViewComponent
|
||||
{
|
||||
private readonly ILocalizationService _localization;
|
||||
|
||||
public ServiceDetailEmployeesViewComponent(ILocalizationService localization)
|
||||
{
|
||||
_localization = localization;
|
||||
}
|
||||
|
||||
public IViewComponentResult Invoke(string key)
|
||||
{
|
||||
var service = ServiceDetailCatalog.Get(key);
|
||||
|
||||
var model = new ServiceDetailEmployeesViewModel
|
||||
{
|
||||
// Data
|
||||
Employees = service.Employees,
|
||||
Availability = service.Availability,
|
||||
|
||||
// Labels
|
||||
LabelEmployeesForService = _localization.Get("services.detail.employees.employeesForService"),
|
||||
LabelSelectAll = _localization.Get("services.detail.employees.selectAll"),
|
||||
LabelAvailability = _localization.Get("services.detail.employees.availability"),
|
||||
LabelDuration = _localization.Get("services.detail.employees.duration"),
|
||||
ToggleYes = _localization.Get("common.yes"),
|
||||
ToggleNo = _localization.Get("common.no")
|
||||
};
|
||||
|
||||
return View(model);
|
||||
}
|
||||
}
|
||||
|
||||
public class ServiceDetailEmployeesViewModel
|
||||
{
|
||||
// Data
|
||||
public required List<ServiceEmployee> Employees { get; init; }
|
||||
public required List<ServiceAvailability> Availability { get; init; }
|
||||
|
||||
// Labels
|
||||
public required string LabelEmployeesForService { get; init; }
|
||||
public required string LabelSelectAll { get; init; }
|
||||
public required string LabelAvailability { get; init; }
|
||||
public required string LabelDuration { get; init; }
|
||||
|
||||
// Toggle labels
|
||||
public required string ToggleYes { get; init; }
|
||||
public required string ToggleNo { get; init; }
|
||||
}
|
||||
|
|
@ -56,19 +56,13 @@
|
|||
|
||||
<swp-tab-content data-tab="employees">
|
||||
<swp-page-container>
|
||||
<swp-card>
|
||||
<swp-section-label>Medarbejdere</swp-section-label>
|
||||
<p style="color: var(--color-text-secondary);">Medarbejdere-tab kommer snart...</p>
|
||||
</swp-card>
|
||||
@await Component.InvokeAsync("ServiceDetailEmployees", Model.ServiceKey)
|
||||
</swp-page-container>
|
||||
</swp-tab-content>
|
||||
|
||||
<swp-tab-content data-tab="addons">
|
||||
<swp-page-container>
|
||||
<swp-card>
|
||||
<swp-section-label>Tilvalg</swp-section-label>
|
||||
<p style="color: var(--color-text-secondary);">Tilvalg-tab kommer snart...</p>
|
||||
</swp-card>
|
||||
@await Component.InvokeAsync("ServiceDetailAddons", Model.ServiceKey)
|
||||
</swp-page-container>
|
||||
</swp-tab-content>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue