WIP
This commit is contained in:
parent
54b057886c
commit
7fc1ae0650
204 changed files with 4345 additions and 134 deletions
94
PlanTempus.Application/Features/Dashboard/Pages/Index.cshtml
Normal file
94
PlanTempus.Application/Features/Dashboard/Pages/Index.cshtml
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
@page "/"
|
||||
@using PlanTempus.Application.Features.Dashboard.Pages
|
||||
@model PlanTempus.Application.Features.Dashboard.Pages.IndexModel
|
||||
@{
|
||||
ViewData["Title"] = "Dashboard";
|
||||
}
|
||||
|
||||
<swp-page-container>
|
||||
<!-- Stats Bar -->
|
||||
<swp-stats-bar>
|
||||
<swp-stat-card class="highlight">
|
||||
<swp-stat-value>12</swp-stat-value>
|
||||
<swp-stat-label>Bookinger i dag</swp-stat-label>
|
||||
<swp-stat-trend class="up">
|
||||
<i class="ph ph-check-circle"></i>
|
||||
4 gennemført, 2 i gang
|
||||
</swp-stat-trend>
|
||||
</swp-stat-card>
|
||||
<swp-stat-card class="success">
|
||||
<swp-stat-value>8.450 kr</swp-stat-value>
|
||||
<swp-stat-label>Forventet omsætning</swp-stat-label>
|
||||
<swp-stat-trend class="up">
|
||||
<i class="ph ph-trend-up"></i>
|
||||
+12% vs. gennemsnit
|
||||
</swp-stat-trend>
|
||||
</swp-stat-card>
|
||||
<swp-stat-card>
|
||||
<swp-stat-value>78%</swp-stat-value>
|
||||
<swp-stat-label>Belægningsgrad</swp-stat-label>
|
||||
<swp-stat-trend class="up">
|
||||
<i class="ph ph-trend-up"></i>
|
||||
God kapacitet
|
||||
</swp-stat-trend>
|
||||
</swp-stat-card>
|
||||
<swp-stat-card class="warning">
|
||||
<swp-stat-value>4</swp-stat-value>
|
||||
<swp-stat-label>Kræver opmærksomhed</swp-stat-label>
|
||||
</swp-stat-card>
|
||||
</swp-stats-bar>
|
||||
|
||||
<!-- Dashboard Content -->
|
||||
<swp-dashboard-grid>
|
||||
<swp-main-column>
|
||||
<!-- AI Insight -->
|
||||
<swp-card>
|
||||
<swp-ai-insight>
|
||||
<swp-ai-header>
|
||||
<i class="ph ph-sparkle"></i>
|
||||
<span>AI Analyse</span>
|
||||
</swp-ai-header>
|
||||
<swp-ai-text>
|
||||
<strong>Godt i gang!</strong> 4 af 12 bookinger er gennemført. 2 er i gang nu, og 6 venter.
|
||||
Forventet omsætning: <strong>8.450 kr</strong> – allerede realiseret <strong>2.150 kr</strong>.
|
||||
</swp-ai-text>
|
||||
</swp-ai-insight>
|
||||
</swp-card>
|
||||
|
||||
<!-- Today's Bookings Preview -->
|
||||
<swp-card>
|
||||
<swp-card-header>
|
||||
<swp-card-title>
|
||||
<i class="ph ph-calendar-check"></i>
|
||||
Dagens bookinger
|
||||
</swp-card-title>
|
||||
<swp-card-action>Se alle</swp-card-action>
|
||||
</swp-card-header>
|
||||
<swp-card-content>
|
||||
<p>Booking oversigt kommer her...</p>
|
||||
</swp-card-content>
|
||||
</swp-card>
|
||||
</swp-main-column>
|
||||
|
||||
<swp-side-column>
|
||||
<!-- Quick Actions -->
|
||||
<swp-card>
|
||||
<swp-card-header>
|
||||
<swp-card-title>Hurtige handlinger</swp-card-title>
|
||||
</swp-card-header>
|
||||
<swp-card-content>
|
||||
<swp-quick-actions>
|
||||
<swp-quick-action-btn>
|
||||
<i class="ph ph-plus"></i>
|
||||
Ny booking
|
||||
</swp-quick-action-btn>
|
||||
<swp-quick-action-btn>
|
||||
<i class="ph ph-user-plus"></i>
|
||||
Ny kunde
|
||||
</swp-quick-action-btn>
|
||||
</swp-quick-actions>
|
||||
</swp-card-content>
|
||||
</swp-card>
|
||||
</swp-side-column>
|
||||
</swp-dashboard-grid>
|
||||
</swp-page-container>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace PlanTempus.Application.Features.Dashboard.Pages;
|
||||
|
||||
public class IndexModel : PageModel
|
||||
{
|
||||
public void OnGet()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
namespace PlanTempus.Application.Features.Localization.Models;
|
||||
|
||||
public class SupportedCulture
|
||||
{
|
||||
public required string Code { get; set; }
|
||||
public required string Name { get; set; }
|
||||
public required string NativeName { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
using PlanTempus.Application.Features.Localization.Models;
|
||||
|
||||
namespace PlanTempus.Application.Features.Localization.Services;
|
||||
|
||||
public interface ILocalizationService
|
||||
{
|
||||
string Get(string key, string? culture = null);
|
||||
string CurrentCulture { get; }
|
||||
IEnumerable<SupportedCulture> GetSupportedCultures();
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
using System.Text.Json;
|
||||
using PlanTempus.Application.Features.Localization.Models;
|
||||
|
||||
namespace PlanTempus.Application.Features.Localization.Services;
|
||||
|
||||
public class JsonLocalizationService : ILocalizationService
|
||||
{
|
||||
private readonly string _translationsPath;
|
||||
|
||||
public JsonLocalizationService(IWebHostEnvironment env)
|
||||
{
|
||||
_translationsPath = Path.Combine(env.ContentRootPath, "Features", "Localization", "Translations");
|
||||
}
|
||||
|
||||
public string CurrentCulture => "da";
|
||||
|
||||
public string Get(string key, string? culture = null)
|
||||
{
|
||||
culture ??= CurrentCulture;
|
||||
var filePath = Path.Combine(_translationsPath, $"{culture}.json");
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
return key;
|
||||
|
||||
var json = File.ReadAllText(filePath);
|
||||
var doc = JsonDocument.Parse(json);
|
||||
|
||||
var parts = key.Split('.');
|
||||
JsonElement current = doc.RootElement;
|
||||
|
||||
foreach (var part in parts)
|
||||
if (!current.TryGetProperty(part, out current))
|
||||
return key;
|
||||
|
||||
return current.GetString() ?? key;
|
||||
}
|
||||
|
||||
public IEnumerable<SupportedCulture> GetSupportedCultures()
|
||||
{
|
||||
return new List<SupportedCulture>
|
||||
{
|
||||
new() { Code = "da", Name = "Danish", NativeName = "Dansk" },
|
||||
new() { Code = "en", Name = "English", NativeName = "English" }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"menu": {
|
||||
"home": "Dashboard",
|
||||
"calendar": "Kalender",
|
||||
"pos": "Kasse",
|
||||
"products": "Produkter & Lager",
|
||||
"suppliers": "Leverandører",
|
||||
"customers": "Kunder",
|
||||
"employees": "Medarbejdere",
|
||||
"reports": "Statistik & Rapporter",
|
||||
"settings": "Indstillinger",
|
||||
"account": "Abonnement & Konto"
|
||||
},
|
||||
"groups": {
|
||||
"dashboard": "Dashboard",
|
||||
"data": "Data",
|
||||
"analytics": "Analyse",
|
||||
"system": "System"
|
||||
},
|
||||
"common": {
|
||||
"save": "Gem",
|
||||
"cancel": "Annuller",
|
||||
"search": "Søg",
|
||||
"close": "Luk",
|
||||
"delete": "Slet",
|
||||
"edit": "Rediger",
|
||||
"add": "Tilføj"
|
||||
},
|
||||
"sidebar": {
|
||||
"lockScreen": "Lås skærm",
|
||||
"appName": "Salon OS"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"menu": {
|
||||
"home": "Dashboard",
|
||||
"calendar": "Calendar",
|
||||
"pos": "Point of Sale",
|
||||
"products": "Products & Inventory",
|
||||
"suppliers": "Suppliers",
|
||||
"customers": "Customers",
|
||||
"employees": "Employees",
|
||||
"reports": "Statistics & Reports",
|
||||
"settings": "Settings",
|
||||
"account": "Subscription & Account"
|
||||
},
|
||||
"groups": {
|
||||
"dashboard": "Dashboard",
|
||||
"data": "Data",
|
||||
"analytics": "Analytics",
|
||||
"system": "System"
|
||||
},
|
||||
"common": {
|
||||
"save": "Save",
|
||||
"cancel": "Cancel",
|
||||
"search": "Search",
|
||||
"close": "Close",
|
||||
"delete": "Delete",
|
||||
"edit": "Edit",
|
||||
"add": "Add"
|
||||
},
|
||||
"sidebar": {
|
||||
"lockScreen": "Lock screen",
|
||||
"appName": "Salon OS"
|
||||
}
|
||||
}
|
||||
12
PlanTempus.Application/Features/Menu/Models/MenuGroup.cs
Normal file
12
PlanTempus.Application/Features/Menu/Models/MenuGroup.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
namespace PlanTempus.Application.Features.Menu.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a group of menu items (e.g., "Dashboard", "Data", "System").
|
||||
/// </summary>
|
||||
public class MenuGroup
|
||||
{
|
||||
public required string Id { get; set; }
|
||||
public required string Label { get; set; }
|
||||
public int SortOrder { get; set; }
|
||||
public List<MenuItem> Items { get; set; } = new();
|
||||
}
|
||||
15
PlanTempus.Application/Features/Menu/Models/MenuItem.cs
Normal file
15
PlanTempus.Application/Features/Menu/Models/MenuItem.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
namespace PlanTempus.Application.Features.Menu.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a single menu item in the sidebar.
|
||||
/// </summary>
|
||||
public class MenuItem
|
||||
{
|
||||
public required string Id { get; set; }
|
||||
public required string Label { get; set; }
|
||||
public required string Icon { get; set; }
|
||||
public required string Url { get; set; }
|
||||
public UserRole MinimumRole { get; set; } = UserRole.Staff;
|
||||
public int SortOrder { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
}
|
||||
11
PlanTempus.Application/Features/Menu/Models/UserRole.cs
Normal file
11
PlanTempus.Application/Features/Menu/Models/UserRole.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
namespace PlanTempus.Application.Features.Menu.Models;
|
||||
|
||||
/// <summary>
|
||||
/// User roles for menu visibility. Higher value = more access.
|
||||
/// </summary>
|
||||
public enum UserRole
|
||||
{
|
||||
Staff = 0,
|
||||
Manager = 1,
|
||||
Admin = 2
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
using PlanTempus.Application.Features.Menu.Models;
|
||||
|
||||
namespace PlanTempus.Application.Features.Menu.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service for retrieving menu structure based on user role.
|
||||
/// </summary>
|
||||
public interface IMenuService
|
||||
{
|
||||
/// <summary>
|
||||
/// Get menu groups filtered by user role.
|
||||
/// </summary>
|
||||
List<MenuGroup> GetMenuForRole(UserRole role, string? currentUrl = null);
|
||||
}
|
||||
187
PlanTempus.Application/Features/Menu/Services/MockMenuService.cs
Normal file
187
PlanTempus.Application/Features/Menu/Services/MockMenuService.cs
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
using PlanTempus.Application.Features.Localization.Services;
|
||||
using PlanTempus.Application.Features.Menu.Models;
|
||||
|
||||
namespace PlanTempus.Application.Features.Menu.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Mock implementation of IMenuService with hardcoded menu data.
|
||||
/// </summary>
|
||||
public class MockMenuService : IMenuService
|
||||
{
|
||||
private readonly ILocalizationService _localize;
|
||||
|
||||
public MockMenuService(ILocalizationService localize)
|
||||
{
|
||||
_localize = localize;
|
||||
}
|
||||
|
||||
public List<MenuGroup> GetMenuForRole(UserRole role, string? currentUrl = null)
|
||||
{
|
||||
var allGroups = GetAllMenuGroups();
|
||||
|
||||
return allGroups
|
||||
.Select(g => new MenuGroup
|
||||
{
|
||||
Id = g.Id,
|
||||
Label = _localize.Get($"groups.{g.Id}"),
|
||||
SortOrder = g.SortOrder,
|
||||
Items = g.Items
|
||||
.Where(i => role >= i.MinimumRole)
|
||||
.Select(i => new MenuItem
|
||||
{
|
||||
Id = i.Id,
|
||||
Label = _localize.Get($"menu.{i.Id}"),
|
||||
Icon = i.Icon,
|
||||
Url = i.Url,
|
||||
MinimumRole = i.MinimumRole,
|
||||
SortOrder = i.SortOrder,
|
||||
IsActive = currentUrl != null && i.Url.Equals(currentUrl, StringComparison.OrdinalIgnoreCase)
|
||||
})
|
||||
.OrderBy(i => i.SortOrder)
|
||||
.ToList()
|
||||
})
|
||||
.Where(g => g.Items.Any())
|
||||
.OrderBy(g => g.SortOrder)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static List<MenuGroup> GetAllMenuGroups()
|
||||
{
|
||||
return new List<MenuGroup>
|
||||
{
|
||||
// DASHBOARD GROUP
|
||||
new MenuGroup
|
||||
{
|
||||
Id = "dashboard",
|
||||
Label = "Dashboard",
|
||||
SortOrder = 1,
|
||||
Items = new List<MenuItem>
|
||||
{
|
||||
new MenuItem
|
||||
{
|
||||
Id = "home",
|
||||
Label = "Dashboard",
|
||||
Icon = "ph-squares-four",
|
||||
Url = "/",
|
||||
MinimumRole = UserRole.Staff,
|
||||
SortOrder = 1
|
||||
},
|
||||
new MenuItem
|
||||
{
|
||||
Id = "calendar",
|
||||
Label = "Kalender",
|
||||
Icon = "ph-calendar",
|
||||
Url = "/poc-calendar.html",
|
||||
MinimumRole = UserRole.Staff,
|
||||
SortOrder = 2
|
||||
},
|
||||
new MenuItem
|
||||
{
|
||||
Id = "pos",
|
||||
Label = "Kasse",
|
||||
Icon = "ph-device-mobile",
|
||||
Url = "/pos",
|
||||
MinimumRole = UserRole.Staff,
|
||||
SortOrder = 3
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// DATA GROUP
|
||||
new MenuGroup
|
||||
{
|
||||
Id = "data",
|
||||
Label = "Data",
|
||||
SortOrder = 2,
|
||||
Items = new List<MenuItem>
|
||||
{
|
||||
new MenuItem
|
||||
{
|
||||
Id = "products",
|
||||
Label = "Produkter & Lager",
|
||||
Icon = "ph-package",
|
||||
Url = "/poc-produkter.html",
|
||||
MinimumRole = UserRole.Manager,
|
||||
SortOrder = 1
|
||||
},
|
||||
new MenuItem
|
||||
{
|
||||
Id = "suppliers",
|
||||
Label = "Leverandører",
|
||||
Icon = "ph-truck",
|
||||
Url = "/poc-leverandoerer.html",
|
||||
MinimumRole = UserRole.Manager,
|
||||
SortOrder = 2
|
||||
},
|
||||
new MenuItem
|
||||
{
|
||||
Id = "customers",
|
||||
Label = "Kunder",
|
||||
Icon = "ph-users",
|
||||
Url = "/customers",
|
||||
MinimumRole = UserRole.Staff,
|
||||
SortOrder = 3
|
||||
},
|
||||
new MenuItem
|
||||
{
|
||||
Id = "employees",
|
||||
Label = "Medarbejdere",
|
||||
Icon = "ph-user",
|
||||
Url = "/poc-medarbejdere.html",
|
||||
MinimumRole = UserRole.Manager,
|
||||
SortOrder = 4
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// ANALYSE GROUP
|
||||
new MenuGroup
|
||||
{
|
||||
Id = "analytics",
|
||||
Label = "Analyse",
|
||||
SortOrder = 3,
|
||||
Items = new List<MenuItem>
|
||||
{
|
||||
new MenuItem
|
||||
{
|
||||
Id = "reports",
|
||||
Label = "Statistik & Rapporter",
|
||||
Icon = "ph-chart-bar",
|
||||
Url = "/reports",
|
||||
MinimumRole = UserRole.Manager,
|
||||
SortOrder = 1
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// SYSTEM GROUP
|
||||
new MenuGroup
|
||||
{
|
||||
Id = "system",
|
||||
Label = "System",
|
||||
SortOrder = 4,
|
||||
Items = new List<MenuItem>
|
||||
{
|
||||
new MenuItem
|
||||
{
|
||||
Id = "settings",
|
||||
Label = "Indstillinger",
|
||||
Icon = "ph-gear",
|
||||
Url = "/poc-indstillinger.html",
|
||||
MinimumRole = UserRole.Admin,
|
||||
SortOrder = 1
|
||||
},
|
||||
new MenuItem
|
||||
{
|
||||
Id = "account",
|
||||
Label = "Abonnement & Konto",
|
||||
Icon = "ph-credit-card",
|
||||
Url = "/poc-konto.html",
|
||||
MinimumRole = UserRole.Admin,
|
||||
SortOrder = 2
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using PlanTempus.Application.Features.Menu.Services;
|
||||
using PlanTempus.Application.Features.Menu;
|
||||
using PlanTempus.Application.Features.Menu.Models;
|
||||
|
||||
namespace PlanTempus.Application.Features.Menu;
|
||||
|
||||
/// <summary>
|
||||
/// ViewComponent for rendering the side menu based on user role.
|
||||
/// </summary>
|
||||
public class SideMenuViewComponent : ViewComponent
|
||||
{
|
||||
private readonly IMenuService _menuService;
|
||||
|
||||
public SideMenuViewComponent(IMenuService menuService)
|
||||
{
|
||||
_menuService = menuService;
|
||||
}
|
||||
|
||||
public IViewComponentResult Invoke(UserRole? role = null)
|
||||
{
|
||||
// Default to Admin for demo (in real app, get from auth)
|
||||
var userRole = role ?? UserRole.Admin;
|
||||
|
||||
var currentUrl = HttpContext.Request.Path.Value;
|
||||
var groups = _menuService.GetMenuForRole(userRole, currentUrl);
|
||||
|
||||
var viewModel = new SideMenuViewModel
|
||||
{
|
||||
Groups = groups,
|
||||
CurrentUserRole = userRole
|
||||
};
|
||||
|
||||
return View(viewModel);
|
||||
}
|
||||
}
|
||||
12
PlanTempus.Application/Features/Menu/SideMenuViewModel.cs
Normal file
12
PlanTempus.Application/Features/Menu/SideMenuViewModel.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
using PlanTempus.Application.Features.Menu.Models;
|
||||
|
||||
namespace PlanTempus.Application.Features.Menu;
|
||||
|
||||
/// <summary>
|
||||
/// ViewModel for the side menu partial view.
|
||||
/// </summary>
|
||||
public class SideMenuViewModel
|
||||
{
|
||||
public required List<MenuGroup> Groups { get; set; }
|
||||
public UserRole CurrentUserRole { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
@using PlanTempus.Application.Features.Menu
|
||||
@model SideMenuViewModel
|
||||
|
||||
<swp-side-menu>
|
||||
<swp-side-menu-header>
|
||||
<i class="ph ph-squares-four"></i>
|
||||
<swp-side-menu-logo localize="sidebar.appName">Salon OS</swp-side-menu-logo>
|
||||
<swp-menu-toggle id="menuToggle">
|
||||
<i class="ph ph-caret-left"></i>
|
||||
</swp-menu-toggle>
|
||||
</swp-side-menu-header>
|
||||
|
||||
<swp-side-menu-nav>
|
||||
@foreach (var group in Model.Groups)
|
||||
{
|
||||
<swp-side-menu-group>
|
||||
<swp-side-menu-label>@group.Label</swp-side-menu-label>
|
||||
@foreach (var item in group.Items)
|
||||
{
|
||||
<a href="@item.Url" is="swp-side-menu-item"
|
||||
data-active="@(item.IsActive ? "true" : "false")"
|
||||
data-tooltip="@item.Label">
|
||||
<i class="ph @item.Icon"></i>
|
||||
<span>@item.Label</span>
|
||||
</a>
|
||||
}
|
||||
</swp-side-menu-group>
|
||||
}
|
||||
</swp-side-menu-nav>
|
||||
|
||||
<swp-side-menu-footer>
|
||||
<swp-side-menu-action class="lock" id="lockScreen" title="Lås skærm">
|
||||
<i class="ph ph-lock"></i>
|
||||
<span localize="sidebar.lockScreen">Lås skærm</span>
|
||||
</swp-side-menu-action>
|
||||
</swp-side-menu-footer>
|
||||
</swp-side-menu>
|
||||
|
||||
<!-- Tooltip for collapsed menu -->
|
||||
<span id="menuTooltip" class="swp-menu-tooltip" popover="manual"></span>
|
||||
49
PlanTempus.Application/Features/Shared/_ProfileDrawer.cshtml
Normal file
49
PlanTempus.Application/Features/Shared/_ProfileDrawer.cshtml
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<swp-profile-drawer id="profileDrawer">
|
||||
<swp-drawer-header>
|
||||
<swp-drawer-title>Profil</swp-drawer-title>
|
||||
<swp-drawer-close id="closeProfileDrawer">
|
||||
<i class="ph ph-x"></i>
|
||||
</swp-drawer-close>
|
||||
</swp-drawer-header>
|
||||
|
||||
<swp-drawer-content>
|
||||
<swp-profile-section>
|
||||
<swp-profile-avatar-large>MJ</swp-profile-avatar-large>
|
||||
<swp-profile-name-large>Maria Jensen</swp-profile-name-large>
|
||||
<swp-profile-email>maria@salon.dk</swp-profile-email>
|
||||
</swp-profile-section>
|
||||
|
||||
<swp-drawer-divider></swp-drawer-divider>
|
||||
|
||||
<swp-drawer-menu>
|
||||
<swp-drawer-menu-item>
|
||||
<i class="ph ph-user"></i>
|
||||
<span>Min profil</span>
|
||||
</swp-drawer-menu-item>
|
||||
<swp-drawer-menu-item>
|
||||
<i class="ph ph-gear"></i>
|
||||
<span>Indstillinger</span>
|
||||
</swp-drawer-menu-item>
|
||||
</swp-drawer-menu>
|
||||
|
||||
<swp-drawer-divider></swp-drawer-divider>
|
||||
|
||||
<swp-theme-toggle>
|
||||
<swp-theme-label>
|
||||
<i class="ph ph-moon"></i>
|
||||
<span>Mørk tilstand</span>
|
||||
</swp-theme-label>
|
||||
<swp-toggle-switch id="themeToggle">
|
||||
<input type="checkbox" id="themeCheckbox">
|
||||
<swp-toggle-slider></swp-toggle-slider>
|
||||
</swp-toggle-switch>
|
||||
</swp-theme-toggle>
|
||||
</swp-drawer-content>
|
||||
|
||||
<swp-drawer-footer>
|
||||
<swp-drawer-action class="logout" id="logoutBtn">
|
||||
<i class="ph ph-sign-out"></i>
|
||||
<span>Log ud</span>
|
||||
</swp-drawer-action>
|
||||
</swp-drawer-footer>
|
||||
</swp-profile-drawer>
|
||||
26
PlanTempus.Application/Features/Shared/_TopBar.cshtml
Normal file
26
PlanTempus.Application/Features/Shared/_TopBar.cshtml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<swp-app-topbar>
|
||||
<swp-topbar-search>
|
||||
<i class="ph ph-magnifying-glass"></i>
|
||||
<input type="text" placeholder="Søg i Salon OS..." id="globalSearch">
|
||||
<kbd>⌘K</kbd>
|
||||
</swp-topbar-search>
|
||||
|
||||
<swp-topbar-actions>
|
||||
<!-- Notifications -->
|
||||
<swp-topbar-btn id="notificationsBtn" title="Notifikationer">
|
||||
<i class="ph ph-bell"></i>
|
||||
<swp-notification-badge>3</swp-notification-badge>
|
||||
</swp-topbar-btn>
|
||||
|
||||
<swp-topbar-divider></swp-topbar-divider>
|
||||
|
||||
<!-- Profile (opens drawer) -->
|
||||
<swp-topbar-profile id="profileTrigger">
|
||||
<swp-profile-avatar>MJ</swp-profile-avatar>
|
||||
<swp-profile-info>
|
||||
<swp-profile-name>Maria Jensen</swp-profile-name>
|
||||
<swp-profile-role>Administrator</swp-profile-role>
|
||||
</swp-profile-info>
|
||||
</swp-topbar-profile>
|
||||
</swp-topbar-actions>
|
||||
</swp-app-topbar>
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
@model PlanTempus.Application.Features.Menu.SideMenuViewModel
|
||||
|
||||
<swp-side-menu>
|
||||
<swp-side-menu-header>
|
||||
<i class="ph ph-squares-four"></i>
|
||||
<swp-side-menu-logo localize="sidebar.appName">Salon OS</swp-side-menu-logo>
|
||||
<swp-menu-toggle id="menuToggle">
|
||||
<i class="ph ph-caret-left"></i>
|
||||
</swp-menu-toggle>
|
||||
</swp-side-menu-header>
|
||||
|
||||
<swp-side-menu-nav>
|
||||
@foreach (var group in Model.Groups)
|
||||
{
|
||||
<swp-side-menu-group>
|
||||
<swp-side-menu-label>@group.Label</swp-side-menu-label>
|
||||
@foreach (var item in group.Items)
|
||||
{
|
||||
<a href="@item.Url" is="swp-side-menu-item"
|
||||
data-active="@(item.IsActive ? "true" : "false")"
|
||||
data-tooltip="@item.Label">
|
||||
<i class="ph @item.Icon"></i>
|
||||
<span>@item.Label</span>
|
||||
</a>
|
||||
}
|
||||
</swp-side-menu-group>
|
||||
}
|
||||
</swp-side-menu-nav>
|
||||
|
||||
<swp-side-menu-footer>
|
||||
<swp-side-menu-action class="lock" id="lockScreen" title="Lås skærm">
|
||||
<i class="ph ph-lock"></i>
|
||||
<span localize="sidebar.lockScreen">Lås skærm</span>
|
||||
</swp-side-menu-action>
|
||||
</swp-side-menu-footer>
|
||||
</swp-side-menu>
|
||||
|
||||
<!-- Tooltip for collapsed menu -->
|
||||
<span id="menuTooltip" class="swp-menu-tooltip" popover="manual"></span>
|
||||
38
PlanTempus.Application/Features/_Shared/Pages/_Layout.cshtml
Normal file
38
PlanTempus.Application/Features/_Shared/Pages/_Layout.cshtml
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="da">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>@ViewData["Title"] - Salon OS</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@@phosphor-icons/web@@2.1.2/src/regular/style.css" />
|
||||
<!-- Design System -->
|
||||
<link rel="stylesheet" href="~/css/design-system.css">
|
||||
<link rel="stylesheet" href="~/css/base.css">
|
||||
<!-- Layout Components -->
|
||||
<link rel="stylesheet" href="~/css/app-layout.css">
|
||||
<link rel="stylesheet" href="~/css/sidebar.css">
|
||||
<link rel="stylesheet" href="~/css/topbar.css">
|
||||
<link rel="stylesheet" href="~/css/drawers.css">
|
||||
<!-- Page Components -->
|
||||
<link rel="stylesheet" href="~/css/page.css">
|
||||
<link rel="stylesheet" href="~/css/stats.css">
|
||||
@await RenderSectionAsync("Styles", required: false)
|
||||
</head>
|
||||
<body>
|
||||
<swp-app-layout id="appLayout">
|
||||
@await Component.InvokeAsync("SideMenu")
|
||||
<partial name="_TopBar" />
|
||||
|
||||
<swp-main-content>
|
||||
@RenderBody()
|
||||
</swp-main-content>
|
||||
</swp-app-layout>
|
||||
|
||||
<partial name="_ProfileDrawer" />
|
||||
<swp-drawer-overlay id="drawerOverlay"></swp-drawer-overlay>
|
||||
|
||||
<script type="module" src="~/js/app.js"></script>
|
||||
@await RenderSectionAsync("Scripts", required: false)
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<swp-profile-drawer id="profileDrawer">
|
||||
<swp-drawer-header>
|
||||
<swp-drawer-title>Profil</swp-drawer-title>
|
||||
<swp-drawer-close id="closeProfileDrawer">
|
||||
<i class="ph ph-x"></i>
|
||||
</swp-drawer-close>
|
||||
</swp-drawer-header>
|
||||
|
||||
<swp-drawer-content>
|
||||
<swp-profile-section>
|
||||
<swp-profile-avatar-large>MJ</swp-profile-avatar-large>
|
||||
<swp-profile-name-large>Maria Jensen</swp-profile-name-large>
|
||||
<swp-profile-email>maria@salon.dk</swp-profile-email>
|
||||
</swp-profile-section>
|
||||
|
||||
<swp-drawer-divider></swp-drawer-divider>
|
||||
|
||||
<swp-drawer-menu>
|
||||
<swp-drawer-menu-item>
|
||||
<i class="ph ph-user"></i>
|
||||
<span>Min profil</span>
|
||||
</swp-drawer-menu-item>
|
||||
<swp-drawer-menu-item>
|
||||
<i class="ph ph-gear"></i>
|
||||
<span>Indstillinger</span>
|
||||
</swp-drawer-menu-item>
|
||||
</swp-drawer-menu>
|
||||
|
||||
<swp-drawer-divider></swp-drawer-divider>
|
||||
|
||||
<swp-theme-toggle>
|
||||
<swp-theme-label>
|
||||
<i class="ph ph-moon"></i>
|
||||
<span>Mørk tilstand</span>
|
||||
</swp-theme-label>
|
||||
<swp-toggle-switch id="themeToggle">
|
||||
<input type="checkbox" id="themeCheckbox">
|
||||
<swp-toggle-slider></swp-toggle-slider>
|
||||
</swp-toggle-switch>
|
||||
</swp-theme-toggle>
|
||||
</swp-drawer-content>
|
||||
|
||||
<swp-drawer-footer>
|
||||
<swp-drawer-action class="logout" id="logoutBtn">
|
||||
<i class="ph ph-sign-out"></i>
|
||||
<span>Log ud</span>
|
||||
</swp-drawer-action>
|
||||
</swp-drawer-footer>
|
||||
</swp-profile-drawer>
|
||||
26
PlanTempus.Application/Features/_Shared/Pages/_TopBar.cshtml
Normal file
26
PlanTempus.Application/Features/_Shared/Pages/_TopBar.cshtml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<swp-app-topbar>
|
||||
<swp-topbar-search>
|
||||
<i class="ph ph-magnifying-glass"></i>
|
||||
<input type="text" placeholder="Søg i Salon OS..." id="globalSearch">
|
||||
<kbd>⌘K</kbd>
|
||||
</swp-topbar-search>
|
||||
|
||||
<swp-topbar-actions>
|
||||
<!-- Notifications -->
|
||||
<swp-topbar-btn id="notificationsBtn" title="Notifikationer">
|
||||
<i class="ph ph-bell"></i>
|
||||
<swp-notification-badge>3</swp-notification-badge>
|
||||
</swp-topbar-btn>
|
||||
|
||||
<swp-topbar-divider></swp-topbar-divider>
|
||||
|
||||
<!-- Profile (opens drawer) -->
|
||||
<swp-topbar-profile id="profileTrigger">
|
||||
<swp-profile-avatar>MJ</swp-profile-avatar>
|
||||
<swp-profile-info>
|
||||
<swp-profile-name>Maria Jensen</swp-profile-name>
|
||||
<swp-profile-role>Administrator</swp-profile-role>
|
||||
</swp-profile-info>
|
||||
</swp-topbar-profile>
|
||||
</swp-topbar-actions>
|
||||
</swp-app-topbar>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
@using PlanTempus.Application
|
||||
@namespace PlanTempus.Application.Features
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@addTagHelper *, PlanTempus.Application
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
@{
|
||||
Layout = "/Features/_Shared/Pages/_Layout.cshtml";
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
using PlanTempus.Application.Features.Localization.Services;
|
||||
|
||||
namespace PlanTempus.Application.Features._Shared.TagHelpers;
|
||||
|
||||
[HtmlTargetElement(Attributes = "localize")]
|
||||
public class LocalizeTagHelper : TagHelper
|
||||
{
|
||||
private readonly ILocalizationService _localize;
|
||||
|
||||
public LocalizeTagHelper(ILocalizationService localize)
|
||||
{
|
||||
_localize = localize;
|
||||
}
|
||||
|
||||
[HtmlAttributeName("localize")]
|
||||
public string Key { get; set; } = string.Empty;
|
||||
|
||||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
var translated = _localize.Get(Key);
|
||||
|
||||
if (!string.IsNullOrEmpty(translated) && translated != Key)
|
||||
output.Content.SetContent(translated);
|
||||
|
||||
output.Attributes.RemoveAll("localize");
|
||||
}
|
||||
}
|
||||
4
PlanTempus.Application/Features/_ViewImports.cshtml
Normal file
4
PlanTempus.Application/Features/_ViewImports.cshtml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
@using PlanTempus.Application
|
||||
@namespace PlanTempus.Application.Features
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@addTagHelper *, PlanTempus.Application
|
||||
3
PlanTempus.Application/Features/_ViewStart.cshtml
Normal file
3
PlanTempus.Application/Features/_ViewStart.cshtml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
@{
|
||||
Layout = "/Features/_Shared/Pages/_Layout.cshtml";
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue