Rename salesbook

This commit is contained in:
2025-06-26 10:08:21 +02:00
parent a34e673cd2
commit 3f2b7a6bb5
164 changed files with 267 additions and 262 deletions

View File

@@ -0,0 +1,89 @@
@inject IJSRuntime JS
<div class="@(Back ? "" : "container") header">
<div class="header-content @(Back ? "with-back" : "no-back")">
@if (Back)
{
<div class="left-section">
<MudButton StartIcon="@(!Cancel ? Icons.Material.Outlined.ArrowBackIosNew : "")"
OnClick="GoBack"
Color="Color.Info"
Style="text-transform: none"
Variant="Variant.Text">
@BackTo
</MudButton>
</div>
}
<h3 class="page-title">@Title</h3>
<div class="right-section">
@if (LabelSave.IsNullOrEmpty())
{
@if (ShowFilter)
{
<MudIconButton OnClick="OnFilterToggle" Icon="@Icons.Material.Outlined.FilterAlt"/>
}
@* @if (ShowCalendarToggle)
{
<MudIconButton OnClick="OnCalendarToggle" Icon="@Icons.Material.Filled.CalendarMonth" Color="Color.Dark"/>
} *@
@if (ShowProfile)
{
<MudIconButton Class="user" OnClick="OpenPersonalInfo" Icon="@Icons.Material.Filled.Person"/>
}
}
else
{
<MudButton OnClick="OnSave"
Color="Color.Info"
Style="text-transform: none"
Variant="Variant.Text">
@LabelSave
</MudButton>
}
</div>
</div>
</div>
@code{
[Parameter] public string? Title { get; set; }
[Parameter] public bool ShowFilter { get; set; }
[Parameter] public bool ShowProfile { get; set; } = true;
[Parameter] public bool Back { get; set; }
[Parameter] public bool BackOnTop { get; set; }
[Parameter] public string BackTo { get; set; } = "";
[Parameter] public EventCallback OnFilterToggle { get; set; }
[Parameter] public bool Cancel { get; set; }
[Parameter] public EventCallback OnCancel { get; set; }
[Parameter] public string? LabelSave { get; set; }
[Parameter] public EventCallback OnSave { get; set; }
[Parameter] public bool ShowCalendarToggle { get; set; }
[Parameter] public EventCallback OnCalendarToggle { get; set; }
protected override void OnParametersSet()
{
Back = !Back ? !Back && Cancel : Back;
BackTo = Cancel ? "Annulla" : BackTo;
}
private async Task GoBack()
{
if (Cancel)
{
await OnCancel.InvokeAsync();
return;
}
await JS.InvokeVoidAsync("goBack");
}
private void OpenPersonalInfo() =>
NavigationManager.NavigateTo("/PersonalInfo");
}

View File

@@ -0,0 +1,21 @@
.header-content {
line-height: normal;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
}
.header-content.with-back .page-title {
position: absolute;
left: 50%;
transform: translateX(-50%);
margin: 0;
font-size: larger;
}
.left-section ::deep button, .right-section ::deep button { font-size: 1.1rem; }
.left-section ::deep .mud-button-icon-start { margin-right: 3px !important; }
.header-content.no-back .page-title { margin: 0; }

View File

@@ -0,0 +1,92 @@
@using System.Globalization
@using CommunityToolkit.Mvvm.Messaging
@using salesbook.Shared.Core.Messages.Back
@inherits LayoutComponentBase
@inject IJSRuntime JS
@inject IMessenger Messenger
@inject BackNavigationService BackService
<MudThemeProvider Theme="_currentTheme" @ref="@_mudThemeProvider" @bind-IsDarkMode="@IsDarkMode" />
<MudPopoverProvider/>
<MudDialogProvider/>
<MudSnackbarProvider/>
<div class="page">
<NavMenu/>
<main>
<article>
@Body
</article>
</main>
</div>
@code {
private MudThemeProvider? _mudThemeProvider;
private bool IsDarkMode { get; set; }
private string _mainContentClass = "";
private readonly MudTheme _currentTheme = new()
{
PaletteLight = new PaletteLight()
{
Primary = "#00a0de",
Secondary = "#002339",
Tertiary = "#dff2ff",
TextPrimary = "#000"
},
PaletteDark = new PaletteDark
{
Primary = "#00a0de",
Secondary = "#002339",
Tertiary = "#dff2ff",
Surface = "#000406",
Background = "#000406",
TextPrimary = "#fff",
GrayDark = "#E0E0E0"
}
};
protected override async Task OnAfterRenderAsync(bool firstRender)
{
// if (firstRender)
// {
// var isDarkMode = LocalStorage.GetString("isDarkMode");
// if (isDarkMode == null && _mudThemeProvider != null)
// {
// IsDarkMode = await _mudThemeProvider.GetSystemPreference();
// await _mudThemeProvider.WatchSystemPreference(OnSystemPreferenceChanged);
// LocalStorage.SetString("isDarkMode", IsDarkMode.ToString());
// StateHasChanged();
// }
// else
// {
// IsDarkMode = bool.Parse(isDarkMode!);
// }
// if (IsDarkMode)
// {
// _mainContentClass += "is-dark";
// StateHasChanged();
// }
// }
}
private async Task OnSystemPreferenceChanged(bool newValue)
{
IsDarkMode = newValue;
}
protected override void OnInitialized()
{
BackService.OnHardwareBack += async () => { await JS.InvokeVoidAsync("goBack"); };
var culture = new CultureInfo("it-IT", false);
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}
}

View File

@@ -0,0 +1,77 @@
.page {
position: relative;
display: flex;
flex-direction: column;
}
main {
flex: 1;
}
.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}
.top-row ::deep a, .top-row ::deep .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
text-decoration: none;
}
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
text-decoration: underline;
}
.top-row ::deep a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 640.98px) {
.top-row {
justify-content: space-between;
}
.top-row ::deep a, .top-row ::deep .btn-link {
margin-left: 0;
}
}
@media (min-width: 641px) {
.page {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.top-row {
position: sticky;
top: 0;
z-index: 1;
}
.top-row.auth ::deep a:first-child {
flex: 1;
text-align: right;
width: 0;
}
.top-row, article {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}

View File

@@ -0,0 +1,96 @@
@using CommunityToolkit.Mvvm.Messaging
@using salesbook.Shared.Core.Dto
@using salesbook.Shared.Core.Entity
@using salesbook.Shared.Core.Messages.Activity.Copy
@using salesbook.Shared.Core.Messages.Activity.New
@inject IDialogService Dialog
@inject IMessenger Messenger
@inject CopyActivityService CopyActivityService
<div class="container animated-navbar @(IsVisible ? "show-nav" : "hide-nav") @(IsVisible? PlusVisible ? "with-plus" : "without-plus" : "with-plus")">
<nav class="navbar @(IsVisible? PlusVisible ? "with-plus" : "without-plus" : "with-plus")">
<div class="container-navbar">
<ul class="navbar-nav flex-row nav-justified align-items-center w-100 text-center">
<li class="nav-item">
<NavLink class="nav-link" href="Users" Match="NavLinkMatch.All">
<div class="d-flex flex-column">
<i class="ri-group-line"></i>
<span>Contatti</span>
</div>
</NavLink>
</li>
<li class="nav-item">
<NavLink class="nav-link" href="Calendar" Match="NavLinkMatch.All">
<div class="d-flex flex-column">
<i class="ri-calendar-todo-line"></i>
<span>Agenda</span>
</div>
</NavLink>
</li>
<li class="nav-item">
<NavLink class="nav-link" href="Notifications" Match="NavLinkMatch.All">
<div class="d-flex flex-column">
<i class="ri-notification-4-line"></i>
<span>Notifiche</span>
</div>
</NavLink>
</li>
</ul>
</div>
@if (PlusVisible)
{
<MudMenu PopoverClass="custom_popover" AnchorOrigin="Origin.TopLeft" TransformOrigin="Origin.BottomRight">
<ActivatorContent>
<MudFab Class="custom-plus-button" Color="Color.Surface" Size="Size.Medium" IconSize="Size.Medium" IconColor="Color.Primary" StartIcon="@Icons.Material.Filled.Add" />
</ActivatorContent>
<ChildContent>
<MudMenuItem Disabled="true">Nuovo contatto</MudMenuItem>
<MudMenuItem OnClick="CreateActivity">Nuova attivit<69></MudMenuItem>
</ChildContent>
</MudMenu>
}
</nav>
</div>
@code
{
private bool IsVisible { get; set; } = true;
private bool PlusVisible { get; set; } = true;
protected override Task OnInitializedAsync()
{
CopyActivityService.OnCopyActivity += async dto => await CreateActivity(dto);
NavigationManager.LocationChanged += (_, args) =>
{
var location = args.Location.Remove(0, NavigationManager.BaseUri.Length);
var newIsVisible = new List<string> { "Calendar", "Users", "Notifications" }
.Contains(location);
var newPlusVisible = new List<string> { "Calendar", "Users" }
.Contains(location);
if (IsVisible == newIsVisible && PlusVisible == newPlusVisible) return;
IsVisible = newIsVisible;
PlusVisible = newPlusVisible;
StateHasChanged();
};
return Task.CompletedTask;
}
private Task CreateActivity() => CreateActivity(null);
private async Task CreateActivity(ActivityDTO? activity)
{
var result = await ModalHelpers.OpenActivityForm(Dialog, activity, null);
if (result is { Canceled: false, Data: not null } && result.Data.GetType() == typeof(StbActivity))
{
Messenger.Send(new NewActivityMessage(((StbActivity)result.Data).ActivityId));
}
}
}

View File

@@ -0,0 +1,97 @@
.animated-navbar {
background: transparent;
position: fixed;
bottom: 0;
width: 100%;
z-index: 1001;
transition: all 0.3s ease-in-out;
}
.animated-navbar.show-nav { transform: translateY(0); }
.animated-navbar.hide-nav { transform: translateY(100%); }
.animated-navbar.with-plus { margin-left: 30px; }
.navbar {
padding-bottom: 1rem;
padding-top: 0 !important;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: end;
transition: all 0.3s ease-in-out;
}
.navbar.with-plus { transform: translateX(-30px); }
.navbar.without-plus {
transform: translateX(0);
justify-content: center;
}
.container-navbar {
background: var(--mud-palette-surface);
border-radius: 50px;
padding: 0 10px;
box-shadow: var(--custom-box-shadow);
transition: all 0.3s ease-in-out;
}
.nav-item { font-size: 0.9rem; }
.nav-item.plus-button {
position: relative;
bottom: 15px;
}
.navbar ::deep .custom-plus-button .mud-icon-root {
transition: .5s;
transform: rotate(0);
font-size: 2rem;
}
.navbar ::deep .custom-plus-button {
background: var(--mud-palette-surface);
box-shadow: var(--custom-box-shadow);
transition: all 0.3s ease-in-out;
}
.navbar ::deep .custom-plus-button:focus .mud-icon-root { transform: rotate(225deg); }
.nav-item ::deep a {
display: flex;
align-items: center;
line-height: 1.2;
justify-content: center;
padding-top: .25rem !important;
padding-bottom: .25rem !important;
}
.nav-item ::deep a > div {
-webkit-transition: all .1s ease-out;
transition: all .1s ease-out;
min-width: 75px;
}
.nav-item ::deep a.active > div { color: var(--mud-palette-primary); }
.nav-item ::deep a.active > div > i {
/*background-color: color-mix(in srgb, var(--mud-palette-primary) 20%, transparent);*/
border-radius: 10px;
}
.nav-item ::deep a.active > div > span { font-weight: 800; }
.nav-item ::deep a:not(.active) > div {
color: var(--mud-palette-text-primary);
}
.nav-item ::deep a i { font-size: 1.65rem; }
.nav-item ::deep a span {
font-size: 0.8rem;
font-weight: 500;
}
@supports (-webkit-touch-callout: none) { .navbar { padding-bottom: env(safe-area-inset-bottom); } }

View File

@@ -0,0 +1,23 @@
@using salesbook.Shared.Components.Layout.Spinner
<MudOverlay Visible="VisibleOverlay" LightBackground="true">
@if (SuccessAnimation)
{
<div class="success-checkmark">
<div class="check-icon">
<span class="icon-line line-tip"></span>
<span class="icon-line line-long"></span>
<div class="icon-circle"></div>
<div class="icon-fix"></div>
</div>
</div>
}
else
{
<SpinnerLayout/>
}
</MudOverlay>
@code {
[Parameter] public required bool SuccessAnimation { get; set; }
[Parameter] public required bool VisibleOverlay { get; set; }
}

View File

@@ -0,0 +1,156 @@
.success-checkmark {
width: 80px;
height: 80px;
margin: 0 auto;
}
.success-checkmark .check-icon {
width: 80px;
height: 80px;
position: relative;
border-radius: 50%;
box-sizing: content-box;
border: 4px solid var(--mud-palette-success);
}
.success-checkmark .check-icon::before {
top: 3px;
left: -2px;
width: 30px;
transform-origin: 100% 50%;
border-radius: 100px 0 0 100px;
}
.success-checkmark .check-icon::after {
top: 0;
left: 30px;
width: 60px;
transform-origin: 0 50%;
border-radius: 0 100px 100px 0;
animation: rotate-circle 4.25s ease-in;
}
.success-checkmark .check-icon::before,
.success-checkmark .check-icon::after {
content: '';
height: 100px;
position: absolute;
transform: rotate(-45deg);
z-index: 2;
}
.icon-line {
height: 5px;
background-color: var(--mud-palette-success);
display: block;
border-radius: 2px;
position: absolute;
z-index: 10;
}
.icon-line.line-tip {
top: 46px;
left: 14px;
width: 25px;
transform: rotate(45deg);
animation: icon-line-tip 0.75s;
}
.icon-line.line-long {
top: 38px;
right: 8px;
width: 47px;
transform: rotate(-45deg);
animation: icon-line-long 0.75s;
}
.icon-circle {
top: -4px;
left: -4px;
z-index: 10;
width: 80px;
height: 80px;
border-radius: 50%;
position: absolute;
box-sizing: content-box;
border: 4px solid var(--mud-palette-success);
}
.icon-fix {
top: 8px;
width: 5px;
left: 26px;
z-index: 1;
height: 85px;
position: absolute;
transform: rotate(-45deg);
}
@keyframes rotate-circle {
0% { transform: rotate(-45deg); }
5% { transform: rotate(-45deg); }
12% { transform: rotate(-405deg); }
100% { transform: rotate(-405deg); }
}
@keyframes icon-line-tip {
0% {
width: 0;
left: 1px;
top: 19px;
}
54% {
width: 0;
left: 1px;
top: 19px;
}
70% {
width: 50px;
left: -8px;
top: 37px;
}
84% {
width: 17px;
left: 21px;
top: 48px;
}
100% {
width: 25px;
left: 14px;
top: 45px;
}
}
@keyframes icon-line-long {
0% {
width: 0;
right: 46px;
top: 54px;
}
65% {
width: 0;
right: 46px;
top: 54px;
}
84% {
width: 55px;
right: 0px;
top: 35px;
}
100% {
width: 47px;
right: 8px;
top: 38px;
}
}

View File

@@ -0,0 +1,8 @@
<div class="spinner-container @(FullScreen ? "" : "not-fullScreen")">
<span class="loader"></span>
</div>
@code
{
[Parameter] public bool FullScreen { get; set; } = true;
}

View File

@@ -0,0 +1,43 @@
.spinner-container {
display: flex;
justify-content: center;
height: calc(100vh - 10.1rem);
align-items: center;
color: var(--mud-palette-primary);
}
.not-fullScreen {
height: auto !important;
padding: 2rem 0 !important;
}
.loader {
width: 50px;
aspect-ratio: 1;
border-radius: 50%;
border: 8px solid #0000;
border-right-color: var(--mud-palette-secondary);
position: relative;
animation: l24 1s infinite linear;
}
.loader:before,
.loader:after {
content: "";
position: absolute;
inset: -8px;
border-radius: 50%;
border: inherit;
animation: inherit;
animation-duration: 2s;
}
.loader:after {
animation-duration: 4s;
}
@keyframes l24 {
100% {
transform: rotate(1turn)
}
}

View File

@@ -0,0 +1,20 @@
@if (Elements is not null)
{
<div class="container-loader">
<span>Download risorse in corso</span>
<div>
@foreach (var element in Elements)
{
<div class="progress-content">
<span>@element.Key</span>
<MudProgressLinear Indeterminate="@(!element.Value)" Value="100" Rounded="true" Color="@(element.Value ? Color.Tertiary : Color.Secondary)" Size="Size.Large" />
</div>
}
</div>
</div>
}
@code
{
[Parameter] public Dictionary<string, bool>? Elements { get; set; }
}

View File

@@ -0,0 +1,27 @@
.container-loader {
display: flex;
height: 95vh;
flex-direction: column;
justify-content: center;
padding: 0 1rem;
align-items: center;
gap: 5vh;
}
.container-loader > div {
width: 100%;
}
.container-loader > span {
font-weight: 900;
font-size: large;
color: var(--mud-palette-primary);
}
.progress-content > span {
font-weight: 700;
}
.progress-content:nth-last-child(2) {
margin: 10px 0;
}

View File

@@ -0,0 +1,625 @@
@page "/Calendar"
@using salesbook.Shared.Core.Dto
@using salesbook.Shared.Core.Interface
@using salesbook.Shared.Components.Layout
@using salesbook.Shared.Components.SingleElements
@using salesbook.Shared.Components.Layout.Spinner
@using salesbook.Shared.Components.SingleElements.BottomSheet
@using salesbook.Shared.Core.Entity
@using salesbook.Shared.Core.Messages.Activity.New
@inject IManageDataService ManageData
@inject IJSRuntime JS
@inject NewActivityService NewActivity
<HeaderLayout Title="@_headerTitle"
ShowFilter="true"
ShowCalendarToggle="true"
OnFilterToggle="ToggleFilter"
OnCalendarToggle="ToggleExpanded"/>
<div @ref="_weekSliderRef" class="container week-slider @(Expanded ? "expanded" : "") @(SliderAnimation)">
@if (Expanded)
{
<!-- Vista mensile -->
@foreach (var nomeGiorno in GiorniSettimana)
{
<div class="week-day">
<div>@nomeGiorno</div>
</div>
}
@foreach (var unused in Enumerable.Range(0, StartOffset))
{
<div class="day" style="visibility: hidden"></div>
}
@if (_isInitialized && _monthDaysData.Length > 0)
{
@for (var d = 1; d <= DaysInMonth; d++)
{
var day = new DateTime(CurrentMonth.Year, CurrentMonth.Month, d);
var dayData = _monthDaysData[d - 1];
<div class="day @dayData.CssClass"
@onclick="() => SelezionaDataDalMese(day)">
<div>@d</div>
@if (dayData.HasEvents)
{
<div class="event-dot-container" style="margin-top: 2px;">
@foreach (var cat in dayData.EventCategories)
{
<div class="event-dot @cat.CssClass" title="@cat.Title"></div>
}
</div>
}
</div>
}
}
else
{
@* Fallback rendering per prima inizializzazione *@
@for (var d = 1; d <= DaysInMonth; d++)
{
var day = new DateTime(CurrentMonth.Year, CurrentMonth.Month, d);
var isSelected = IsSameDay(day, SelectedDate);
var isToday = IsSameDay(day, DateTime.Today);
var events = GetEventsForDay(day);
<div class="day @(isSelected ? "selected" : (isToday ? "today" : ""))"
@onclick="() => SelezionaDataDalMese(day)">
<div>@d</div>
@if (events.Any())
{
<div class="event-dot-container" style="margin-top: 2px;">
@foreach (var cat in events.Select(x => x.Category).Distinct())
{
<div class="event-dot @cat.ConvertToHumanReadable()" title="@cat.ConvertToHumanReadable()"></div>
}
</div>
}
</div>
}
}
@foreach (var unused in Enumerable.Range(0, EndOffset))
{
<div class="day" style="visibility: hidden"></div>
}
}
else
{
<!-- Vista settimanale -->
@if (_isInitialized && _weekDaysData.Length == 7 && _weekDaysData[0].Date != default)
{
@for (int i = 0; i < 7; i++)
{
var dayData = _weekDaysData[i];
var day = dayData.Date;
<div class="week-day">
<div>@dayData.DayName</div>
<div class="day @dayData.CssClass"
@onclick="() => SelezionaData(day)">
<div>@day.Day</div>
@if (dayData.HasEvents)
{
<div class="event-dot-container" style="margin-top: 2px;">
@foreach (var cat in dayData.EventCategories)
{
<div class="event-dot @cat.CssClass" title="@cat.Title"></div>
}
</div>
}
</div>
</div>
}
}
else
{
var start = GetStartOfWeek(SelectedDate);
var culture = new System.Globalization.CultureInfo("it-IT");
for (var i = 0; i < 7; i++)
{
var day = start.AddDays(i);
var isSelected = IsSameDay(day, SelectedDate);
var isToday = IsSameDay(day, DateTime.Today);
var events = GetEventsForDay(day);
<div class="week-day">
<div>@day.ToString("ddd", culture)</div>
<div class="day @(isSelected ? "selected" : (isToday ? "today" : ""))"
@onclick="() => SelezionaData(day)"
aria-label="@day.ToString("dddd d MMMM", culture)">
<div>@day.Day</div>
@if (events.Any())
{
<div class="event-dot-container" style="margin-top: 2px;">
@foreach (var cat in events.Select(x => x.Category).Distinct())
{
<div class="event-dot @cat.ConvertToHumanReadable()" title="@cat.ConvertToHumanReadable()"></div>
}
</div>
}
</div>
</div>
}
}
}
</div>
<div class="container appointments">
@if (IsLoading)
{
<SpinnerLayout FullScreen="false"/>
}
else if (FilteredActivities is { Count: > 0 })
{
<Virtualize Items="FilteredActivities" Context="activity">
<ActivityCard Activity="activity" ActivityChanged="OnActivityChanged" ActivityDeleted="OnActivityDeleted" />
</Virtualize>
}
else
{
<NoDataAvailable Text="Nessuna attività trovata" ImageSource="_content/salesbook.Shared/images/undraw_file-search_cbur.svg"/>
}
</div>
<FilterActivity @bind-IsSheetVisible="OpenFilter" @bind-Filter="Filter" @bind-Filter:after="ApplyFilter"/>
@code {
// Modelli per ottimizzazione rendering
private record DayData(DateTime Date, string CssClass, bool HasEvents, CategoryData[] EventCategories, string DayName = "");
private record CategoryData(string CssClass, string Title);
// Cache per rendering
private DayData[] _monthDaysData = [];
private readonly DayData[] _weekDaysData = new DayData[7];
private string _headerTitle = string.Empty;
private readonly Dictionary<DateTime, List<ActivityDTO>> _eventsCache = new();
private readonly Dictionary<DateTime, CategoryData[]> _categoriesCache = new();
private bool _isInitialized = false;
// Stato UI
private bool Expanded { get; set; }
private string SliderAnimation { get; set; } = string.Empty;
private ElementReference _weekSliderRef;
private DotNetObjectReference<Calendar>? _dotNetHelper;
// Stato calendario
private DateTime SelectedDate { get; set; } = DateTime.Today;
private DateTime _internalMonth = DateTime.Today;
private DateTime CurrentMonth => new(_internalMonth.Year, _internalMonth.Month, 1);
// Stato attività
private List<ActivityDTO> MonthActivities { get; set; } = [];
private List<ActivityDTO> FilteredActivities { get; set; } = [];
private bool IsLoading { get; set; } = true;
// Supporto rendering mese
private static readonly string[] GiorniSettimana = ["Lu", "Ma", "Me", "Gi", "Ve", "Sa", "Do"];
private int DaysInMonth => DateTime.DaysInMonth(CurrentMonth.Year, CurrentMonth.Month);
private int StartOffset => (int)CurrentMonth.DayOfWeek == 0 ? 6 : (int)CurrentMonth.DayOfWeek - 1;
//Filtri
private bool OpenFilter { get; set; }
private FilterActivityDTO Filter { get; set; } = new();
private int EndOffset
{
get
{
var totalCells = (int)Math.Ceiling((DaysInMonth + StartOffset) / 7.0) * 7;
return totalCells - (DaysInMonth + StartOffset);
}
}
protected override void OnInitialized()
{
PrepareRenderingData();
NewActivity.OnActivityCreated += async activityId => await OnActivityCreated(activityId);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
Filter.User = new HashSet<string> { UserSession.User.Username };
_dotNetHelper = DotNetObjectReference.Create(this);
await JS.InvokeVoidAsync("calendarSwipe.register", _weekSliderRef, _dotNetHelper);
_internalMonth = new DateTime(SelectedDate.Year, SelectedDate.Month, 1);
await LoadMonthData();
_isInitialized = true;
ApplyFilter();
StateHasChanged();
}
}
// Metodo per preparare i dati di rendering una sola volta
private void PrepareRenderingData()
{
PrepareHeaderTitle();
PrepareEventsCache();
if (Expanded)
{
PrepareMonthDaysData();
}
else
{
PrepareWeekDaysData();
}
}
private void PrepareHeaderTitle()
{
_headerTitle = CurrentMonth.ToString("MMMM yyyy", new System.Globalization.CultureInfo("it-IT")).FirstCharToUpper();
}
private void PrepareEventsCache()
{
_eventsCache.Clear();
_categoriesCache.Clear();
// Raggruppa le attività per data
var activitiesByDate = MonthActivities
.GroupBy(x => (x.EffectiveDate ?? x.EstimatedDate!).Value.Date)
.ToDictionary(g => g.Key, g => g.ToList());
foreach (var (date, activities) in activitiesByDate)
{
_eventsCache[date] = activities;
// Pre-calcola le categorie per ogni giorno
var categories = activities
.Select(x => x.Category)
.Distinct()
.Select(cat => new CategoryData(cat.ConvertToHumanReadable(), cat.ConvertToHumanReadable()))
.ToArray();
_categoriesCache[date] = categories;
}
}
private void PrepareMonthDaysData()
{
_monthDaysData = new DayData[DaysInMonth];
var today = DateTime.Today;
for (var d = 1; d <= DaysInMonth; d++)
{
var day = new DateTime(CurrentMonth.Year, CurrentMonth.Month, d);
var isSelected = day.Date == SelectedDate.Date;
var isToday = day.Date == today;
var cssClass = isSelected ? "selected" : (isToday ? "today" : "");
var hasEvents = _eventsCache.ContainsKey(day.Date);
var eventCategories = hasEvents ? GetFilteredCategoriesForDay(day.Date) : [];
_monthDaysData[d - 1] = new DayData(day, cssClass, eventCategories.Length > 0, eventCategories);
}
}
private void PrepareWeekDaysData()
{
var start = GetStartOfWeek(SelectedDate);
var today = DateTime.Today;
var culture = new System.Globalization.CultureInfo("it-IT");
for (var i = 0; i < 7; i++)
{
var day = start.AddDays(i);
var isSelected = day.Date == SelectedDate.Date;
var isToday = day.Date == today;
var cssClass = isSelected ? "selected" : (isToday ? "today" : "");
var dayName = day.ToString("ddd", culture);
var hasEvents = _eventsCache.ContainsKey(day.Date);
var eventCategories = hasEvents ? GetFilteredCategoriesForDay(day.Date) : [];
_weekDaysData[i] = new DayData(day, cssClass, eventCategories.Length > 0, eventCategories, dayName);
}
}
private CategoryData[] GetFilteredCategoriesForDay(DateTime date)
{
if (!_categoriesCache.TryGetValue(date, out var categories))
return [];
if (Filter.ClearFilter)
return categories;
// Applica i filtri alle categorie
var filteredActivities = GetFilteredActivitiesForDay(date);
if (!filteredActivities.Any())
return [];
return filteredActivities
.Select(x => x.Category)
.Distinct()
.Select(cat => new CategoryData(cat.ConvertToHumanReadable(), cat.ConvertToHumanReadable()))
.ToArray();
}
private List<ActivityDTO> GetFilteredActivitiesForDay(DateTime date)
{
if (!_eventsCache.TryGetValue(date, out var activities))
return [];
if (Filter.ClearFilter)
return activities;
var filteredActivity = activities.AsQueryable();
filteredActivity = filteredActivity
.Where(x => Filter.Text.IsNullOrEmpty() || (x.ActivityDescription != null && x.ActivityDescription.ContainsIgnoreCase(Filter.Text!)));
filteredActivity = filteredActivity
.Where(x => Filter.Type.IsNullOrEmpty() || (x.ActivityTypeId != null && x.ActivityTypeId.Equals(Filter.Type)));
filteredActivity = filteredActivity
.Where(x => Filter.Result.IsNullOrEmpty() || (x.ActivityResultId != null && x.ActivityResultId.Equals(Filter.Result)));
filteredActivity = filteredActivity
.Where(x => Filter.User.IsNullOrEmpty() || (x.UserName != null && Filter.User!.Contains(x.UserName)));
filteredActivity = filteredActivity
.Where(x => Filter.Category == null || x.Category.Equals(Filter.Category));
return filteredActivity.ToList();
}
[JSInvokable]
public async Task OnSwipeLeft()
{
await CambiaPeriodo(1);
PrepareRenderingData();
StateHasChanged();
if (Expanded)
{
await LoadMonthData();
}
}
[JSInvokable]
public async Task OnSwipeRight()
{
await CambiaPeriodo(-1);
PrepareRenderingData();
StateHasChanged();
if (Expanded)
{
await LoadMonthData();
}
}
[JSInvokable]
public async Task OnSwipeDown()
{
if (!Expanded)
ToggleExpanded();
}
[JSInvokable]
public async Task OnSwipeUp()
{
if (Expanded)
ToggleExpanded();
}
// Cambio periodo mese/settimana
private async Task CambiaPeriodo(int direzione)
{
if (Expanded)
{
var y = CurrentMonth.Year;
var m = CurrentMonth.Month + direzione;
if (m < 1)
{
y--;
m = 12;
}
if (m > 12)
{
y++;
m = 1;
}
_internalMonth = new DateTime(y, m, 1);
}
else
{
await SelezionaData(SelectedDate.AddDays(7 * direzione));
_internalMonth = new DateTime(SelectedDate.Year, SelectedDate.Month, 1);
}
}
// Cambio modalità
private void ToggleExpanded()
{
if (Expanded)
{
SliderAnimation = "collapse-animation";
Expanded = false;
}
else
{
Expanded = true;
SliderAnimation = "expand-animation";
}
PrepareRenderingData();
StateHasChanged();
SliderAnimation = "";
StateHasChanged();
}
// Caricamento attività al cambio mese
private async Task LoadMonthData()
{
IsLoading = true;
StateHasChanged();
var start = CurrentMonth;
var end = start.AddDays(DaysInMonth - 1);
var activities = await ManageData.GetActivity(x =>
(x.EffectiveDate == null && x.EstimatedDate >= start && x.EstimatedDate <= end) ||
(x.EffectiveDate >= start && x.EffectiveDate <= end));
MonthActivities = activities.OrderBy(x => x.EffectiveDate ?? x.EstimatedDate).ToList();
PrepareRenderingData();
IsLoading = false;
StateHasChanged();
}
// Selezione giorno in settimana
private async Task SelezionaData(DateTime day)
{
SelectedDate = day;
var cacheInternalMonth = _internalMonth;
_internalMonth = new DateTime(day.Year, day.Month, 1);
if (cacheInternalMonth != _internalMonth)
{
await LoadMonthData();
}
else
{
PrepareRenderingData();
}
ApplyFilter();
StateHasChanged();
}
// Selezione giorno dal mese (chiude la vista mese!)
private async Task SelezionaDataDalMese(DateTime day)
{
SelectedDate = day;
SliderAnimation = "collapse-animation";
Expanded = false;
_internalMonth = new DateTime(day.Year, day.Month, 1);
PrepareRenderingData();
ApplyFilter();
StateHasChanged();
SliderAnimation = "";
StateHasChanged();
}
// Utility
private static bool IsSameDay(DateTime d1, DateTime d2) => d1.Date == d2.Date;
private static DateTime GetStartOfWeek(DateTime date)
{
var day = date.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)date.DayOfWeek;
return date.AddDays(-day + 1).Date;
}
private List<ActivityDTO> GetEventsForDay(DateTime day)
=> _eventsCache.TryGetValue(day.Date, out var events) ? events : [];
public void Dispose()
{
_dotNetHelper?.Dispose();
}
private async Task OnActivityDeleted(ActivityDTO activity)
{
IsLoading = true;
await ManageData.DeleteActivity(activity);
var indexActivity = MonthActivities?.FindIndex(x => x.ActivityId.Equals(activity.ActivityId));
if (indexActivity != null)
{
MonthActivities?.RemoveAt(indexActivity.Value);
PrepareRenderingData();
ApplyFilter();
}
IsLoading = false;
}
private async Task OnActivityCreated(string activityId)
{
IsLoading = true;
var activity = (await ManageData.GetActivity(x => x.ActivityId.Equals(activityId))).LastOrDefault();
if (activity == null)
{
IsLoading = false;
return;
}
var date = activity.EffectiveDate ?? activity.EstimatedDate;
if (CurrentMonth.Month != date!.Value.Month)
{
IsLoading = false;
return;
}
MonthActivities.Add(activity);
PrepareRenderingData();
IsLoading = false;
ApplyFilter();
}
private async Task OnActivityChanged(string activityId)
{
var newActivity = await ManageData.GetActivity(x => x.ActivityId.Equals(activityId));
var indexActivity = MonthActivities?.FindIndex(x => x.ActivityId.Equals(activityId));
if (indexActivity != null && !newActivity.IsNullOrEmpty())
{
MonthActivities![indexActivity.Value] = newActivity[0];
PrepareRenderingData(); // Ricalcola i dati di rendering
ApplyFilter();
}
}
private void ToggleFilter()
{
OpenFilter = !OpenFilter;
StateHasChanged();
}
private void ApplyFilter()
{
FilteredActivities = GetFilteredActivitiesForDay(SelectedDate);
// Aggiorna i dati di rendering se il filtro è cambiato
if (Expanded)
{
PrepareMonthDaysData();
}
else
{
PrepareWeekDaysData();
}
StateHasChanged();
}
// Metodo ottimizzato per il rendering dei filtri
private List<ActivityDTO> ReturnFilteredActivity(DateTime day)
{
return GetFilteredActivitiesForDay(day);
}
}

View File

@@ -0,0 +1,167 @@
.calendar {
overflow: hidden;
position: relative;
}
.week-slider {
width: 100%;
transition: all 0.4s ease;
transform-origin: center center;
transform: scaleY(1);
opacity: 1;
touch-action: pan-x;
user-select: none;
margin: 0 auto;
}
.week-slider:not(.expanded) {
display: flex;
flex-direction: row;
justify-content: center;
align-items: flex-start;
gap: 0.6rem;
padding-top: 1rem;
padding-bottom: 1rem;
overflow-x: hidden;
overflow-y: visible;
}
.week-slider.expanded {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 0.4rem;
padding: 1rem;
overflow-y: auto;
}
.week-slider.expand-animation { animation: expandFromCenter 0.3s ease forwards; }
.week-slider.collapse-animation { animation: collapseToCenter 0.3s ease forwards; }
@keyframes expandFromCenter {
from {
transform: scaleY(0.6);
opacity: 0;
}
to {
transform: scaleY(1);
opacity: 1;
}
}
@keyframes collapseToCenter {
from {
transform: scaleY(1);
opacity: 1;
}
to {
transform: scaleY(0.6);
opacity: 0;
}
}
.week-day {
display: flex;
flex-direction: column;
align-items: center;
max-width: 60px;
flex: 1 1 0;
gap: 0.2rem;
}
.week-day > div:first-child {
font-size: 0.8rem;
color: var(--mud-palette-text-primary);
margin-bottom: 0.2rem;
font-weight: 500;
}
.day {
background: var(--mud-palette-surface);
border-radius: 10px;
text-align: center;
cursor: pointer;
transition: background 0.3s ease, transform 0.2s ease;
font-size: 0.95rem;
box-shadow: var(--custom-box-shadow);
width: 38px;
height: 38px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: var(--mud-palette-text-primary);
border: 1px solid var(--mud-palette-surface);
margin: 0 auto;
}
.day:hover { transform: scale(1.08); }
.week-slider:not(.expanded) .day {
padding: 0;
min-width: 38px;
min-height: 38px;
max-width: 48px;
max-height: 48px;
}
.day.selected {
background: var(--mud-palette-tertiary);
border: 1px solid var(--mud-palette-tertiary);
color: var(--mud-palette-secondary);
}
.day.today { border: 1px solid var(--mud-palette-primary); }
.appointments {
display: flex;
gap: 1rem;
overflow-y: auto;
flex-direction: column;
-ms-overflow-style: none;
scrollbar-width: none;
padding-bottom: 70px;
height: calc(100% - 130px);
}
.appointments.ah-calendar-m { height: calc(100% - 315px) !important; }
.appointments::-webkit-scrollbar { display: none; }
.appointment {
background: var(--mud-palette-surface);
border-radius: 8px;
padding: 0.8rem;
margin-bottom: 0.5rem;
box-shadow: var(--custom-box-shadow);
}
.toggle-month {
background: none;
border: none;
color: var(--mud-palette-text-primary);
font-size: 1rem;
cursor: pointer;
}
.event-dot-container {
display: flex;
gap: 5px;
flex-direction: row;
}
.event-dot {
height: 5px;
width: 5px;
border-radius: 50%;
}
.event-dot.memo { background-color: var(--mud-palette-info-darken); }
.event-dot.interna { background-color: var(--mud-palette-success-darken); }
.event-dot.commessa { background-color: var(--mud-palette-warning); }
@supports (-webkit-touch-callout: none) { .appointments { padding-bottom: calc(60px + env(safe-area-inset-bottom)) !important; } }

View File

@@ -0,0 +1,22 @@
@page "/"
@using salesbook.Shared.Core.Interface
@attribute [Authorize]
@inject IFormFactor FormFactor
@inject INetworkService NetworkService
@code
{
protected override Task OnInitializedAsync()
{
var lastSyncDate = LocalStorage.Get<DateTime>("last-sync");
if (!FormFactor.IsWeb() && NetworkService.IsNetworkAvailable() && lastSyncDate.Equals(DateTime.MinValue))
{
NavigationManager.NavigateTo("/sync");
return base.OnInitializedAsync();
}
NavigationManager.NavigateTo("/Calendar");
return base.OnInitializedAsync();
}
}

View File

@@ -0,0 +1,113 @@
@page "/login"
@using salesbook.Shared.Components.Layout.Spinner
@using salesbook.Shared.Core.Services
@inject IUserAccountService UserAccountService
@inject AppAuthenticationStateProvider AuthenticationStateProvider
@if (Spinner)
{
<SpinnerLayout/>
}
else
{
<div class="login-page">
<div class="container container-top-logo">
<img src="_content/salesbook.Shared/images/salesbook-marchio_vers.positiva.svg" class="logo" alt="sales book">
</div>
<div class="container container-login">
<div class="login-form-container">
<div class="input-group">
<MudTextField @bind-Value="UserData.Username" Label="Username" Variant="Variant.Outlined"/>
</div>
<div class="input-group">
<MudTextField InputType="@_passwordInput" @bind-Value="UserData.Password" Label="Password" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentIcon="@_passwordInputIcon" OnAdornmentClick="ShowPassword" AdornmentAriaLabel="Show Password"/>
</div>
<div class="input-group mb-2">
<MudTextField @bind-Value="UserData.CodHash" Label="Profilo azienda" Variant="Variant.Outlined"/>
</div>
<MudButton OnClick="SignInUser" Color="Color.Primary" Variant="Variant.Filled">Login</MudButton>
@if (_attemptFailed)
{
<MudAlert Class="my-3" Dense="true" Severity="Severity.Error" Variant="Variant.Filled">@ErrorMessage</MudAlert>
}
</div>
<div class="my-4 login-footer">
<span>Powered by</span>
<img src="_content/salesbook.Shared/images/logoIntegry.svg" class="img-fluid" alt="Integry">
</div>
</div>
</div>
}
@code {
private SignIn UserData { get; } = new();
private bool Spinner { get; set; }
private string ErrorMessage { get; set; } = "";
private bool _attemptFailed;
private bool _isShow;
private InputType _passwordInput = InputType.Password;
private string _passwordInputIcon = Icons.Material.Rounded.VisibilityOff;
private void ShowPassword()
{
@if (_isShow)
{
_isShow = false;
_passwordInputIcon = Icons.Material.Rounded.VisibilityOff;
_passwordInput = InputType.Password;
}
else
{
_isShow = true;
_passwordInputIcon = Icons.Material.Rounded.Visibility;
_passwordInput = InputType.Text;
}
}
protected override void OnInitialized()
{
UserData.CodHash = LocalStorage.GetString("codHash");
StateHasChanged();
}
private async Task SignInUser()
{
_attemptFailed = false;
if (!string.IsNullOrEmpty(UserData.Username) && !string.IsNullOrEmpty(UserData.Password) && !string.IsNullOrEmpty(UserData.CodHash))
{
Spinner = true;
StateHasChanged();
try
{
await UserAccountService.Login(UserData.Username, UserData.Password, UserData.CodHash);
AuthenticationStateProvider.NotifyAuthenticationState(); //Chiamato per forzare il refresh
LocalStorage.SetString("codHash", UserData.CodHash);
NavigationManager.NavigateTo("/");
StateHasChanged();
}
catch (Exception e)
{
Spinner = false;
StateHasChanged();
ErrorMessage = e.Message;
_attemptFailed = true;
Console.WriteLine(e);
// Logger<>.LogError(e, e.Message);
}
}
}
public class SignIn
{
public string? Username { get; set; }
public string? Password { get; set; }
public string? CodHash { get; set; }
}
}

View File

@@ -0,0 +1,54 @@
.login-page {
height: 100%;
display: flex;
flex-direction: column;
background: var(--mud-palette-surface);
}
.container-top-logo > .logo {
width: 50%;
}
.container-login > span {
font-size: large;
font-weight: 900;
text-align: center;
}
.container-top-logo {
height: 35vh;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
.login-form-container {
display: flex;
flex-direction: column;
gap: 1rem;
}
.container-login {
height: 100%;
display: flex;
flex-direction: column;
padding: 4px 16px 16px;
justify-content: space-between;
}
.login-footer {
width: 100%;
display: flex;
justify-content: center;
}
.login-footer span {
font-size: 9px;
color: var(--mud-palette-gray-darker);
}
.login-footer img {
height: 15px;
margin-left: 4px;
}

View File

@@ -0,0 +1,14 @@
@page "/Notifications"
@attribute [Authorize]
@using salesbook.Shared.Components.Layout
@using salesbook.Shared.Components.SingleElements
<HeaderLayout Title="Notifiche" />
<div class="container">
<NoDataAvailable Text="Nessuna notifica meno recente" />
</div>
@code {
}

View File

@@ -0,0 +1,164 @@
@page "/PersonalInfo"
@attribute [Authorize]
@using salesbook.Shared.Components.Layout
@using salesbook.Shared.Core.Authorization.Enum
@using salesbook.Shared.Core.Interface
@using salesbook.Shared.Core.Services
@inject AppAuthenticationStateProvider AuthenticationStateProvider
@inject INetworkService NetworkService
@inject IFormFactor FormFactor
<HeaderLayout BackTo="Indietro" Back="true" BackOnTop="true" Title="Profilo" ShowProfile="false"/>
@if (IsLoggedIn)
{
<div class="container content">
<div class="container-primary-info">
<div class="section-primary-info">
<MudAvatar Style="height: 70px; width: 70px; font-size: 2rem; font-weight: bold" Color="Color.Secondary">
@UtilityString.ExtractInitials(UserSession.User.Fullname)
</MudAvatar>
<div class="personal-info">
<span class="info-nome">@UserSession.User.Fullname</span>
@if (UserSession.User.KeyGroup is not null)
{
<span class="info-section">@(((KeyGroupEnum)UserSession.User.KeyGroup).ConvertToHumanReadable())</span>
}
</div>
</div>
<div class="divider"></div>
<div class="section-info">
<div class="section-personal-info">
<div>
<span class="info-title">Telefono</span>
<span class="info-text">000 0000000</span> @*Todo: to implement*@
</div>
<div>
<span class="info-title">Status</span>
@if (NetworkService.IsNetworkAvailable())
{
<div class="status online">
<i class="ri-wifi-line"></i>
<span>Online</span>
</div>
}
else
{
<div class="status offline">
<i class="ri-wifi-off-line"></i>
<span>Offline</span>
</div>
}
</div>
</div>
<div class="section-personal-info">
<div>
<span class="info-title">E-mail</span>
<span class="info-text">
@if (string.IsNullOrEmpty(UserSession.User.Email))
{
@("Nessuna mail configurata")
}
else
{
@UserSession.User.Email
}
</span>
</div>
<div>
<span class="info-title">Ultima sincronizzazione</span>
<span class="info-text">@LastSync.ToString("g")</span>
</div>
</div>
</div>
</div>
<div class="container-button">
<MudButton Class="button-settings green-icon"
FullWidth="true"
StartIcon="@Icons.Material.Outlined.Sync"
Size="Size.Medium"
OnClick="() => UpdateDb(true)"
Variant="Variant.Outlined">
Sincronizza
</MudButton>
<div class="divider"></div>
<MudButton Class="button-settings red-icon"
FullWidth="true"
StartIcon="@Icons.Material.Outlined.Sync"
Size="Size.Medium"
OnClick="() => UpdateDb()"
Variant="Variant.Outlined">
Ripristina dati
</MudButton>
</div>
<div class="container-button">
<MudButton Class="button-settings exit"
FullWidth="true"
Color="Color.Error"
Size="Size.Medium"
OnClick="Logout"
Variant="Variant.Outlined">
Esci
</MudButton>
</div>
</div>
}
@code {
private bool Unavailable { get; set; }
private bool IsLoggedIn { get; set; }
private DateTime LastSync { get; set; }
protected override async Task OnInitializedAsync()
{
IsLoggedIn = await UserSession.IsLoggedIn();
await LoadData();
}
private async Task LoadData()
{
await Task.Run(() =>
{
Unavailable = FormFactor.IsWeb() || !NetworkService.IsNetworkAvailable();
LastSync = LocalStorage.Get<DateTime>("last-sync");
});
StateHasChanged();
}
private void OpenSettings() =>
NavigationManager.NavigateTo("/settings/Profilo");
private async Task Logout()
{
await AuthenticationStateProvider.SignOut();
IsLoggedIn = await UserSession.IsLoggedIn();
StateHasChanged();
}
private void UpdateDb(bool withData = false)
{
var absoluteUri = NavigationManager.ToAbsoluteUri(NavigationManager.Uri);
var pathAndQuery = absoluteUri.Segments.Length > 1 ? absoluteUri.PathAndQuery : null;
string path;
if (withData)
path = pathAndQuery == null ? $"/sync/{DateTime.Today:yyyy-MM-dd}" : $"/sync/{DateTime.Today:yyyy-MM-dd}?path=" + System.Web.HttpUtility.UrlEncode(pathAndQuery);
else
path = pathAndQuery == null ? "/sync" : "/sync?path=" + System.Web.HttpUtility.UrlEncode(pathAndQuery);
NavigationManager.NavigateTo(path, replace: true);
}
}

View File

@@ -0,0 +1,83 @@
.container-primary-info {
box-shadow: var(--custom-box-shadow);
width: 100%;
margin-bottom: 2rem;
border-radius: 12px;
}
.container-primary-info .divider {
margin: 0 0 0 7rem;
width: unset;
}
.section-primary-info {
display: flex;
flex-direction: row;
align-items: center;
gap: 1.5rem;
padding: .8rem 1.2rem .4rem;
}
.personal-info {
display: flex;
flex-direction: column;
align-items: flex-start;
line-height: normal;
}
.info-nome {
color: var(--mud-palette-text-primary);
font-weight: 800;
font-size: x-large;
}
.info-section {
color: var(--mud-palette-gray-default);
font-size: medium;
font-weight: 600;
}
.section-info {
display: flex;
justify-content: space-between;
flex-direction: row;
padding: .4rem 1.2rem .8rem;
}
.section-personal-info {
display: flex;
flex-direction: column;
}
.section-personal-info > div {
display: flex;
flex-direction: column;
line-height: normal;
margin: .25rem 0;
}
.info-title {
color: var(--mud-palette-gray-darker);
font-weight: 800;
}
.info-text {
color: var(--mud-palette-text-secondary);
font-weight: 700;
font-size: small;
}
.content ::deep .user-button { border: 1px solid var(--card-border-color) !important; }
.user-button > i { font-size: large; }
.user-button > span {
font-size: medium;
font-weight: 600;
}
.status { font-weight: 700; }
.status.online { color: var(--mud-palette-success); }
.status.offline { color: var(--mud-palette-error); }

View File

@@ -0,0 +1,16 @@
@page "/settings"
@page "/settings/{BackTo}"
@using salesbook.Shared.Components.Layout
<HeaderLayout BackTo="@BackTo" Back="true" Title="Impostazioni"/>
<div class="content">
</div>
@code {
[Parameter] public string BackTo { get; set; } = "";
}

View File

@@ -0,0 +1,105 @@
@page "/sync"
@page "/sync/{DateFilter}"
@using salesbook.Shared.Components.Layout.Spinner
@using salesbook.Shared.Core.Interface
@inject ISyncDbService syncDb
@inject IManageDataService manageData
<SyncSpinner Elements="@Elements"/>
@code {
[Parameter] public string? DateFilter { get; set; }
private Dictionary<string, bool> Elements { get; set; } = new();
private bool _hasStarted = false;
private int _completedCount = 0;
protected override void OnInitialized()
{
Elements["Attività"] = false;
Elements["Commesse"] = false;
Elements["Clienti"] = false;
Elements["Prospect"] = false;
Elements["Impostazioni"] = false;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && !_hasStarted)
{
_hasStarted = true;
if (DateFilter is null)
{
await manageData.ClearDb();
}
await Task.WhenAll(
RunAndTrack(SetActivity),
RunAndTrack(SetClienti),
RunAndTrack(SetProspect),
RunAndTrack(SetCommesse),
RunAndTrack(SetSettings)
);
}
}
private async Task RunAndTrack(Func<Task> func)
{
await func();
_completedCount++;
if (_completedCount == Elements.Count)
{
LocalStorage.Set("last-sync", DateTime.Now);
var pathQuery = System.Web.HttpUtility.ParseQueryString(new UriBuilder(NavigationManager.Uri).Query);
var originalPath = pathQuery["path"] ?? null;
var path = originalPath ?? "/Calendar";
NavigationManager.NavigateTo(path, replace: true);
}
}
private async Task SetActivity()
{
await Task.Run(async () => { await syncDb.GetAndSaveActivity(DateFilter); });
Elements["Attività"] = true;
StateHasChanged();
}
private async Task SetClienti()
{
await Task.Run(async () => { await syncDb.GetAndSaveClienti(DateFilter); });
Elements["Clienti"] = true;
StateHasChanged();
}
private async Task SetProspect()
{
await Task.Run(async () => { await syncDb.GetAndSaveProspect(DateFilter); });
Elements["Prospect"] = true;
StateHasChanged();
}
private async Task SetCommesse()
{
await Task.Run(async () => { await syncDb.GetAndSaveCommesse(DateFilter); });
Elements["Commesse"] = true;
StateHasChanged();
}
private async Task SetSettings()
{
await Task.Run(async () => { await syncDb.GetAndSaveSettings(DateFilter); });
Elements["Impostazioni"] = true;
StateHasChanged();
}
}

View File

@@ -0,0 +1,121 @@
@page "/User/{CodAnag}"
@attribute [Authorize]
@using salesbook.Shared.Components.Layout
@using salesbook.Shared.Core.Entity
@using salesbook.Shared.Core.Interface
@using salesbook.Shared.Components.Layout.Spinner
@inject IManageDataService ManageData
<HeaderLayout BackTo="Indietro" Back="true" BackOnTop="true" Title="" ShowProfile="false"/>
@if (IsLoading)
{
<SpinnerLayout FullScreen="true"/>
}
else
{
<div class="container content">
<div class="container-primary-info">
<div class="section-primary-info">
<MudAvatar Style="height: 70px; width: 70px; font-size: 2rem; font-weight: bold" Color="Color.Secondary">
@UtilityString.ExtractInitials(Anag.RagSoc)
</MudAvatar>
<div class="personal-info">
<span class="info-nome">@Anag.RagSoc</span>
@if (UserSession.User.KeyGroup is not null)
{
<span class="info-section">@Anag.Indirizzo</span>
<span class="info-section">@($"{Anag.Cap} - {Anag.Citta} ({Anag.Prov})")</span>
}
</div>
</div>
<div class="divider"></div>
<div class="section-info">
<div class="section-personal-info">
<div>
<span class="info-title">Telefono</span>
<span class="info-text">
@if (string.IsNullOrEmpty(Anag.Telefono))
{
@("Nessuna mail configurata")
}
else
{
@Anag.Telefono
}
</span>
</div>
</div>
<div class="section-personal-info">
<div>
<span class="info-title">E-mail</span>
<span class="info-text">
@if (string.IsNullOrEmpty(Anag.EMail))
{
@("Nessuna mail configurata")
}
else
{
@Anag.EMail
}
</span>
</div>
</div>
</div>
</div>
@if (PersRif is { Count: > 0 })
{
<div class="container-pers-rif">
<Virtualize Items="PersRif" Context="person">
@{
var index = PersRif.IndexOf(person);
var isLast = index == PersRif.Count - 1;
}
<ContactCard Contact="person" />
@if (!isLast)
{
<div class="divider"></div>
}
</Virtualize>
</div>
}
<div class="container-button">
<MudButton Class="button-settings infoText"
FullWidth="true"
Size="Size.Medium"
Variant="Variant.Outlined">
Aggiungi contatto
</MudButton>
</div>
</div>
}
@code {
[Parameter] public string CodAnag { get; set; }
private AnagClie Anag { get; set; } = new();
private List<VtbCliePersRif>? PersRif { get; set; }
private bool IsLoading { get; set; } = true;
protected override async Task OnInitializedAsync()
{
await LoadData();
}
private async Task LoadData()
{
Anag = (await ManageData.GetTable<AnagClie>(x => x.CodAnag.Equals(CodAnag))).Last();
PersRif = await ManageData.GetTable<VtbCliePersRif>(x => x.CodAnag.Equals(Anag.CodAnag));
IsLoading = false;
StateHasChanged();
}
}

View File

@@ -0,0 +1,147 @@
.container-primary-info {
box-shadow: var(--custom-box-shadow);
width: 100%;
margin-bottom: 2rem;
border-radius: 16px;
}
.container-primary-info .divider {
margin: 0 0 0 7rem;
width: unset;
}
.section-primary-info {
display: flex;
flex-direction: row;
align-items: center;
gap: 1.5rem;
padding: .8rem 1.2rem .4rem;
}
.personal-info {
display: flex;
flex-direction: column;
align-items: flex-start;
line-height: normal;
}
.info-nome {
color: var(--mud-palette-text-primary);
font-weight: 800;
font-size: medium;
}
.info-section {
color: var(--mud-palette-gray-default);
font-size: small;
font-weight: 600;
}
.section-info {
display: flex;
justify-content: space-between;
flex-direction: row;
padding: .4rem 1.2rem .8rem;
}
.section-personal-info {
display: flex;
flex-direction: column;
}
.section-personal-info > div {
display: flex;
flex-direction: column;
line-height: normal;
margin: .25rem 0;
}
.info-title {
color: var(--mud-palette-gray-darker);
font-weight: 800;
}
.info-text {
color: var(--mud-palette-text-secondary);
font-weight: 700;
font-size: small;
}
.content ::deep .user-button { border: 1px solid var(--card-border-color) !important; }
.user-button > i { font-size: large; }
.user-button > span {
font-size: medium;
font-weight: 600;
}
.status { font-weight: 700; }
.status.online { color: var(--mud-palette-success); }
.status.offline { color: var(--mud-palette-error); }
.container-button {
width: 100%;
box-shadow: var(--custom-box-shadow);
padding: .25rem 0;
border-radius: 16px;
}
.container-button .divider {
margin: .5rem 0 .5rem 3rem;
width: unset;
}
.container-button ::deep .button-settings { border: none !important; }
.container-button ::deep .button-settings .mud-icon-root {
border-radius: 6px;
padding: 2px;
min-width: 25px;
min-height: 25px;
}
.container-button ::deep .button-settings.infoText { color: var(--mud-palette-info); }
.container-button ::deep .button-settings.green-icon .mud-icon-root {
border: 1px solid var(--mud-palette-success);
background: hsl(from var(--mud-palette-success-lighten) h s 95%);
color: var(--mud-palette-success-darken);
}
.container-button ::deep .button-settings.red-icon .mud-icon-root {
border: 1px solid var(--mud-palette-error);
background: hsl(from var(--mud-palette-error-lighten) h s 95%);
color: var(--mud-palette-error-darken);
}
.container-button ::deep .button-settings .mud-button-label {
justify-content: flex-start;
text-transform: capitalize;
font-size: 1rem;
}
.container-button ::deep .button-settings.exit { padding: 0; }
.container-button ::deep .button-settings.exit .mud-button-label {
justify-content: center;
font-size: 1.1rem;
line-height: normal;
}
.container-pers-rif {
width: 100%;
display: flex;
flex-direction: column;
gap: 1rem;
margin-bottom: 1rem;
box-shadow: var(--custom-box-shadow);
border-radius: 16px;
}
.container-pers-rif .divider {
margin: 0 0 0 3.5rem;
width: unset;
}

View File

@@ -0,0 +1,117 @@
@page "/Users"
@attribute [Authorize]
@using salesbook.Shared.Components.Layout
@using salesbook.Shared.Core.Entity
@using salesbook.Shared.Core.Interface
@inject IManageDataService ManageData
<HeaderLayout Title="Contatti"/>
<div class="container search-box">
<div class="input-card clearButton">
<MudTextField T="string?" Placeholder="Cerca..." Variant="Variant.Text" @bind-Value="TextToFilter" OnDebounceIntervalElapsed="FilterUsers" DebounceInterval="500"/>
@if (!TextToFilter.IsNullOrEmpty())
{
<MudIconButton Class="closeIcon" Icon="@Icons.Material.Filled.Close" OnClick="() => FilterUsers(true)"/>
}
</div>
</div>
<div class="container users">
@if (GroupedUserList?.Count > 0)
{
<Virtualize Items="FilteredGroupedUserList" Context="item">
@if (item.ShowHeader)
{
<div class="letter-header">@item.HeaderLetter</div>
}
<UserCard User="item.User"/>
</Virtualize>
}
</div>
@code {
private List<UserDisplayItem> GroupedUserList { get; set; } = [];
private List<UserDisplayItem> FilteredGroupedUserList { get; set; } = [];
private string? TextToFilter { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await LoadData();
StateHasChanged();
}
}
private async Task LoadData()
{
var users = await ManageData.GetTable<AnagClie>(x => x.FlagStato.Equals("A"));
var sortedUsers = users
.Where(u => !string.IsNullOrWhiteSpace(u.RagSoc))
.OrderBy(u =>
{
var firstChar = char.ToUpper(u.RagSoc[0]);
return char.IsLetter(firstChar) ? firstChar.ToString() : "ZZZ";
})
.ThenBy(u => u.RagSoc)
.ToList();
GroupedUserList = [];
string? lastHeader = null;
foreach (var user in sortedUsers)
{
var firstChar = char.ToUpper(user.RagSoc[0]);
var currentLetter = char.IsLetter(firstChar) ? firstChar.ToString() : "#";
var showHeader = currentLetter != lastHeader;
lastHeader = currentLetter;
GroupedUserList.Add(new UserDisplayItem
{
User = user,
ShowHeader = showHeader,
HeaderLetter = currentLetter
});
}
FilterUsers(true);
}
private class UserDisplayItem
{
public required AnagClie User { get; set; }
public bool ShowHeader { get; set; }
public string? HeaderLetter { get; set; }
}
private void FilterUsers() => FilterUsers(false);
private void FilterUsers(bool clearFilter)
{
if (clearFilter)
{
TextToFilter = null;
FilteredGroupedUserList = GroupedUserList;
StateHasChanged();
return;
}
if (TextToFilter == null) return;
FilteredGroupedUserList = GroupedUserList.FindAll(x =>
x.User.RagSoc.Contains(TextToFilter, StringComparison.OrdinalIgnoreCase) ||
x.User.Indirizzo.Contains(TextToFilter, StringComparison.OrdinalIgnoreCase) ||
(x.User.Telefono != null && x.User.Telefono.Contains(TextToFilter, StringComparison.OrdinalIgnoreCase)) ||
(x.User.EMail != null && x.User.EMail.Contains(TextToFilter, StringComparison.OrdinalIgnoreCase)) ||
x.User.PartIva.Contains(TextToFilter, StringComparison.OrdinalIgnoreCase)
);
StateHasChanged();
}
}

View File

@@ -0,0 +1,33 @@
.users {
display: flex;
gap: 1rem;
overflow-y: auto;
flex-direction: column;
-ms-overflow-style: none;
scrollbar-width: none;
padding-bottom: 70px;
height: 100%;
}
.users .divider {
margin: .1rem 0;
margin-left: 3rem;
}
.users .input-card { margin: 0 !important; }
.letter-header {
border-bottom: 1px solid var(--card-border-color);
font-weight: bolder;
}
.search-box {
position: sticky;
top: 0;
background: var(--mud-palette-surface);
padding-bottom: .5rem;
}
.search-box .input-card {
margin: 0 !important;
}

View File

@@ -0,0 +1,48 @@
@using salesbook.Shared.Components.SingleElements
@inject NavigationManager NavigationManager
<ErrorBoundary @ref="ErrorBoundary">
<ChildContent>
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(Routes).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(Layout.MainLayout)">
<Authorizing>
<p>Authorizing page</p>
</Authorizing>
<NotAuthorized>
@if (context.User.Identity?.IsAuthenticated != true)
{
NavigationManager.NavigateTo("/login");
}
else
{
<p role="alert">You are not authorized to access this resource.</p>
}
</NotAuthorized>
</AuthorizeRouteView>
<FocusOnNavigate RouteData="@routeData" Selector="h1"/>
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView>
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
</ChildContent>
<ErrorContent>
<ExceptionModal @ref="ExceptionModal"
Exception="@context"
ErrorBoundary="@ErrorBoundary"
OnRetry="() => ErrorBoundary?.Recover()"/>
</ErrorContent>
</ErrorBoundary>
@code {
private ErrorBoundary? ErrorBoundary { get; set; }
private ExceptionModal ExceptionModal { get; set; }
}

View File

@@ -0,0 +1,151 @@
@using salesbook.Shared.Core.Dto
@using salesbook.Shared.Core.Entity
@using salesbook.Shared.Core.Helpers.Enum
@using salesbook.Shared.Core.Interface
@inject IManageDataService manageData
<div class="bottom-sheet-backdrop @(IsSheetVisible ? "show" : "")" @onclick="CloseBottomSheet"></div>
<div class="bottom-sheet-container @(IsSheetVisible ? "show" : "")">
<div class="bottom-sheet pb-safe-area">
<div class="title">
<MudText Typo="Typo.h6">
<b>Filtri</b>
</MudText>
<MudIconButton Icon="@Icons.Material.Filled.Close" OnClick="CloseBottomSheet"/>
</div>
<div class="input-card clearButton">
<MudTextField T="string?" Placeholder="Cerca..." Variant="Variant.Text" @bind-Value="Filter.Text" DebounceInterval="500"/>
<MudIconButton Class="closeIcon" Icon="@Icons.Material.Filled.Close" OnClick="() => Filter.Text = null"/>
</div>
<div class="input-card">
<div class="form-container">
<span class="disable-full-width">Assegnata a</span>
<MudSelectExtended SearchBox="true"
ItemCollection="Users.Select(x => x.UserName).ToList()"
SelectAllPosition="SelectAllPosition.NextToSearchBox"
SelectAll="true"
NoWrap="true"
MultiSelection="true"
MultiSelectionTextFunc="@(new Func<List<string>, string>(GetMultiSelectionUser))"
FullWidth="true" T="string"
Variant="Variant.Text"
Virtualize="true"
@bind-SelectedValues="Filter.User"
Class="customIcon-select"
AdornmentIcon="@Icons.Material.Filled.Code"/>
</div>
<div class="divider"></div>
<div class="form-container">
<span class="disable-full-width">Tipo</span>
<MudSelectExtended FullWidth="true"
T="string?"
Variant="Variant.Text"
@bind-Value="Filter.Type"
Class="customIcon-select"
AdornmentIcon="@Icons.Material.Filled.Code">
@foreach (var type in ActivityType)
{
<MudSelectItemExtended Class="custom-item-select" Value="@type.ActivityTypeId">@type.ActivityTypeId</MudSelectItemExtended>
}
</MudSelectExtended>
</div>
<div class="divider"></div>
<div class="form-container">
<span class="disable-full-width">Esito</span>
<MudSelectExtended FullWidth="true"
T="string?"
Variant="Variant.Text"
@bind-Value="Filter.Result"
Class="customIcon-select"
AdornmentIcon="@Icons.Material.Filled.Code">
@foreach (var result in ActivityResult)
{
<MudSelectItemExtended Class="custom-item-select" Value="@result.ActivityResultId">@result.ActivityResultId</MudSelectItemExtended>
}
</MudSelectExtended>
</div>
<div class="divider"></div>
<div class="form-container">
<span class="disable-full-width">Categoria</span>
<MudSelectExtended FullWidth="true"
T="ActivityCategoryEnum?"
Variant="Variant.Text"
@bind-Value="Filter.Category"
Class="customIcon-select"
AdornmentIcon="@Icons.Material.Filled.Code">
@foreach (var category in CategoryList)
{
<MudSelectItemExtended T="ActivityCategoryEnum?" Class="custom-item-select" Value="@category">@category.ConvertToHumanReadable()</MudSelectItemExtended>
}
</MudSelectExtended>
</div>
</div>
<div class="button-section">
<MudButton OnClick="() => Filter = new FilterActivityDTO()" Variant="Variant.Outlined" Color="Color.Error">Pulisci</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="OnFilterButton">Filtra</MudButton>
</div>
</div>
</div>
@code {
[Parameter] public bool IsSheetVisible { get; set; }
[Parameter] public EventCallback<bool> IsSheetVisibleChanged { get; set; }
[Parameter] public FilterActivityDTO Filter { get; set; }
[Parameter] public EventCallback<FilterActivityDTO> FilterChanged { get; set; }
private List<StbActivityResult> ActivityResult { get; set; } = [];
private List<StbActivityType> ActivityType { get; set; } = [];
private List<StbUser> Users { get; set; } = [];
private List<ActivityCategoryEnum> CategoryList { get; set; } = [];
protected override async Task OnParametersSetAsync()
{
if (IsSheetVisible)
await LoadData();
}
private string GetMultiSelectionUser(List<string> selectedValues)
{
return $"{selectedValues.Count} Utent{(selectedValues.Count != 1 ? "i selezionati" : "e selezionato")}";
}
private async Task LoadData()
{
Users = await manageData.GetTable<StbUser>();
ActivityResult = await manageData.GetTable<StbActivityResult>();
ActivityType = await manageData.GetTable<StbActivityType>(x => x.FlagTipologia.Equals("A"));
CategoryList = ActivityCategoryHelper.AllActivityCategory;
StateHasChanged();
}
private void CloseBottomSheet()
{
IsSheetVisible = false;
IsSheetVisibleChanged.InvokeAsync(IsSheetVisible);
}
private void OnFilterButton()
{
FilterChanged.InvokeAsync(Filter);
CloseBottomSheet();
}
}

View File

@@ -0,0 +1,151 @@
@using salesbook.Shared.Core.Dto
@using salesbook.Shared.Core.Entity
@using salesbook.Shared.Core.Helpers.Enum
@using salesbook.Shared.Core.Interface
@inject IManageDataService manageData
<div class="bottom-sheet-backdrop @(IsSheetVisible ? "show" : "")" @onclick="CloseBottomSheet"></div>
<div class="bottom-sheet-container @(IsSheetVisible ? "show" : "")">
<div class="bottom-sheet pb-safe-area">
<div class="title">
<MudText Typo="Typo.h6">
<b>Filtri</b>
</MudText>
<MudIconButton Icon="@Icons.Material.Filled.Close" OnClick="CloseBottomSheet"/>
</div>
<div class="input-card clearButton">
<MudTextField T="string?" Placeholder="Cerca..." Variant="Variant.Text" @bind-Value="Filter.Text" DebounceInterval="500"/>
<MudIconButton Class="closeIcon" Icon="@Icons.Material.Filled.Close" OnClick="() => Filter.Text = null"/>
</div>
<div class="input-card">
<div class="form-container">
<span class="disable-full-width">Assegnata a</span>
<MudSelectExtended SearchBox="true"
ItemCollection="Users.Select(x => x.UserName).ToList()"
SelectAllPosition="SelectAllPosition.NextToSearchBox"
SelectAll="true"
NoWrap="true"
MultiSelection="true"
MultiSelectionTextFunc="@(new Func<List<string>, string>(GetMultiSelectionUser))"
FullWidth="true" T="string"
Variant="Variant.Text"
Virtualize="true"
@bind-SelectedValues="Filter.User"
Class="customIcon-select"
AdornmentIcon="@Icons.Material.Filled.Code"/>
</div>
<div class="divider"></div>
<div class="form-container">
<span class="disable-full-width">Tipo</span>
<MudSelectExtended FullWidth="true"
T="string?"
Variant="Variant.Text"
@bind-Value="Filter.Type"
Class="customIcon-select"
AdornmentIcon="@Icons.Material.Filled.Code">
@foreach (var type in ActivityType)
{
<MudSelectItemExtended Class="custom-item-select" Value="@type.ActivityTypeId">@type.ActivityTypeId</MudSelectItemExtended>
}
</MudSelectExtended>
</div>
<div class="divider"></div>
<div class="form-container">
<span class="disable-full-width">Esito</span>
<MudSelectExtended FullWidth="true"
T="string?"
Variant="Variant.Text"
@bind-Value="Filter.Result"
Class="customIcon-select"
AdornmentIcon="@Icons.Material.Filled.Code">
@foreach (var result in ActivityResult)
{
<MudSelectItemExtended Class="custom-item-select" Value="@result.ActivityResultId">@result.ActivityResultId</MudSelectItemExtended>
}
</MudSelectExtended>
</div>
<div class="divider"></div>
<div class="form-container">
<span class="disable-full-width">Categoria</span>
<MudSelectExtended FullWidth="true"
T="ActivityCategoryEnum?"
Variant="Variant.Text"
@bind-Value="Filter.Category"
Class="customIcon-select"
AdornmentIcon="@Icons.Material.Filled.Code">
@foreach (var category in CategoryList)
{
<MudSelectItemExtended T="ActivityCategoryEnum?" Class="custom-item-select" Value="@category">@category.ConvertToHumanReadable()</MudSelectItemExtended>
}
</MudSelectExtended>
</div>
</div>
<div class="button-section">
<MudButton OnClick="() => Filter = new FilterActivityDTO()" Variant="Variant.Outlined" Color="Color.Error">Pulisci</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="OnFilterButton">Filtra</MudButton>
</div>
</div>
</div>
@code {
[Parameter] public bool IsSheetVisible { get; set; }
[Parameter] public EventCallback<bool> IsSheetVisibleChanged { get; set; }
[Parameter] public FilterActivityDTO Filter { get; set; }
[Parameter] public EventCallback<FilterActivityDTO> FilterChanged { get; set; }
private List<StbActivityResult> ActivityResult { get; set; } = [];
private List<StbActivityType> ActivityType { get; set; } = [];
private List<StbUser> Users { get; set; } = [];
private List<ActivityCategoryEnum> CategoryList { get; set; } = [];
protected override async Task OnParametersSetAsync()
{
if (IsSheetVisible)
await LoadData();
}
private string GetMultiSelectionUser(List<string> selectedValues)
{
return $"{selectedValues.Count} Utent{(selectedValues.Count != 1 ? "i selezionati" : "e selezionato")}";
}
private async Task LoadData()
{
Users = await manageData.GetTable<StbUser>();
ActivityResult = await manageData.GetTable<StbActivityResult>();
ActivityType = await manageData.GetTable<StbActivityType>(x => x.FlagTipologia.Equals("A"));
CategoryList = ActivityCategoryHelper.AllActivityCategory;
StateHasChanged();
}
private void CloseBottomSheet()
{
IsSheetVisible = false;
IsSheetVisibleChanged.InvokeAsync(IsSheetVisible);
}
private void OnFilterButton()
{
FilterChanged.InvokeAsync(Filter);
CloseBottomSheet();
}
}

View File

@@ -0,0 +1,109 @@
@using salesbook.Shared.Core.Dto
@using salesbook.Shared.Core.Entity
@using salesbook.Shared.Core.Interface
@inject IManageDataService ManageData
<div class="bottom-sheet-backdrop @(IsSheetVisible ? "show" : "")" @onclick="CloseBottomSheet"></div>
<div class="bottom-sheet-container @(IsSheetVisible ? "show" : "")">
<div class="bottom-sheet pb-safe-area">
<div class="title">
<MudText Typo="Typo.h6">
<b>Esito</b>
</MudText>
<MudIconButton Icon="@Icons.Material.Filled.Close" OnClick="CloseBottomSheet"/>
</div>
<div class="input-card">
<div class="form-container">
<span>Data effettiva</span>
<MudTextField T="DateTime?" Format="yyyy-MM-dd" InputType="InputType.Date" @bind-Value="EffectiveDate" />
</div>
<div class="divider"></div>
<div class="form-container">
<span>Inizio</span>
<MudTextField T="TimeSpan" InputType="InputType.Time" @bind-Value="EffectiveTime" />
</div>
<div class="divider"></div>
<div class="form-container">
<span>Fine</span>
<MudTextField T="TimeSpan" InputType="InputType.Time" @bind-Value="EffectiveEndTime" />
</div>
</div>
<div class="input-card">
<div class="form-container">
<span class="disable-full-width">Esito</span>
<MudSelectExtended FullWidth="true" T="string?" Variant="Variant.Text" @bind-Value="ActivityModel.ActivityResultId" Class="customIcon-select" AdornmentIcon="@Icons.Material.Filled.Code">
@foreach (var result in ActivityResult)
{
<MudSelectItemExtended Class="custom-item-select" Value="@result.ActivityResultId">@result.ActivityResultId</MudSelectItemExtended>
}
</MudSelectExtended>
</div>
</div>
<div class="input-card">
<MudTextField T="string?" Placeholder="Descrizione esito" Variant="Variant.Text" Lines="4" @bind-Value="ActivityModel.ResultDescription" />
</div>
<div class="button-section">
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="CloseBottomSheet">Salva</MudButton>
</div>
</div>
</div>
@code {
[Parameter] public bool IsSheetVisible { get; set; }
[Parameter] public EventCallback<bool> IsSheetVisibleChanged { get; set; }
[Parameter] public ActivityDTO ActivityModel { get; set; }
[Parameter] public EventCallback<ActivityDTO> ActivityModelChanged { get; set; }
private List<StbActivityResult> ActivityResult { get; set; } = [];
private DateTime? EffectiveDate { get; set; } = DateTime.Today;
private TimeSpan EffectiveTime { get; set; }
private TimeSpan EffectiveEndTime { get; set; }
protected override async Task OnParametersSetAsync()
{
if (IsSheetVisible)
await LoadData();
}
private async Task LoadData()
{
ActivityResult = await ManageData.GetTable<StbActivityResult>();
EffectiveTime = ActivityModel.EffectiveTime?.TimeOfDay ?? TimeSpan.Zero;
EffectiveEndTime = ActivityModel.EffectiveEndtime?.TimeOfDay ?? TimeSpan.Zero;
StateHasChanged();
}
private void CloseBottomSheet()
{
if (EffectiveDate != null)
{
ActivityModel.EffectiveTime = new DateTime(EffectiveDate!.Value.Year, EffectiveDate!.Value.Month, EffectiveDate!.Value.Day,
EffectiveTime.Hours, EffectiveTime.Minutes, EffectiveTime.Seconds);
ActivityModel.EffectiveEndtime = new DateTime(EffectiveDate!.Value.Year, EffectiveDate!.Value.Month, EffectiveDate!.Value.Day,
EffectiveEndTime.Hours, EffectiveEndTime.Minutes, EffectiveEndTime.Seconds);
}
IsSheetVisible = false;
IsSheetVisibleChanged.InvokeAsync(IsSheetVisible);
ActivityModelChanged.InvokeAsync(ActivityModel);
}
}

View File

@@ -0,0 +1,96 @@
@using salesbook.Shared.Core.Dto
@using salesbook.Shared.Core.Entity
@using salesbook.Shared.Core.Helpers.Enum
@inject IDialogService Dialog
<div class="activity-card @Activity.Category.ConvertToHumanReadable()" @onclick="OpenActivity">
<div class="activity-left-section">
<div class="activity-body-section">
<div class="title-section">
<MudText Class="activity-title" Typo="Typo.body1" HtmlTag="h3">
@switch (Activity.Category)
{
case ActivityCategoryEnum.Commessa:
@Activity.Commessa
break;
case ActivityCategoryEnum.Interna:
@Activity.Cliente
break;
case ActivityCategoryEnum.Memo:
@Activity.ActivityDescription
break;
default:
@("")
break;
}
</MudText>
<div class="activity-hours-section">
<span class="activity-hours">
@if (Activity.EffectiveTime is null)
{
@($"{Activity.EstimatedTime:t}")
}
else
{
@($"{Activity.EffectiveTime:t}")
}
</span>
</div>
</div>
@if (Activity.Category != ActivityCategoryEnum.Memo)
{
<MudText Class="activity-subtitle" Typo="Typo.body1" HtmlTag="p">@Activity.ActivityDescription</MudText>
}
</div>
</div>
<div class="activity-info-section">
@if (Durata != null && (Durata.Value.TotalHours > 0 || Durata.Value.Minutes > 0))
{
var ore = (int)Durata.Value.TotalHours;
var minuti = Durata.Value.Minutes;
<MudChip T="string" Icon="@IconConstants.Chip.Time" Color="Color.Dark" Size="Size.Small">
@(ore > 0 ? $"{ore}h{(minuti > 0 ? $" {minuti}m" : "")}" : $"{minuti}m")
</MudChip>
}
@if (Activity.ActivityResultId != null)
{
<MudChip T="string" Icon="@IconConstants.Chip.Stato" Size="Size.Small" Color="Color.Success">@Activity.ActivityResultId</MudChip>
}
<MudChip T="string" Icon="@IconConstants.Chip.User" Size="Size.Small">@Activity.UserName</MudChip>
</div>
</div>
@code {
[Parameter] public ActivityDTO Activity { get; set; } = new();
[Parameter] public EventCallback<string> ActivityChanged { get; set; }
[Parameter] public EventCallback<ActivityDTO> ActivityDeleted { get; set; }
private TimeSpan? Durata { get; set; }
protected override void OnInitialized()
{
Durata = Activity switch
{
{ EffectiveTime: not null, EffectiveEndtime: not null } => Activity.EffectiveEndtime.Value - Activity.EffectiveTime.Value,
{ EstimatedTime: not null, EstimatedEndtime: not null } => Activity.EstimatedEndtime.Value - Activity.EstimatedTime.Value,
_ => null
};
}
private async Task OpenActivity()
{
var result = await ModalHelpers.OpenActivityForm(Dialog, null, Activity.ActivityId);
switch (result)
{
case { Canceled: false, Data: not null } when result.Data.GetType() == typeof(StbActivity):
await ActivityChanged.InvokeAsync(((StbActivity)result.Data).ActivityId);
break;
case { Canceled: false, Data: not null } when result.Data.GetType() == typeof(ActivityDTO):
await ActivityDeleted.InvokeAsync((ActivityDTO)result.Data);
break;
}
}
}

View File

@@ -0,0 +1,59 @@
.activity-card {
width: 100%;
display: flex;
flex-direction: column;
padding: .5rem .5rem;
border-radius: 12px;
line-height: normal;
box-shadow: var(--custom-box-shadow);
}
.activity-card.memo { border-left: 5px solid var(--mud-palette-info-darken); }
.activity-card.interna { border-left: 5px solid var(--mud-palette-success-darken); }
.activity-card.commessa { border-left: 5px solid var(--mud-palette-warning); }
.activity-left-section {
display: flex;
align-items: center;
margin-left: 4px;
}
.title-section {
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
}
.activity-hours {
font-weight: 700;
color: var(--mud-palette-text-primary);
}
.activity-hours-section ::deep .mud-chip { margin: 5px 0 0 !important; }
.activity-body-section {
width: 100%;
display: flex;
flex-direction: column;
}
.title-section ::deep > .activity-title {
font-weight: 800 !important;
margin: 0 !important;
line-height: normal !important;
color: var(--mud-palette-text-primary);
}
.activity-body-section ::deep > .activity-subtitle {
color: var(--mud-palette-gray-darker);
margin: .2rem 0 !important;
line-height: normal !important;
}
.activity-info-section {
display: flex;
flex-wrap: wrap;
}

View File

@@ -0,0 +1,32 @@
@using salesbook.Shared.Core.Entity
<div class="contact-card">
<div class="contact-left-section">
<MudIcon Color="Color.Default" Icon="@Icons.Material.Filled.PersonOutline" Size="Size.Large" />
<div class="contact-body-section">
<div class="title-section">
<MudText Class="contact-title" Typo="Typo.body1" HtmlTag="h3">@UtilityString.FormatString(Contact.PersonaRif).TitleCase</MudText>
</div>
@if (Contact.Mansione is not null)
{
<MudText Class="contact-subtitle" Typo="Typo.body1" HtmlTag="p">@UtilityString.FormatString(Contact.Mansione).SentenceCase</MudText>
}
</div>
</div>
<div class="contact-right-section">
@if (!Contact.NumCellulare.IsNullOrEmpty())
{
<MudIcon Color="Color.Success" Size="Size.Large" Icon="@Icons.Material.Outlined.Phone" />
}
@if (!Contact.EMail.IsNullOrEmpty()){
<MudIcon Color="Color.Info" Size="Size.Large" Icon="@Icons.Material.Filled.MailOutline" />
}
</div>
</div>
@code {
[Parameter] public VtbCliePersRif Contact { get; set; } = new();
}

View File

@@ -0,0 +1,62 @@
.contact-card {
width: 100%;
display: flex;
flex-direction: row;
padding: 0 .75rem;
border-radius: 16px;
line-height: normal;
justify-content: space-between;
align-items: center;
}
.contact-card.memo { border-left: 5px solid var(--mud-palette-info-darken); }
.contact-card.interna { border-left: 5px solid var(--mud-palette-success-darken); }
.contact-card.commessa { border-left: 5px solid var(--mud-palette-warning); }
.contact-left-section {
display: flex;
align-items: center;
gap: 1rem;
}
.title-section {
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
}
.contact-hours { font-weight: 700; }
.contact-hours-section ::deep .mud-chip { margin: 5px 0 0 !important; }
.contact-body-section {
width: 100%;
display: flex;
flex-direction: column;
}
.title-section ::deep > .contact-title {
font-weight: 700 !important;
margin: 0 !important;
line-height: normal !important;
}
.contact-body-section ::deep > .contact-subtitle {
color: var(--mud-palette-gray-darker);
margin: .2rem 0 !important;
line-height: normal !important;
}
.contact-info-section {
display: flex;
flex-wrap: wrap;
}
.contact-right-section {
display: flex;
flex-direction: row;
gap: 1rem;
}

View File

@@ -0,0 +1,74 @@
@using salesbook.Shared.Core.Entity
@using salesbook.Shared.Core.Interface
@inject IManageDataService ManageData
<div class="user-card-card">
<div class="user-card-left-section">
<div class="user-card-body-section">
<div class="title-section">
<MudIcon @onclick="OpenUser" Color="Color.Primary" Icon="@Icons.Material.Filled.Person" Size="Size.Large" />
<div class="user-card-right-section">
<div class="user-card-title">
<MudText Typo="Typo.body1" @onclick="OpenUser" HtmlTag="h3">@User.RagSoc</MudText>
<MudIcon @onclick="ShowCommesse" Color="Color.Info" Icon="@Icons.Material.Outlined.Info" Size="Size.Medium"/>
</div>
<div class="user-card-subtitle @(ShowSectionCommesse ? "show" : "")">
@if (ShowSectionCommesse && IsLoading)
{
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Class="my-7" />
}
else
{
@if (!Commesse.IsNullOrEmpty())
{
<div @onclick="OpenUser" class="container-commesse">
@foreach (var commessa in Commesse!)
{
<div class="commessa">
<span>@($"{commessa.CodJcom} - {commessa.Descrizione}")</span>
</div>
}
</div>
}
else
{
<div class="commessa">
<span>Nessuna commessa presente</span>
</div>
}
}
</div>
</div>
</div>
</div>
</div>
</div>
@code {
[Parameter] public AnagClie User { get; set; } = new();
private List<JtbComt>? Commesse { get; set; }
private bool IsLoading { get; set; } = true;
private bool ShowSectionCommesse { get; set; }
private void OpenUser() =>
NavigationManager.NavigateTo($"/User/{User.CodAnag}");
private async Task ShowCommesse()
{
ShowSectionCommesse = !ShowSectionCommesse;
if (ShowSectionCommesse)
{
Commesse = await ManageData.GetTable<JtbComt>(x => x.CodAnag.Equals(User.CodAnag));
IsLoading = false;
StateHasChanged();
return;
}
IsLoading = true;
}
}

View File

@@ -0,0 +1,90 @@
.user-card-card { width: 100%; }
.user-card-card.memo { border-left: 5px solid var(--mud-palette-info-darken); }
.user-card-card.interna { border-left: 5px solid var(--mud-palette-success-darken); }
.user-card-card.commessa { border-left: 5px solid var(--mud-palette-warning); }
.user-card-left-section {
display: flex;
align-items: center;
}
.title-section {
display: flex;
flex-direction: row;
width: 100%;
align-items: center;
gap: .75rem;
}
.user-card-hours { font-weight: 700; }
.user-card-hours-section ::deep .mud-chip { margin: 5px 0 0 !important; }
.user-card-body-section {
width: 100%;
display: flex;
flex-direction: column;
}
.user-card-right-section {
width: 100%;
border-bottom: 1px solid var(--card-border-color);
}
.user-card-title {
display: flex;
align-items: center;
min-height: 32px;
padding-left: .25rem;
justify-content: space-between;
}
.user-card-title ::deep > h3 {
font-weight: 700 !important;
margin: 0 !important;
line-height: normal !important;
font-size: .85rem;
}
.user-card-body-section ::deep > .user-card-subtitle {
color: var(--mud-palette-gray-darker);
margin: .2rem 0 !important;
line-height: normal !important;
}
.user-card-info-section {
display: flex;
flex-wrap: wrap;
}
.title-section ::deep > .mud-icon-root {
background: var(--mud-palette-gray-lighter);
padding: .25rem;
border-radius: 50%;
}
.user-card-subtitle {
overflow: hidden;
max-height: 0;
opacity: 0;
transition: max-height 0.4s ease, opacity 0.4s ease;
margin-top: 0;
}
.user-card-subtitle.show {
max-height: 250px;
opacity: 1;
}
.container-commesse {
padding: .5rem;
}
.commessa {
color: var(--mud-palette-text-secondary);
font-weight: 700;
font-size: small;
}

View File

@@ -0,0 +1,62 @@
@using salesbook.Shared.Core.Services
@inject AppAuthenticationStateProvider AuthenticationStateProvider
<div class="container container-modal">
<div class="c-modal">
<div class="exception-header mb-2">
<i class="ri-emotion-unhappy-line"></i>
<span>Ops</span>
</div>
<div class="text">
@if (Exception != null)
{
<code>@Message</code>
}
</div>
<div class="button-container">
<div @onclick="OnRetryClick" class="card-button">
<span>Riprova</span>
</div>
<div @onclick="OnContinueClick" class="card-button">
<span>Continua</span>
</div>
</div>
</div>
</div>
@code {
[Parameter] public Exception? Exception { get; set; }
[Parameter] public EventCallback OnRetry { get; set; }
[Parameter] public ErrorBoundary? ErrorBoundary { get; set; }
private string Message { get; set; } = "";
protected override void OnInitialized()
{
if (Exception == null) return;
if (Exception.Message.Contains("Failed to connect to"))
{
var ipPort = Exception.Message.Split("to /")[1];
Message = $"Impossibile collegarsi al server ({ipPort})";
}
else
{
Message = Exception.Message;
}
StateHasChanged();
}
private async Task OnRetryClick()
{
await OnRetry.InvokeAsync();
}
private async Task OnContinueClick()
{
NavigationManager.NavigateTo("/");
await OnRetry.InvokeAsync();
}
}

View File

@@ -0,0 +1,61 @@
.container-modal {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
}
.c-modal {
border-radius: 16px;
box-shadow: var(--exception-box-shadow);
padding: 16px;
}
.button-container {
display: flex;
gap: 1rem;
flex-direction: row;
align-items: center;
width: 100%;
justify-content: space-between;
margin: 1.5rem 0 0 0;
}
.text {
font-size: medium;
font-weight: 500;
display: flex;
text-align: center;
}
.card-button {
text-align: center;
background-color: transparent;
padding: .3rem 1rem;
font-weight: 700;
color: var(--bs-primary-text-emphasis);
}
.exception-header {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.exception-header > i {
font-size: 3rem;
line-height: normal;
color: var(--bs-danger);
}
.exception-header > span {
font-size: x-large;
font-weight: 700;
}
code {
width: 100%;
height: auto;
color: var(--bs-gray-dark);
}

View File

@@ -0,0 +1,371 @@
@using System.Globalization
@using CommunityToolkit.Mvvm.Messaging
@using salesbook.Shared.Core.Dto
@using salesbook.Shared.Components.Layout
@using salesbook.Shared.Core.Entity
@using salesbook.Shared.Core.Interface
@using salesbook.Shared.Components.Layout.Overlay
@using salesbook.Shared.Components.SingleElements.BottomSheet
@using salesbook.Shared.Core.Messages.Activity.Copy
@inject IManageDataService ManageData
@inject INetworkService NetworkService
@inject IIntegryApiService IntegryApiService
@inject IMessenger Messenger
<MudDialog Class="customDialog-form">
<DialogContent>
<HeaderLayout ShowProfile="false" Cancel="true" OnCancel="() => MudDialog.Cancel()" LabelSave="@LabelSave" OnSave="Save" Title="@(IsNew ? "Nuova" : $"{ActivityModel.ActivityId}")"/>
<div class="content">
<div class="input-card">
<MudTextField ReadOnly="IsView" T="string?" Placeholder="Descrizione" Variant="Variant.Text" Lines="3" @bind-Value="ActivityModel.ActivityDescription" @bind-Value:after="OnAfterChangeValue" DebounceInterval="500" OnDebounceIntervalElapsed="OnAfterChangeValue"/>
</div>
<div class="input-card">
<div class="form-container">
<MudAutocomplete ReadOnly="IsView" T="string?" Placeholder="Cliente"
SearchFunc="@SearchCliente" @bind-Value="ActivityModel.Cliente" @bind-Value:after="OnClienteChanged"
CoerceValue="true"/>
</div>
<div class="divider"></div>
<div class="form-container">
<span class="disable-full-width">Commessa</span>
@if (Commesse.IsNullOrEmpty())
{
<span class="warning-text">Nessuna commessa presente</span>
}
else
{
<MudSelectExtended FullWidth="true" ReadOnly="@(IsView || Commesse.IsNullOrEmpty())" T="string?" Variant="Variant.Text" @bind-Value="ActivityModel.CodJcom" @bind-Value:after="OnCommessaChanged" Class="customIcon-select" AdornmentIcon="@Icons.Material.Filled.Code">
@foreach (var com in Commesse)
{
<MudSelectItemExtended Class="custom-item-select" Value="@com.CodJcom">@($"{com.CodJcom} - {com.Descrizione}")</MudSelectItemExtended>
}
</MudSelectExtended>
}
</div>
</div>
<div class="input-card">
<div class="form-container">
<span>Inizio</span>
<MudTextField ReadOnly="IsView" T="DateTime?" Format="s" Culture="CultureInfo.CurrentUICulture" InputType="InputType.DateTimeLocal" @bind-Value="ActivityModel.EstimatedTime" @bind-Value:after="OnAfterChangeValue" DebounceInterval="500" OnDebounceIntervalElapsed="OnAfterChangeValue"/>
</div>
<div class="divider"></div>
<div class="form-container">
<span>Fine</span>
<MudTextField ReadOnly="IsView" T="DateTime?" Format="s" Culture="CultureInfo.CurrentUICulture" InputType="InputType.DateTimeLocal" @bind-Value="ActivityModel.EstimatedEndtime" @bind-Value:after="OnAfterChangeValue" DebounceInterval="500" OnDebounceIntervalElapsed="OnAfterChangeValue"/>
</div>
<div class="divider"></div>
<div class="form-container">
<span>Avviso</span>
<MudSwitch ReadOnly="IsView" T="bool" Disabled="true" Color="Color.Primary"/>
</div>
</div>
<div class="input-card">
<div class="form-container">
<span class="disable-full-width">Assegnata a</span>
<MudSelectExtended FullWidth="true" ReadOnly="IsView" T="string?" Variant="Variant.Text" @bind-Value="ActivityModel.UserName" @bind-Value:after="OnAfterChangeValue" Class="customIcon-select" AdornmentIcon="@Icons.Material.Filled.Code">
@foreach (var user in Users)
{
<MudSelectItemExtended Class="custom-item-select" Value="@user.UserName">@user.FullName</MudSelectItemExtended>
}
</MudSelectExtended>
</div>
<div class="divider"></div>
<div class="form-container">
<span class="disable-full-width">Tipo</span>
<MudSelectExtended ReadOnly="IsView" FullWidth="true" T="string?" Variant="Variant.Text" @bind-Value="ActivityModel.ActivityTypeId" @bind-Value:after="OnAfterChangeValue" Class="customIcon-select" AdornmentIcon="@Icons.Material.Filled.Code">
@foreach (var type in ActivityType)
{
<MudSelectItemExtended Class="custom-item-select" Value="@type.ActivityTypeId">@type.ActivityTypeId</MudSelectItemExtended>
}
</MudSelectExtended>
</div>
<div class="divider"></div>
<div class="form-container" @onclick="OpenSelectEsito">
<span class="disable-full-width">Esito</span>
<MudSelectExtended ReadOnly="true" FullWidth="true" T="string?" Variant="Variant.Text" @bind-Value="ActivityModel.ActivityResultId" @bind-Value:after="OnAfterChangeValue" Class="customIcon-select" AdornmentIcon="@Icons.Material.Filled.Code">
@foreach (var result in ActivityResult)
{
<MudSelectItemExtended Class="custom-item-select" Value="@result.ActivityResultId">@result.ActivityResultId</MudSelectItemExtended>
}
</MudSelectExtended>
</div>
</div>
<div class="input-card">
<MudTextField ReadOnly="IsView" T="string?" Placeholder="Note" Variant="Variant.Text" Lines="4" @bind-Value="ActivityModel.Note" @bind-Value:after="OnAfterChangeValue" DebounceInterval="500" OnDebounceIntervalElapsed="OnAfterChangeValue"/>
</div>
@if (!IsNew)
{
<div class="container-button">
<MudButton Class="button-settings gray-icon"
FullWidth="true"
StartIcon="@Icons.Material.Filled.ContentCopy"
Size="Size.Medium"
OnClick="Duplica"
Variant="Variant.Outlined">
Duplica
</MudButton>
<div class="divider"></div>
<MudButton Class="button-settings red-icon"
FullWidth="true"
StartIcon="@Icons.Material.Outlined.Delete"
Size="Size.Medium"
OnClick="DeleteActivity"
Variant="Variant.Outlined">
Elimina
</MudButton>
</div>
}
</div>
<MudMessageBox @ref="ConfirmDelete" Class="c-messageBox" Title="Attenzione!" CancelText="Annulla">
<MessageContent>
Confermi la cancellazione dell'attività corrente?
</MessageContent>
<YesButton>
<MudButton Size="Size.Small" Variant="Variant.Filled" Color="Color.Error"
StartIcon="@Icons.Material.Filled.DeleteForever">
Cancella
</MudButton>
</YesButton>
</MudMessageBox>
</DialogContent>
</MudDialog>
<SaveOverlay VisibleOverlay="VisibleOverlay" SuccessAnimation="SuccessAnimation"/>
<SelectEsito @bind-IsSheetVisible="OpenEsito" @bind-ActivityModel="ActivityModel" @bind-ActivityModel:after="OnAfterChangeValue"/>
@code {
[CascadingParameter] private IMudDialogInstance MudDialog { get; set; }
[Parameter] public string? Id { get; set; }
[Parameter] public ActivityDTO? ActivityCopied { get; set; }
private ActivityDTO OriginalModel { get; set; } = new();
private ActivityDTO ActivityModel { get; set; } = new();
private List<StbActivityResult> ActivityResult { get; set; } = [];
private List<StbActivityType> ActivityType { get; set; } = [];
private List<StbUser> Users { get; set; } = [];
private List<JtbComt> Commesse { get; set; } = [];
private List<AnagClie> Clienti { get; set; } = [];
private List<PtbPros> Pros { get; set; } = [];
private bool IsNew => Id.IsNullOrEmpty();
private bool IsView => !NetworkService.IsNetworkAvailable();
private string? LabelSave { get; set; }
//Overlay for save
private bool VisibleOverlay { get; set; }
private bool SuccessAnimation { get; set; }
private bool OpenEsito { get; set; } = false;
private MudMessageBox ConfirmDelete { get; set; }
protected override async Task OnInitializedAsync()
{
_ = LoadData();
LabelSave = IsNew ? "Aggiungi" : null;
if (!Id.IsNullOrEmpty())
ActivityModel = (await ManageData.GetActivity(x => x.ActivityId.Equals(Id))).Last();
if (ActivityCopied != null)
{
ActivityModel = ActivityCopied.Clone();
}
if (IsNew)
{
ActivityModel.EstimatedTime = DateTime.Today.Add(TimeSpan.FromHours(DateTime.Now.Hour));
ActivityModel.EstimatedEndtime = DateTime.Today.Add(TimeSpan.FromHours(DateTime.Now.Hour) + TimeSpan.FromHours(1));
ActivityModel.UserName = UserSession.User.Username;
}
OriginalModel = ActivityModel.Clone();
}
private async Task Save()
{
if (!CheckPreSave()) return;
VisibleOverlay = true;
StateHasChanged();
var response = await IntegryApiService.SaveActivity(ActivityModel);
if (response == null)
return;
var newActivity = response.Last();
await ManageData.InsertOrUpdate(newActivity);
SuccessAnimation = true;
StateHasChanged();
await Task.Delay(1250);
MudDialog.Close(newActivity);
}
private bool CheckPreSave()
{
Snackbar.Clear();
Snackbar.Configuration.PositionClass = Defaults.Classes.Position.TopCenter;
if (!ActivityModel.ActivityTypeId.IsNullOrEmpty()) return true;
Snackbar.Add("Tipo attività obbligatorio!", Severity.Error);
return false;
}
private async Task LoadData()
{
Users = await ManageData.GetTable<StbUser>();
ActivityResult = await ManageData.GetTable<StbActivityResult>();
Clienti = await ManageData.GetTable<AnagClie>(x => x.FlagStato.Equals("A"));
Pros = await ManageData.GetTable<PtbPros>();
ActivityType = await ManageData.GetTable<StbActivityType>(x => x.FlagTipologia.Equals("A"));
await LoadCommesse();
}
private async Task LoadCommesse() =>
Commesse = await ManageData.GetTable<JtbComt>(x => x.CodAnag != null && x.CodAnag.Equals(ActivityModel.CodAnag));
private async Task<IEnumerable<string>?> SearchCliente(string value, CancellationToken token)
{
if (string.IsNullOrEmpty(value))
return null;
var listToReturn = new List<string>();
listToReturn.AddRange(
Clienti.Where(x => x.RagSoc.Contains(value, StringComparison.OrdinalIgnoreCase)).Select(x => $"{x.CodAnag} - {x.RagSoc}")
);
listToReturn.AddRange(
Pros.Where(x => x.RagSoc.Contains(value, StringComparison.OrdinalIgnoreCase)).Select(x => $"{x.CodPpro} - {x.RagSoc}")
);
return listToReturn;
}
private async Task OnClienteChanged()
{
string? codAnag = null;
ActivityModel.CodJcom = null;
var cliente = Clienti.LastOrDefault(x => ActivityModel.Cliente != null && x.RagSoc.Contains(ActivityModel.Cliente, StringComparison.OrdinalIgnoreCase));
if (cliente is null)
{
var pros = Pros.LastOrDefault(x => ActivityModel.Cliente != null && x.RagSoc.Contains(ActivityModel.Cliente, StringComparison.OrdinalIgnoreCase));
if (pros is not null)
{
codAnag = pros.CodAnag;
}
}
else
{
codAnag = cliente.CodAnag;
}
if (codAnag is not null)
{
ActivityModel.CodAnag = codAnag;
await LoadCommesse();
OnAfterChangeValue();
}
}
private async Task OnCommessaChanged()
{
ActivityModel.Commessa = (await ManageData.GetTable<JtbComt>(x => x.CodJcom.Equals(ActivityModel.CodJcom))).Last().Descrizione;
OnAfterChangeValue();
}
private void OnAfterChangeValue()
{
if (!IsNew)
{
LabelSave = !OriginalModel.Equals(ActivityModel) ? "Aggiorna" : null;
}
}
private void OpenSelectEsito()
{
if (!IsNew && (ActivityModel.UserName is null || !ActivityModel.UserName.Equals(UserSession.User.Username))) return;
OpenEsito = !OpenEsito;
StateHasChanged();
}
private async Task DeleteActivity()
{
var result = await ConfirmDelete.ShowAsync();
if (result is true)
{
VisibleOverlay = true;
StateHasChanged();
try
{
await IntegryApiService.DeleteActivity(ActivityModel.ActivityId);
ActivityModel.Deleted = true;
SuccessAnimation = true;
StateHasChanged();
await Task.Delay(1250);
MudDialog.Close(ActivityModel);
}
catch (Exception e)
{
VisibleOverlay = false;
StateHasChanged();
Snackbar.Add("Impossibile cancellare l'attività", Severity.Error);
}
}
}
private void Duplica()
{
var activityCopy = ActivityModel.Clone();
MudDialog.Cancel();
Messenger.Send(new CopyActivityMessage(activityCopy));
}
}

View File

@@ -0,0 +1,4 @@
.container-button {
background: var(--mud-palette-background-gray) !important;
box-shadow: unset;
}

View File

@@ -0,0 +1,10 @@
<div class="no-data opacity-75 d-flex flex-column align-items-center">
<img src="@ImageSource"/>
<p class="mt-3">@Text</p>
</div>
@code {
[Parameter] public string ImageSource { get; set; } = string.Empty;
[Parameter] public string Text { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,5 @@
.no-data { margin-top: 2rem; }
.no-data img { width: 60%; }
.no-data p { font-size: 1.2rem; }

View File

@@ -0,0 +1,8 @@
namespace salesbook.Shared.Core.Authorization.Enum;
public enum KeyGroupEnum
{
UtenteAziendale = 2,
Agenti = 5,
Tecnico = 22
}

View File

@@ -0,0 +1,100 @@
using salesbook.Shared.Core.Entity;
using salesbook.Shared.Core.Helpers.Enum;
namespace salesbook.Shared.Core.Dto;
public class ActivityDTO : StbActivity
{
public string? Commessa { get; set; }
public string? Cliente { get; set; }
public ActivityCategoryEnum Category { get; set; }
public bool Complete { get; set; }
public bool Deleted { get; set; }
public ActivityDTO Clone()
{
return (ActivityDTO)MemberwiseClone();
}
private bool Equals(ActivityDTO other)
{
return Commessa == other.Commessa &&
Cliente == other.Cliente &&
Category == other.Category &&
Complete == other.Complete && ActivityId == other.ActivityId && ActivityResultId == other.ActivityResultId && ActivityTypeId == other.ActivityTypeId && DataInsAct.Equals(other.DataInsAct) && ActivityDescription == other.ActivityDescription && ParentActivityId == other.ParentActivityId && TipoAnag == other.TipoAnag && CodAnag == other.CodAnag && CodJcom == other.CodJcom && CodJfas == other.CodJfas && Nullable.Equals(EstimatedDate, other.EstimatedDate) && Nullable.Equals(EstimatedTime, other.EstimatedTime) && Nullable.Equals(AlarmDate, other.AlarmDate) && Nullable.Equals(AlarmTime, other.AlarmTime) && Nullable.Equals(EffectiveDate, other.EffectiveDate) && Nullable.Equals(EffectiveTime, other.EffectiveTime) && ResultDescription == other.ResultDescription && Nullable.Equals(EstimatedEnddate, other.EstimatedEnddate) && Nullable.Equals(EstimatedEndtime, other.EstimatedEndtime) && Nullable.Equals(EffectiveEnddate, other.EffectiveEnddate) && Nullable.Equals(EffectiveEndtime, other.EffectiveEndtime) && UserCreator == other.UserCreator && UserName == other.UserName && Nullable.Equals(PercComp, other.PercComp) && Nullable.Equals(EstimatedHours, other.EstimatedHours) && CodMart == other.CodMart && PartitaMag == other.PartitaMag && Matricola == other.Matricola && Priorita == other.Priorita && Nullable.Equals(ActivityPlayCounter, other.ActivityPlayCounter) && ActivityEvent == other.ActivityEvent && Guarantee == other.Guarantee && Note == other.Note && Rfid == other.Rfid && IdLotto == other.IdLotto && PersonaRif == other.PersonaRif && HrNum == other.HrNum && Gestione == other.Gestione && Nullable.Equals(DataOrd, other.DataOrd) && NumOrd == other.NumOrd && IdStep == other.IdStep && IdRiga == other.IdRiga && Nullable.Equals(OraInsAct, other.OraInsAct) && IndiceGradimento == other.IndiceGradimento && NoteGradimento == other.NoteGradimento && FlagRisolto == other.FlagRisolto && FlagTipologia == other.FlagTipologia && OreRapportino == other.OreRapportino && UserModifier == other.UserModifier && Nullable.Equals(OraModAct, other.OraModAct) && Nullable.Equals(OraViewAct, other.OraViewAct) && CodVdes == other.CodVdes && CodCmac == other.CodCmac && WrikeId == other.WrikeId && CodMgrp == other.CodMgrp && PlanId == other.PlanId;
}
public override bool Equals(object? obj)
{
if (obj is null) return false;
if (ReferenceEquals(this, obj)) return true;
return obj.GetType() == GetType() && Equals((ActivityDTO)obj);
}
public override int GetHashCode()
{
var hashCode = new HashCode();
hashCode.Add(ActivityId);
hashCode.Add(ActivityResultId);
hashCode.Add(ActivityTypeId);
hashCode.Add(DataInsAct);
hashCode.Add(ActivityDescription);
hashCode.Add(ParentActivityId);
hashCode.Add(TipoAnag);
hashCode.Add(CodAnag);
hashCode.Add(CodJcom);
hashCode.Add(CodJfas);
hashCode.Add(EstimatedDate);
hashCode.Add(EstimatedTime);
hashCode.Add(AlarmDate);
hashCode.Add(AlarmTime);
hashCode.Add(EffectiveDate);
hashCode.Add(EffectiveTime);
hashCode.Add(ResultDescription);
hashCode.Add(EstimatedEnddate);
hashCode.Add(EstimatedEndtime);
hashCode.Add(EffectiveEnddate);
hashCode.Add(EffectiveEndtime);
hashCode.Add(UserCreator);
hashCode.Add(UserName);
hashCode.Add(PercComp);
hashCode.Add(EstimatedHours);
hashCode.Add(CodMart);
hashCode.Add(PartitaMag);
hashCode.Add(Matricola);
hashCode.Add(Priorita);
hashCode.Add(ActivityPlayCounter);
hashCode.Add(ActivityEvent);
hashCode.Add(Guarantee);
hashCode.Add(Note);
hashCode.Add(Rfid);
hashCode.Add(IdLotto);
hashCode.Add(PersonaRif);
hashCode.Add(HrNum);
hashCode.Add(Gestione);
hashCode.Add(DataOrd);
hashCode.Add(NumOrd);
hashCode.Add(IdStep);
hashCode.Add(IdRiga);
hashCode.Add(OraInsAct);
hashCode.Add(IndiceGradimento);
hashCode.Add(NoteGradimento);
hashCode.Add(FlagRisolto);
hashCode.Add(FlagTipologia);
hashCode.Add(OreRapportino);
hashCode.Add(UserModifier);
hashCode.Add(OraModAct);
hashCode.Add(OraViewAct);
hashCode.Add(CodVdes);
hashCode.Add(CodCmac);
hashCode.Add(WrikeId);
hashCode.Add(CodMgrp);
hashCode.Add(PlanId);
hashCode.Add(Commessa);
hashCode.Add(Cliente);
hashCode.Add(Category);
hashCode.Add(Complete);
return hashCode.ToHashCode();
}
}

View File

@@ -0,0 +1,20 @@
using salesbook.Shared.Core.Helpers;
using salesbook.Shared.Core.Helpers.Enum;
namespace salesbook.Shared.Core.Dto;
public class FilterActivityDTO
{
public string? Text { get; set; }
public IEnumerable<string>? User { get; set; }
public string? Type { get; set; }
public string? Result { get; set; }
public ActivityCategoryEnum? Category { get; set; }
public bool ClearFilter =>
Text.IsNullOrEmpty() &&
User.IsNullOrEmpty() &&
Type.IsNullOrEmpty() &&
Result.IsNullOrEmpty() &&
Category == null;
}

View File

@@ -0,0 +1,16 @@
using System.Text.Json.Serialization;
using salesbook.Shared.Core.Entity;
namespace salesbook.Shared.Core.Dto;
public class SettingsResponseDTO
{
[JsonPropertyName("activityTypes")]
public List<StbActivityType>? ActivityTypes { get; set; }
[JsonPropertyName("activityResults")]
public List<StbActivityResult>? ActivityResults { get; set; }
[JsonPropertyName("stbUsers")]
public List<StbUser>? StbUsers { get; set; }
}

View File

@@ -0,0 +1,22 @@
using System.Text.Json.Serialization;
using salesbook.Shared.Core.Entity;
namespace salesbook.Shared.Core.Dto;
public class TaskSyncResponseDTO
{
[JsonPropertyName("anagClie")]
public List<AnagClie>? AnagClie { get; set; }
[JsonPropertyName("vtbDest")]
public List<VtbDest>? VtbDest { get; set; }
[JsonPropertyName("vtbCliePersRif")]
public List<VtbCliePersRif>? VtbCliePersRif { get; set; }
[JsonPropertyName("ptbPros")]
public List<PtbPros>? PtbPros { get; set; }
[JsonPropertyName("ptbProsRif")]
public List<PtbProsRif>? PtbProsRif { get; set; }
}

View File

@@ -0,0 +1,86 @@
using SQLite;
using System.Text.Json.Serialization;
namespace salesbook.Shared.Core.Entity;
[Table("anag_clie")]
public class AnagClie
{
[PrimaryKey, Column("cod_anag"), JsonPropertyName("codAnag")]
public string CodAnag { get; set; }
[Column("cod_vtip"), JsonPropertyName("codVtip")]
public string? CodVtip { get; set; }
[Column("cod_vage"), JsonPropertyName("codVage")]
public string? CodVage { get; set; }
[Column("rag_soc"), JsonPropertyName("ragSoc")]
public string RagSoc { get; set; }
[Column("indirizzo"), JsonPropertyName("indirizzo")]
public string Indirizzo { get; set; }
[Column("cap"), JsonPropertyName("cap")]
public string Cap { get; set; }
[Column("citta"), JsonPropertyName("citta")]
public string Citta { get; set; }
[Column("prov"), JsonPropertyName("prov")]
public string Prov { get; set; }
[Column("nazione"), JsonPropertyName("nazione")]
public string Nazione { get; set; }
[Column("telefono"), JsonPropertyName("telefono")]
public string? Telefono { get; set; }
[Column("fax"), JsonPropertyName("fax")]
public string Fax { get; set; }
[Column("part_iva"), JsonPropertyName("partIva")]
public string PartIva { get; set; }
[Column("cod_fisc"), JsonPropertyName("codFisc")]
public string CodFisc { get; set; }
[Column("note"), JsonPropertyName("note")]
public string Note { get; set; }
[Column("persona_rif"), JsonPropertyName("personaRif")]
public string PersonaRif { get; set; }
[Column("e_mail"), JsonPropertyName("eMail")]
public string? EMail { get; set; }
[Column("e_mail_pec"), JsonPropertyName("eMailPec")]
public string EMailPec { get; set; }
[Column("nome"), JsonPropertyName("nome")]
public string Nome { get; set; }
[Column("data_ins"), JsonPropertyName("dataIns")]
public DateTime? DataIns { get; set; } = DateTime.Now;
[Column("num_cell"), JsonPropertyName("numCell")]
public string NumCell { get; set; }
[Column("cognome"), JsonPropertyName("cognome")]
public string Cognome { get; set; }
[Column("diacod"), JsonPropertyName("diacod")]
public string Diacod { get; set; }
[Column("lat"), JsonPropertyName("lat")]
public decimal? Lat { get; set; }
[Column("lng"), JsonPropertyName("lng")]
public decimal? Lng { get; set; }
[Column("data_mod"), JsonPropertyName("dataMod")]
public DateTime? DataMod { get; set; } = DateTime.Now;
[Column("flag_stato"), JsonPropertyName("flagStato")]
public string FlagStato { get; set; }
}

View File

@@ -0,0 +1,110 @@
using System.Text.Json.Serialization;
using SQLite;
namespace salesbook.Shared.Core.Entity;
[Table("jtb_comt")]
public class JtbComt
{
[PrimaryKey, Column("cod_jcom"), JsonPropertyName("codJcom")]
public string CodJcom { get; set; }
[Column("cod_jfas"), JsonPropertyName("codJfas")]
public string CodJfas { get; set; }
[Column("cod_jflav"), JsonPropertyName("codJflav")]
public string CodJflav { get; set; }
[Column("descrizione"), JsonPropertyName("descrizione")]
public string Descrizione { get; set; }
[Column("importo"), JsonPropertyName("importo")]
public decimal Importo { get; set; } = 0;
[Column("data_inizi_lav"), JsonPropertyName("dataIniziLav")]
public DateTime? DataIniziLav { get; set; }
[Column("cod_mart"), JsonPropertyName("codMart")]
public string CodMart { get; set; }
[Column("data_cons"), JsonPropertyName("dataCons")]
public DateTime? DataCons { get; set; }
[Column("manuali"), JsonPropertyName("manuali")]
public string Manuali { get; set; }
[Column("note"), JsonPropertyName("note")]
public string Note { get; set; }
[Column("cod_anag"), JsonPropertyName("codAnag")]
public string? CodAnag { get; set; }
[Column("cod_divi"), JsonPropertyName("codDivi")]
public string CodDivi { get; set; } = "EURO";
[Column("cambio_divi"), JsonPropertyName("cambioDivi")]
public decimal CambioDivi { get; set; } = 1;
[Column("cod_divi_cont"), JsonPropertyName("codDiviCont")]
public string CodDiviCont { get; set; }
[Column("cambio_divi_cont"), JsonPropertyName("cambioDiviCont")]
public decimal CambioDiviCont { get; set; }
[Column("responsabile_com"), JsonPropertyName("responsabileCom")]
public string ResponsabileCom { get; set; }
[Column("stato_commessa"), JsonPropertyName("statoCommessa")]
public string StatoCommessa { get; set; }
[Column("tipo_commessa"), JsonPropertyName("tipoCommessa")]
public string TipoCommessa { get; set; }
[Column("descrizione_estesa"), JsonPropertyName("descrizioneEstesa")]
public string DescrizioneEstesa { get; set; }
[Column("perc_comp"), JsonPropertyName("percComp")]
public decimal PercComp { get; set; } = 0;
[Column("cod_vdes"), JsonPropertyName("codVdes")]
public string CodVdes { get; set; }
[Column("gestione"), JsonPropertyName("gestione")]
public string Gestione { get; set; }
[Column("data_ord"), JsonPropertyName("dataOrd")]
public DateTime? DataOrd { get; set; }
[Column("num_ord"), JsonPropertyName("numOrd")]
public int? NumOrd { get; set; }
[Column("matricola"), JsonPropertyName("matricola")]
public string Matricola { get; set; }
[Column("tipo_anag"), JsonPropertyName("tipoAnag")]
public string TipoAnag { get; set; }
[Column("flag_pubblica"), JsonPropertyName("flagPubblica")]
public string FlagPubblica { get; set; } = "N";
[Column("cig"), JsonPropertyName("cig")]
public string Cig { get; set; }
[Column("cup"), JsonPropertyName("cup")]
public string Cup { get; set; }
[Column("indirizzo_ente"), JsonPropertyName("indirizzoEnte")]
public string IndirizzoEnte { get; set; }
[Column("note_cons"), JsonPropertyName("noteCons")]
public string NoteCons { get; set; }
[Column("cod_vage"), JsonPropertyName("codVage")]
public string CodVage { get; set; }
[Column("cod_jflav_tec"), JsonPropertyName("codJflavTec")]
public string CodJflavTec { get; set; }
[Column("note_tecniche"), JsonPropertyName("noteTecniche")]
public string NoteTecniche { get; set; }
}

View File

@@ -0,0 +1,131 @@
using SQLite;
using System.Text.Json.Serialization;
namespace salesbook.Shared.Core.Entity;
[Table("ptb_pros")]
public class PtbPros
{
[PrimaryKey, Column("cod_ppro"), JsonPropertyName("codPpro")]
public string CodPpro { get; set; }
[Column("agenzia_banca"), JsonPropertyName("agenziaBanca")]
public string AgenziaBanca { get; set; }
[Column("cap"), JsonPropertyName("cap")]
public string Cap { get; set; }
[Column("citta"), JsonPropertyName("citta")]
public string Citta { get; set; }
[Column("cod_abi"), JsonPropertyName("codAbi")]
public string CodAbi { get; set; }
[Column("cod_aliq"), JsonPropertyName("codAliq")]
public string CodAliq { get; set; }
[Column("cod_anag"), JsonPropertyName("codAnag")]
public string CodAnag { get; set; }
[Column("cod_banc"), JsonPropertyName("codBanc")]
public string CodBanc { get; set; }
[Column("cod_cab"), JsonPropertyName("codCab")]
public string CodCab { get; set; }
[Column("cod_fisc"), JsonPropertyName("codFisc")]
public string CodFisc { get; set; }
[Column("cod_paga"), JsonPropertyName("codPaga")]
public string CodPaga { get; set; }
[Column("cod_vage"), JsonPropertyName("codVage")]
public string CodVage { get; set; }
[Column("cod_vatt"), JsonPropertyName("codVatt")]
public string CodVatt { get; set; }
[Column("cod_vlis"), JsonPropertyName("codVlis")]
public string CodVlis { get; set; }
[Column("cod_vseg"), JsonPropertyName("codVseg")]
public string CodVseg { get; set; }
[Column("cod_vset"), JsonPropertyName("codVset")]
public string CodVset { get; set; }
[Column("cod_vtip"), JsonPropertyName("codVtip")]
public string CodVtip { get; set; }
[Column("cod_vzon"), JsonPropertyName("codVzon")]
public string CodVzon { get; set; }
[Column("data_ins"), JsonPropertyName("dataIns")]
public DateTime? DataIns { get; set; } = DateTime.Now;
[Column("descrizione_pag"), JsonPropertyName("descrizionePag")]
public string DescrizionePag { get; set; }
[Column("e_mail"), JsonPropertyName("eMail")]
public string EMail { get; set; }
[Column("fax"), JsonPropertyName("fax")]
public string Fax { get; set; }
[Column("flag_riv_clie"), JsonPropertyName("flagRivClie")]
public string FlagRivClie { get; set; } = "C";
[Column("fonte"), JsonPropertyName("fonte")]
public string Fonte { get; set; }
[Column("gg_chiusura"), JsonPropertyName("ggChiusura")]
public string GgChiusura { get; set; }
[Column("indirizzo"), JsonPropertyName("indirizzo")]
public string Indirizzo { get; set; }
[Column("nazione"), JsonPropertyName("nazione")]
public string Nazione { get; set; }
[Column("note"), JsonPropertyName("note")]
public string Note { get; set; }
[Column("part_iva"), JsonPropertyName("partIva")]
public string PartIva { get; set; }
[Column("persona_rif"), JsonPropertyName("personaRif")]
public string PersonaRif { get; set; }
[Column("prov"), JsonPropertyName("prov")]
public string Prov { get; set; }
[Column("rag_soc"), JsonPropertyName("ragSoc")]
public string RagSoc { get; set; }
[Column("rag_soc2"), JsonPropertyName("ragSoc2")]
public string RagSoc2 { get; set; }
[Column("sconto1"), JsonPropertyName("sconto1")]
public decimal Sconto1 { get; set; } = 0;
[Column("sconto2"), JsonPropertyName("sconto2")]
public decimal Sconto2 { get; set; } = 0;
[Column("telefono"), JsonPropertyName("telefono")]
public string Telefono { get; set; }
[Column("cuu_pa"), JsonPropertyName("cuuPa")]
public string CuuPa { get; set; }
[Column("e_mail_pec"), JsonPropertyName("eMailPec")]
public string EMailPec { get; set; }
[Column("flag_informativa"), JsonPropertyName("flagInformativa")]
public string FlagInformativa { get; set; } = "N";
[Column("flag_consenso"), JsonPropertyName("flagConsenso")]
public string FlagConsenso { get; set; } = "N";
[Column("username"), JsonPropertyName("userName")]
public string UserName { get; set; }
}

View File

@@ -0,0 +1,32 @@
using SQLite;
using System.Text.Json.Serialization;
namespace salesbook.Shared.Core.Entity;
[Table("ptb_pros_rif")]
public class PtbProsRif
{
[Column("cod_ppro"), JsonPropertyName("codPpro"), Indexed(Name = "PtbProsRifPK", Order = 1, Unique = true)]
public string CodPpro { get; set; }
[Column("id_pers_rif"), JsonPropertyName("idPersRif"), Indexed(Name = "PtbProsRifPK", Order = 2, Unique = true)]
public int IdPersRif { get; set; }
[Column("persona_rif"), JsonPropertyName("personaRif")]
public string PersonaRif { get; set; }
[Column("e_mail"), JsonPropertyName("eMail")]
public string EMail { get; set; }
[Column("fax"), JsonPropertyName("fax")]
public string Fax { get; set; }
[Column("mansione"), JsonPropertyName("mansione")]
public string Mansione { get; set; }
[Column("num_cellulare"), JsonPropertyName("numCellulare")]
public string NumCellulare { get; set; }
[Column("telefono"), JsonPropertyName("telefono")]
public string Telefono { get; set; }
}

View File

@@ -0,0 +1,176 @@
using SQLite;
using System.Text.Json.Serialization;
namespace salesbook.Shared.Core.Entity;
[Table("stb_activity")]
public class StbActivity
{
[PrimaryKey, Column("activity_id"), JsonPropertyName("activityId")]
public string ActivityId { get; set; }
[Column("activity_result_id"), JsonPropertyName("activityResultId")]
public string? ActivityResultId { get; set; }
[Column("activity_type_id"), JsonPropertyName("activityTypeId")]
public string? ActivityTypeId { get; set; }
[Column("data_ins_act"), JsonPropertyName("dataInsAct")]
public DateTime DataInsAct { get; set; } = DateTime.Now;
[Column("activity_description"), JsonPropertyName("activityDescription")]
public string? ActivityDescription { get; set; }
[Column("parent_activity_id"), JsonPropertyName("parentActivityId")]
public string? ParentActivityId { get; set; }
[Column("tipo_anag"), JsonPropertyName("tipoAnag")]
public string? TipoAnag { get; set; }
[Column("cod_anag"), JsonPropertyName("codAnag")]
public string? CodAnag { get; set; }
[Column("cod_jcom"), JsonPropertyName("codJcom")]
public string? CodJcom { get; set; }
[Column("cod_jfas"), JsonPropertyName("codJfas")]
public string? CodJfas { get; set; }
[Column("estimated_date"), JsonPropertyName("estimatedDate")]
public DateTime? EstimatedDate { get; set; }
[Column("estimated_time"), JsonPropertyName("estimatedTime")]
public DateTime? EstimatedTime { get; set; }
[Column("alarm_date"), JsonPropertyName("alarmDate")]
public DateTime? AlarmDate { get; set; }
[Column("alarm_time"), JsonPropertyName("alarmTime")]
public DateTime? AlarmTime { get; set; }
[Column("effective_date"), JsonPropertyName("effectiveDate")]
public DateTime? EffectiveDate { get; set; }
[Column("effective_time"), JsonPropertyName("effectiveTime")]
public DateTime? EffectiveTime { get; set; }
[Column("result_description"), JsonPropertyName("resultDescription")]
public string? ResultDescription { get; set; }
[Column("estimated_enddate"), JsonPropertyName("estimatedEnddate")]
public DateTime? EstimatedEnddate { get; set; }
[Column("estimated_endtime"), JsonPropertyName("estimatedEndtime")]
public DateTime? EstimatedEndtime { get; set; }
[Column("effective_enddate"), JsonPropertyName("effectiveEnddate")]
public DateTime? EffectiveEnddate { get; set; }
[Column("effective_endtime"), JsonPropertyName("effectiveEndtime")]
public DateTime? EffectiveEndtime { get; set; }
[Column("user_creator"), JsonPropertyName("userCreator")]
public string? UserCreator { get; set; }
[Column("user_name"), JsonPropertyName("userName")]
public string? UserName { get; set; }
[Column("perc_comp"), JsonPropertyName("percComp")]
public double? PercComp { get; set; } = 0;
[Column("estimated_hours"), JsonPropertyName("estimatedHours")]
public double? EstimatedHours { get; set; } = 0;
[Column("cod_mart"), JsonPropertyName("codMart")]
public string? CodMart { get; set; }
[Column("partita_mag"), JsonPropertyName("partitaMag")]
public string? PartitaMag { get; set; }
[Column("matricola"), JsonPropertyName("matricola")]
public string? Matricola { get; set; }
[Column("priorita"), JsonPropertyName("priorita")]
public int? Priorita { get; set; } = 0;
[Column("activity_play_counter"), JsonPropertyName("activityPlayCounter")]
public double? ActivityPlayCounter { get; set; } = 0;
[Column("activity_event"), JsonPropertyName("activityEvent")]
public string? ActivityEvent { get; set; }
[Column("guarantee"), JsonPropertyName("guarantee")]
public string? Guarantee { get; set; }
[Column("note"), JsonPropertyName("note")]
public string? Note { get; set; }
[Column("rfid"), JsonPropertyName("rfid")]
public string? Rfid { get; set; }
[Column("id_lotto"), JsonPropertyName("idLotto")]
public int? IdLotto { get; set; }
[Column("persona_rif"), JsonPropertyName("personaRif")]
public string? PersonaRif { get; set; }
[Column("hr_num"), JsonPropertyName("hrNum")]
public int? HrNum { get; set; }
[Column("gestione"), JsonPropertyName("gestione")]
public string? Gestione { get; set; }
[Column("data_ord"), JsonPropertyName("dataOrd")]
public DateTime? DataOrd { get; set; }
[Column("num_ord"), JsonPropertyName("numOrd")]
public int? NumOrd { get; set; }
[Column("id_step"), JsonPropertyName("idStep")]
public int? IdStep { get; set; }
[Column("id_riga"), JsonPropertyName("idRiga")]
public int? IdRiga { get; set; }
[Column("ora_ins_act"), JsonPropertyName("oraInsAct")]
public DateTime? OraInsAct { get; set; }
[Column("indice_gradimento"), JsonPropertyName("indiceGradimento")]
public decimal? IndiceGradimento { get; set; } = 0;
[Column("note_gradimento"), JsonPropertyName("noteGradimento")]
public string? NoteGradimento { get; set; }
[Column("flag_risolto"), JsonPropertyName("flagRisolto")]
public string? FlagRisolto { get; set; } = "N";
[Column("flag_tipologia"), JsonPropertyName("flagTipologia")]
public string? FlagTipologia { get; set; }
[Ignore, JsonPropertyName("oreRapportino")]
public decimal? OreRapportino { get; set; }
[Column("user_modifier"), JsonPropertyName("userModifier")]
public string? UserModifier { get; set; }
[Column("ora_mod_act"), JsonPropertyName("oraModAct")]
public DateTime? OraModAct { get; set; } = DateTime.Now;
[Column("ora_view_act"), JsonPropertyName("oraViewAct")]
public DateTime? OraViewAct { get; set; }
[Column("cod_vdes"), JsonPropertyName("codVdes")]
public string? CodVdes { get; set; }
[Column("cod_cmac"), JsonPropertyName("codCmac")]
public string? CodCmac { get; set; }
[Column("wrike_id"), JsonPropertyName("wrikeId")]
public string? WrikeId { get; set; }
[Column("cod_mgrp"), JsonPropertyName("codMgrp")]
public string? CodMgrp { get; set; }
[Column("plan_id"), JsonPropertyName("planId")]
public long? PlanId { get; set; }
}

View File

@@ -0,0 +1,32 @@
using SQLite;
using System.Text.Json.Serialization;
namespace salesbook.Shared.Core.Entity;
[Table("stb_activity_result")]
public class StbActivityResult
{
[PrimaryKey, Column("activity_result_id"), JsonPropertyName("activityResultId")]
public string ActivityResultId { get; set; }
[Column("path_icona"), JsonPropertyName("pathIcona")]
public string? PathIcona { get; set; }
[Column("flag_save_rap_lav"), JsonPropertyName("flagSaveRapLav")]
public string FlagSaveRapLav { get; set; } = "N";
[Column("flag_activity_result"), JsonPropertyName("flagActivityResult")]
public int? FlagActivityResult { get; set; } = 1;
[Column("flag_insert_activity"), JsonPropertyName("flagInsertActivity")]
public string FlagInsertActivity { get; set; } = "N";
[Column("flag_attivo"), JsonPropertyName("flagAttivo")]
public string FlagAttivo { get; set; } = "S";
[Column("flag_invio_notifica"), JsonPropertyName("flagInvioNotifica")]
public string FlagInvioNotifica { get; set; } = "N";
[Column("flag_stato_attivita"), JsonPropertyName("flagStatoAttivita")]
public string FlagStatoAttivita { get; set; } = "N";
}

View File

@@ -0,0 +1,41 @@
using SQLite;
using System.Text.Json.Serialization;
namespace salesbook.Shared.Core.Entity;
[Table("stb_activity_type")]
public class StbActivityType
{
[Column("activity_type_id"), JsonPropertyName("activityTypeId"), Indexed(Name = "ActivityTypePK", Order = 1, Unique = true)]
public string ActivityTypeId { get; set; }
[Column("flag_tipologia"), JsonPropertyName("flagTipologia"), Indexed(Name = "ActivityTypePK", Order = 2, Unique = true)]
public string FlagTipologia { get; set; }
[Column("estimated_duration"), JsonPropertyName("estimatedDuration")]
public double? EstimatedDuration { get; set; } = 0;
[Column("link_gest"), JsonPropertyName("linkGest")]
public string? LinkGest { get; set; }
[Column("cod_jfas"), JsonPropertyName("codJfas")]
public string? CodJfas { get; set; }
[Column("user_name"), JsonPropertyName("userName")]
public string? UserName { get; set; }
[Column("flag_sal"), JsonPropertyName("flagSal")]
public string FlagSal { get; set; } = "N";
[Column("flag_set_alarm"), JsonPropertyName("flagSetAlarm")]
public string FlagSetAlarm { get; set; } = "N";
[Column("flag_attiva"), JsonPropertyName("flagAttiva")]
public string FlagAttiva { get; set; } = "S";
[Column("flag_generate_mov"), JsonPropertyName("flagGenerateMov")]
public string FlagGenerateMov { get; set; } = "S";
[Column("flag_view_calendar"), JsonPropertyName("flagViewCalendar")]
public bool FlagViewCalendar { get; set; }
}

View File

@@ -0,0 +1,14 @@
using SQLite;
using System.Text.Json.Serialization;
namespace salesbook.Shared.Core.Entity;
[Table("stb_user")]
public class StbUser
{
[PrimaryKey, Column("user_name"), JsonPropertyName("userName")]
public string UserName { get; set; }
[Column("full_name"), JsonPropertyName("fullName")]
public string FullName { get; set; }
}

View File

@@ -0,0 +1,41 @@
using SQLite;
using System.Text.Json.Serialization;
namespace salesbook.Shared.Core.Entity;
[Table("vtb_clie_pers_rif")]
public class VtbCliePersRif
{
[Column("id_pers_rif"), JsonPropertyName("idPersRif"), Indexed(Name = "VtbCliePersRifPK", Order = 1, Unique = true)]
public int IdPersRif { get; set; }
[Column("cod_anag"), JsonPropertyName("codAnag"), Indexed(Name = "VtbCliePersRifPK", Order = 2, Unique = true)]
public string CodAnag { get; set; }
[Column("persona_rif"), JsonPropertyName("personaRif")]
public string PersonaRif { get; set; }
[Column("mansione"), JsonPropertyName("mansione")]
public string? Mansione { get; set; }
[Column("telefono"), JsonPropertyName("telefono")]
public string? Telefono { get; set; }
[Column("fax"), JsonPropertyName("fax")]
public string Fax { get; set; }
[Column("e_mail"), JsonPropertyName("eMail")]
public string? EMail { get; set; }
[Column("num_cellulare"), JsonPropertyName("numCellulare")]
public string? NumCellulare { get; set; }
[Column("tipo_indirizzo"), JsonPropertyName("tipoIndirizzo")]
public string TipoIndirizzo { get; set; }
[Column("cod_vdes"), JsonPropertyName("codVdes")]
public string CodVdes { get; set; }
[Column("data_ult_agg"), JsonPropertyName("dataUltAgg")]
public DateTime? DataUltAgg { get; set; } = DateTime.Now;
}

View File

@@ -0,0 +1,197 @@
using SQLite;
using System.Text.Json.Serialization;
namespace salesbook.Shared.Core.Entity;
[Table("vtb_dest")]
public class VtbDest
{
[Column("cod_anag"), JsonPropertyName("codAnag"), Indexed(Name = "VtbDestPK", Order = 1, Unique = true)]
public string CodAnag { get; set; }
[Column("cod_vdes"), JsonPropertyName("codVdes"), Indexed(Name = "VtbDestPK", Order = 2, Unique = true)]
public string CodVdes { get; set; }
[Column("destinatario"), JsonPropertyName("destinatario")]
public string Destinatario { get; set; }
[Column("indirizzo"), JsonPropertyName("indirizzo")]
public string Indirizzo { get; set; }
[Column("cap"), JsonPropertyName("cap")]
public string Cap { get; set; }
[Column("citta"), JsonPropertyName("citta")]
public string Citta { get; set; }
[Column("prov"), JsonPropertyName("prov")]
public string Prov { get; set; }
[Column("nazione"), JsonPropertyName("nazione")]
public string Nazione { get; set; }
[Column("tel"), JsonPropertyName("tel")]
public string Tel { get; set; }
[Column("fax"), JsonPropertyName("fax")]
public string Fax { get; set; }
[Column("note"), JsonPropertyName("note")]
public string Note { get; set; }
[Column("fonte"), JsonPropertyName("fonte")]
public string Fonte { get; set; }
[Column("cod_centro_azi"), JsonPropertyName("codCentroAzi")]
public string CodCentroAzi { get; set; }
[Column("gg_cons"), JsonPropertyName("ggCons")]
public int GgCons { get; set; } = 0;
[Column("cod_aliq_out"), JsonPropertyName("codAliqOut")]
public string CodAliqOut { get; set; }
[Column("cod_aliq_in"), JsonPropertyName("codAliqIn")]
public string CodAliqIn { get; set; }
[Column("descriz_aliq_out"), JsonPropertyName("descrizAliqOut")]
public string DescrizAliqOut { get; set; }
[Column("cod_vzon"), JsonPropertyName("codVzon")]
public string CodVzon { get; set; }
[Column("cod_vlis"), JsonPropertyName("codVlis")]
public string CodVlis { get; set; }
[Column("cod_vage"), JsonPropertyName("codVage")]
public string CodVage { get; set; }
[Column("persona_rif"), JsonPropertyName("personaRif")]
public string PersonaRif { get; set; }
[Column("part_iva"), JsonPropertyName("partIva")]
public string PartIva { get; set; }
[Column("cod_affiliazione"), JsonPropertyName("codAffiliazione")]
public string CodAffiliazione { get; set; }
[Column("indirizzo_legale"), JsonPropertyName("indirizzoLegale")]
public string IndirizzoLegale { get; set; }
[Column("cap_legale"), JsonPropertyName("capLegale")]
public string CapLegale { get; set; }
[Column("citta_legale"), JsonPropertyName("cittaLegale")]
public string CittaLegale { get; set; }
[Column("prov_legale"), JsonPropertyName("provLegale")]
public string ProvLegale { get; set; }
[Column("nazione_legale"), JsonPropertyName("nazioneLegale")]
public string NazioneLegale { get; set; }
[Column("cod_mdep"), JsonPropertyName("codMdep")]
public string CodMdep { get; set; }
[Column("flag_domic_riba"), JsonPropertyName("flagDomicRiba")]
public string FlagDomicRiba { get; set; }
[Column("flag_attivo"), JsonPropertyName("flagAttivo")]
public string FlagAttivo { get; set; } = "S";
[Column("flag_esponi"), JsonPropertyName("flagEsponi")]
public string FlagEsponi { get; set; } = "S";
[Column("rag_soc_legale"), JsonPropertyName("ragSocLegale")]
public string RagSocLegale { get; set; }
[Column("cod_alis"), JsonPropertyName("codAlis")]
public string CodAlis { get; set; }
[Column("cod_vpre"), JsonPropertyName("codVpre")]
public string CodVpre { get; set; }
[Column("cod_vcom"), JsonPropertyName("codVcom")]
public string CodVcom { get; set; }
[Column("cod_sco_cli"), JsonPropertyName("codScoCli")]
public string CodScoCli { get; set; }
[Column("e_mail"), JsonPropertyName("eMail")]
public string EMail { get; set; }
[Column("data_cessazione"), JsonPropertyName("dataCessazione")]
public DateTime? DataCessazione { get; set; }
[Column("data_attivazione"), JsonPropertyName("dataAttivazione")]
public DateTime? DataAttivazione { get; set; }
[Column("cod_vvet"), JsonPropertyName("codVvet")]
public string CodVvet { get; set; }
[Column("gg_chiusura"), JsonPropertyName("ggChiusura")]
public string GgChiusura { get; set; }
[Column("tipo_negozio"), JsonPropertyName("tipoNegozio")]
public string TipoNegozio { get; set; }
[Column("cod_ean"), JsonPropertyName("codEan")]
public string CodEan { get; set; }
[Column("flag_stampa_prezzi"), JsonPropertyName("flagStampaPrezzi")]
public string FlagStampaPrezzi { get; set; } = "S";
[Column("cod_aliq"), JsonPropertyName("codAliq")]
public string CodAliq { get; set; }
[Column("cod_griglia"), JsonPropertyName("codGriglia")]
public string CodGriglia { get; set; }
[Column("cod_acc"), JsonPropertyName("codAcc")]
public string CodAcc { get; set; }
[Column("cod_vtip"), JsonPropertyName("codVtip")]
public string CodVtip { get; set; }
[Column("cod_vset"), JsonPropertyName("codVset")]
public string CodVset { get; set; }
[Column("cod_vseg"), JsonPropertyName("codVseg")]
public string CodVseg { get; set; }
[Column("cod_vatt"), JsonPropertyName("codVatt")]
public string CodVatt { get; set; }
[Column("cod_fisc"), JsonPropertyName("codFisc")]
public string CodFisc { get; set; }
[Column("cuu_pa"), JsonPropertyName("cuuPa")]
public string CuuPa { get; set; }
[Column("e_mail_pec"), JsonPropertyName("eMailPec")]
public string EMailPec { get; set; }
[Column("flag_stabile_org"), JsonPropertyName("flagStabileOrg")]
public string FlagStabileOrg { get; set; }
[Column("lat"), JsonPropertyName("lat")]
public decimal? Lat { get; set; }
[Column("lng"), JsonPropertyName("lng")]
public decimal? Lng { get; set; }
[Column("term_cons"), JsonPropertyName("termCons")]
public string TermCons { get; set; }
[Column("itinerario"), JsonPropertyName("itinerario")]
public string Itinerario { get; set; }
[Column("imp_min_ord"), JsonPropertyName("impMinOrd")]
public decimal ImpMinOrd { get; set; } = 0;
[Column("part_iva_legale"), JsonPropertyName("partIvaLegale")]
public string PartIvaLegale { get; set; }
[Column("cod_fisc_legale"), JsonPropertyName("codFiscLegale")]
public string CodFiscLegale { get; set; }
}

View File

@@ -0,0 +1,24 @@
using salesbook.Shared.Core.Helpers.Enum;
namespace salesbook.Shared.Core.Helpers;
public static class ActivityCategoryHelper
{
public static string ConvertToHumanReadable(this ActivityCategoryEnum activityCategory)
{
return activityCategory switch
{
ActivityCategoryEnum.Memo => "memo",
ActivityCategoryEnum.Interna => "interna",
ActivityCategoryEnum.Commessa => "commessa",
_ => throw new ArgumentOutOfRangeException(nameof(activityCategory), activityCategory, null)
};
}
public static List<ActivityCategoryEnum> AllActivityCategory =>
[
ActivityCategoryEnum.Memo,
ActivityCategoryEnum.Interna,
ActivityCategoryEnum.Commessa
];
}

View File

@@ -0,0 +1,8 @@
namespace salesbook.Shared.Core.Helpers.Enum;
public enum ActivityCategoryEnum
{
Memo = 0,
Interna = 1,
Commessa = 2
}

View File

@@ -0,0 +1,11 @@
namespace salesbook.Shared.Core.Helpers;
class IconConstants
{
public class Chip
{
public const string Stato = "ri-list-check-3 fa-fw fa-chip";
public const string User = "ri-user-fill fa-fw fa-chip";
public const string Time = "ri-time-line fa-fw fa-chip";
}
}

View File

@@ -0,0 +1,17 @@
using salesbook.Shared.Core.Authorization.Enum;
namespace salesbook.Shared.Core.Helpers;
public static class KeyGroupHelper
{
public static string ConvertToHumanReadable(this KeyGroupEnum keyGroup)
{
return keyGroup switch
{
KeyGroupEnum.Agenti => "Agenti",
KeyGroupEnum.Tecnico => "Tecnico",
KeyGroupEnum.UtenteAziendale => "Utente Aziendale",
_ => throw new ArgumentOutOfRangeException(nameof(keyGroup), keyGroup, null)
};
}
}

View File

@@ -0,0 +1,13 @@
using AutoMapper;
using salesbook.Shared.Core.Dto;
using salesbook.Shared.Core.Entity;
namespace salesbook.Shared.Core.Helpers;
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<StbActivity, ActivityDTO>();
}
}

View File

@@ -0,0 +1,28 @@
using MudBlazor;
using salesbook.Shared.Components.SingleElements.Modal;
using salesbook.Shared.Core.Dto;
namespace salesbook.Shared.Core.Helpers;
public class ModalHelpers
{
public static async Task<DialogResult?> OpenActivityForm(IDialogService dialog, ActivityDTO? activity, string? id)
{
var modal = await dialog.ShowAsync<ActivityForm>(
"Activity form",
new DialogParameters<ActivityForm>
{
{ x => x.Id, id },
{ x => x.ActivityCopied, activity }
},
new DialogOptions
{
FullScreen = true,
CloseButton = false,
NoHeader = true
}
);
return await modal.Result;
}
}

View File

@@ -0,0 +1,19 @@
using System.Collections;
namespace salesbook.Shared.Core.Helpers;
public static class ObjectExtensions
{
public static bool IsNullOrEmpty(this IEnumerable? obj) =>
obj == null || obj.GetEnumerator().MoveNext() == false;
public static bool IsNullOrEmpty(this string? obj) =>
string.IsNullOrEmpty(obj);
public static bool EqualsIgnoreCase(this string obj, string anotherString) =>
string.Equals(obj, anotherString, StringComparison.OrdinalIgnoreCase);
public static bool ContainsIgnoreCase(this string obj, string anotherString) =>
obj.Contains(anotherString, StringComparison.OrdinalIgnoreCase);
}

View File

@@ -0,0 +1,13 @@
namespace salesbook.Shared.Core.Interface;
public interface IFormFactor
{
public string GetFormFactor();
public string GetPlatform();
public bool IsWeb()
{
var formFactor = GetFormFactor();
return formFactor == "Web";
}
}

View File

@@ -0,0 +1,17 @@
using salesbook.Shared.Core.Dto;
using salesbook.Shared.Core.Entity;
namespace salesbook.Shared.Core.Interface;
public interface IIntegryApiService
{
Task<List<StbActivity>?> RetrieveActivity(string? dateFilter = null);
Task<List<JtbComt>?> RetrieveAllCommesse(string? dateFilter = null);
Task<TaskSyncResponseDTO> RetrieveAnagClie(string? dateFilter = null);
Task<TaskSyncResponseDTO> RetrieveProspect(string? dateFilter = null);
Task<SettingsResponseDTO> RetrieveSettings();
Task DeleteActivity(string activityId);
Task<List<StbActivity>?> SaveActivity(ActivityDTO activity);
}

View File

@@ -0,0 +1,18 @@
using System.Linq.Expressions;
using salesbook.Shared.Core.Dto;
using salesbook.Shared.Core.Entity;
namespace salesbook.Shared.Core.Interface;
public interface IManageDataService
{
Task<List<T>> GetTable<T>(Expression<Func<T, bool>>? whereCond = null) where T : new();
Task<List<ActivityDTO>> GetActivity(Expression<Func<StbActivity, bool>>? whereCond = null);
Task InsertOrUpdate<T>(T objectToSave);
Task Delete<T>(T objectToDelete);
Task DeleteActivity(ActivityDTO activity);
Task ClearDb();
}

View File

@@ -0,0 +1,6 @@
namespace salesbook.Shared.Core.Interface;
public interface INetworkService
{
public bool IsNetworkAvailable();
}

View File

@@ -0,0 +1,10 @@
namespace salesbook.Shared.Core.Interface;
public interface ISyncDbService
{
Task GetAndSaveActivity(string? dateFilter = null);
Task GetAndSaveCommesse(string? dateFilter = null);
Task GetAndSaveProspect(string? dateFilter = null);
Task GetAndSaveClienti(string? dateFilter = null);
Task GetAndSaveSettings(string? dateFilter = null);
}

View File

@@ -0,0 +1,6 @@
using CommunityToolkit.Mvvm.Messaging.Messages;
using salesbook.Shared.Core.Dto;
namespace salesbook.Shared.Core.Messages.Activity.Copy;
public class CopyActivityMessage(ActivityDTO value) : ValueChangedMessage<ActivityDTO>(value);

View File

@@ -0,0 +1,17 @@
using CommunityToolkit.Mvvm.Messaging;
using salesbook.Shared.Core.Dto;
namespace salesbook.Shared.Core.Messages.Activity.Copy;
public class CopyActivityService
{
public event Action<ActivityDTO>? OnCopyActivity;
public CopyActivityService(IMessenger messenger)
{
messenger.Register<CopyActivityMessage>(this, (_, o) =>
{
OnCopyActivity?.Invoke(o.Value);
});
}
}

View File

@@ -0,0 +1,5 @@
using CommunityToolkit.Mvvm.Messaging.Messages;
namespace salesbook.Shared.Core.Messages.Activity.New;
public class NewActivityMessage(string value) : ValueChangedMessage<string>(value);

View File

@@ -0,0 +1,16 @@
using CommunityToolkit.Mvvm.Messaging;
namespace salesbook.Shared.Core.Messages.Activity.New;
public class NewActivityService
{
public event Action<string>? OnActivityCreated;
public NewActivityService(IMessenger messenger)
{
messenger.Register<NewActivityMessage>(this, (_, o) =>
{
OnActivityCreated?.Invoke(o.Value);
});
}
}

View File

@@ -0,0 +1,16 @@
using CommunityToolkit.Mvvm.Messaging;
namespace salesbook.Shared.Core.Messages.Back;
public class BackNavigationService
{
public event Action? OnHardwareBack;
public BackNavigationService(IMessenger messenger)
{
messenger.Register<HardwareBackMessage>(this, (_, _) =>
{
OnHardwareBack?.Invoke();
});
}
}

View File

@@ -0,0 +1,5 @@
using CommunityToolkit.Mvvm.Messaging.Messages;
namespace salesbook.Shared.Core.Messages.Back;
public class HardwareBackMessage(string value) : ValueChangedMessage<string>(value);

View File

@@ -0,0 +1,56 @@
using IntegryApiClient.Core.Domain.Abstraction.Contracts.Account;
using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
namespace salesbook.Shared.Core.Services;
public class AppAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly IUserSession _userSession;
private readonly IUserAccountService _userAccountService;
public AppAuthenticationStateProvider(IUserSession userSession, IUserAccountService userAccountService)
{
_userSession = userSession;
_userAccountService = userAccountService;
userAccountService.ExpiredUserSession += (_, _) =>
NotifyAuthenticationStateChanged(LoadAuthenticationState());
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
return await LoadAuthenticationState();
}
public async Task SignOut()
{
await _userAccountService.Logout();
NotifyAuthenticationState();
}
public void NotifyAuthenticationState()
{
NotifyAuthenticationStateChanged(LoadAuthenticationState());
}
private async Task<AuthenticationState> LoadAuthenticationState()
{
if (!await _userSession.IsLoggedIn() || !await _userSession.IsRefreshTokenValid())
{
return new AuthenticationState(
new ClaimsPrincipal(
new ClaimsIdentity()
)
);
}
var claimIdentity = new ClaimsIdentity(_userSession.JwtToken!.Claims, "jwt");
var user = new ClaimsPrincipal(claimIdentity);
var authenticationState = new AuthenticationState(user);
return authenticationState;
}
}

View File

@@ -0,0 +1,70 @@
using IntegryApiClient.Core.Domain.Abstraction.Contracts.Account;
using IntegryApiClient.Core.Domain.RestClient.Contacts;
using salesbook.Shared.Core.Dto;
using salesbook.Shared.Core.Entity;
using salesbook.Shared.Core.Interface;
namespace salesbook.Shared.Core.Services;
public class IntegryApiService(IIntegryApiRestClient integryApiRestClient, IUserSession userSession)
: IIntegryApiService
{
public Task<List<StbActivity>?> RetrieveActivity(string? dateFilter)
{
var queryParams = new Dictionary<string, object> { { "dateFilter", dateFilter ?? "2020-01-01" } };
return integryApiRestClient.AuthorizedGet<List<StbActivity>?>("crm/retrieveActivity", queryParams);
}
public Task<List<JtbComt>?> RetrieveAllCommesse(string? dateFilter)
{
var queryParams = new Dictionary<string, object>();
if (dateFilter != null)
{
queryParams.Add("dateFilter", dateFilter);
}
return integryApiRestClient.AuthorizedGet<List<JtbComt>?>("crm/retrieveCommesse", queryParams);
}
public Task<TaskSyncResponseDTO> RetrieveAnagClie(string? dateFilter)
{
var queryParams = new Dictionary<string, object>();
if (dateFilter != null)
{
queryParams.Add("dateFilter", dateFilter);
}
return integryApiRestClient.AuthorizedGet<TaskSyncResponseDTO>("crm/retrieveClienti", queryParams)!;
}
public Task<TaskSyncResponseDTO> RetrieveProspect(string? dateFilter)
{
var queryParams = new Dictionary<string, object>();
if (dateFilter != null)
{
queryParams.Add("dateFilter", dateFilter);
}
return integryApiRestClient.AuthorizedGet<TaskSyncResponseDTO>("crm/retrieveProspect", queryParams)!;
}
public Task<SettingsResponseDTO> RetrieveSettings() =>
integryApiRestClient.AuthorizedGet<SettingsResponseDTO>("crm/retrieveSettings")!;
public Task DeleteActivity(string activityId)
{
var queryParams = new Dictionary<string, object>
{
{ "activityId", activityId }
};
return integryApiRestClient.AuthorizedGet<object>($"activity/delete", queryParams);
}
public Task<List<StbActivity>?> SaveActivity(ActivityDTO activity) =>
integryApiRestClient.AuthorizedPost<List<StbActivity>?>("crm/saveActivity", activity);
}

View File

@@ -0,0 +1,118 @@
namespace salesbook.Shared.Core.Utility;
public static class UtilityColor
{
public static string CalcHexColor(string input)
{
try
{
var hue = (int)(Math.Abs(input.GetHashCode()) * 137.508 % 360);
var data = new HSL(hue, 0.90f, 0.85f);
var myColor = HSLToRGB(data);
return myColor.R.ToString("X2") + myColor.G.ToString("X2") + myColor.B.ToString("X2");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
return "dddddd";
}
}
private struct RGB(byte r, byte g, byte b)
{
public byte R
{
get => r;
set => r = value;
}
public byte G
{
get => g;
set => g = value;
}
public byte B
{
get => b;
set => b = value;
}
public bool Equals(RGB rgb)
{
return (this.R == rgb.R) && (this.G == rgb.G) && (this.B == rgb.B);
}
}
private struct HSL(int h, float s, float l)
{
public int H
{
get => h;
set => h = value;
}
public float S
{
get => s;
set => s = value;
}
public float L
{
get => l;
set => l = value;
}
public bool Equals(HSL hsl)
{
return H == hsl.H && (this.S == hsl.S) && (this.L == hsl.L);
}
}
private static RGB HSLToRGB(HSL hsl)
{
byte r;
byte g;
byte b;
var hue = (float)hsl.H / 360;
if (hsl.S == 0)
{
r = g = b = (byte)(hsl.L * 255);
}
else
{
var v2 = hsl.L < 0.5 ? hsl.L * (1 + hsl.S) : hsl.L + hsl.S - hsl.L * hsl.S;
var v1 = 2 * hsl.L - v2;
r = (byte)(255 * HueToRGB(v1, v2, hue + 1.0f / 3));
g = (byte)(255 * HueToRGB(v1, v2, hue));
b = (byte)(255 * HueToRGB(v1, v2, hue - 1.0f / 3));
}
return new RGB(r, g, b);
}
private static float HueToRGB(float v1, float v2, float vH)
{
if (vH < 0)
vH += 1;
if (vH > 1)
vH -= 1;
if (6 * vH < 1)
return v1 + (v2 - v1) * 6 * vH;
if (2 * vH < 1)
return v2;
if (3 * vH < 2)
return v1 + (v2 - v1) * (2.0f / 3 - vH) * 6;
return v1;
}
}

View File

@@ -0,0 +1,38 @@
using System.Globalization;
namespace salesbook.Shared.Core.Utility;
public static class UtilityString
{
public static string ExtractInitials(string fullname)
{
return string.Concat(fullname
.Split(' ', StringSplitOptions.RemoveEmptyEntries)
.Take(3)
.Select(word => char.ToUpper(word[0])));
}
public static string FirstCharToUpper(this string input) =>
input switch
{
null => throw new ArgumentNullException(nameof(input)),
"" => throw new ArgumentException($"{nameof(input)} cannot be empty", nameof(input)),
_ => input[0].ToString().ToUpper() + input[1..]
};
public static (string Upper, string Lower, string SentenceCase, string TitleCase) FormatString(string input)
{
if (string.IsNullOrWhiteSpace(input))
return (string.Empty, string.Empty, string.Empty, string.Empty);
var upper = input.ToUpper();
var lower = input.ToLower();
var sentenceCase = char.ToUpper(lower[0]) + lower[1..];
var textInfo = CultureInfo.CurrentCulture.TextInfo;
var titleCase = textInfo.ToTitleCase(lower);
return (upper, lower, sentenceCase, titleCase);
}
}

View File

@@ -0,0 +1,21 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
namespace salesbook.Shared;
public static class InteractiveRenderSettings
{
public static IComponentRenderMode? InteractiveServer { get; set; } =
RenderMode.InteractiveServer;
public static IComponentRenderMode? InteractiveAuto { get; set; } =
RenderMode.InteractiveAuto;
public static IComponentRenderMode? InteractiveWebAssembly { get; set; } =
RenderMode.InteractiveWebAssembly;
public static void ConfigureBlazorHybridRenderModes()
{
InteractiveServer = null;
InteractiveAuto = null;
InteractiveWebAssembly = null;
}
}

View File

@@ -0,0 +1,24 @@
@using System.Net.Http
@using System.Net.Http.Json
@using IntegryApiClient.Core.Domain.Abstraction.Contracts.Account
@using IntegryApiClient.Core.Domain.Abstraction.Contracts.Storage
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using salesbook.Shared.Components
@using MudBlazor
@using MudExtensions
@using MudBlazor.ThemeManager
@using salesbook.Shared.Core.Helpers
@using salesbook.Shared.Components.SingleElements.Card
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Authorization
@using salesbook.Shared.Core.Utility
@using static InteractiveRenderSettings
@inject NavigationManager NavigationManager
@inject IUserSession UserSession
@inject ILocalStorage LocalStorage
@inject ISnackbar Snackbar

View File

@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<SupportedPlatform Include="browser" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="14.0.0" />
<PackageReference Include="CodeBeam.MudBlazor.Extensions" Version="8.2.2" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="IntegryApiClient.Core" Version="1.1.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.5" />
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.11.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="9.0.5" />
<PackageReference Include="MudBlazor" Version="8.6.0" />
<PackageReference Include="MudBlazor.ThemeManager" Version="3.0.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="wwwroot\css\lineicons\" />
<Folder Include="wwwroot\js\bootstrap\" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,233 @@
html { overflow: hidden; }
.page, article, main { height: 100% !important; }
#app { height: 100vh; }
html, body {
font-family: "Nunito", sans-serif;
font-size: 14px;
font-weight: 400;
line-height: 1.8;
color: black;
}
* { font-family: "Nunito", sans-serif !important; }
.mud-button-label { font-weight: 700 !important; }
a, .btn-link {
/*color: #006bb7;*/
text-decoration: none;
color: inherit;
}
.btn-primary {
color: #fff;
background-color: var(--primary-color);
border-color: var(--darker-color);
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; }
.content {
padding-top: 1.1rem;
display: flex;
align-items: center;
flex-direction: column;
height: 84vh;
}
h1:focus { outline: none; }
.valid.modified:not([type=checkbox]) { outline: 1px solid #26b050; }
.invalid { outline: 1px solid #e50000; }
.validation-message { color: #e50000; }
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
.blazor-error-boundary {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
padding: 1rem 1rem 1rem 3.7rem;
color: white;
}
.blazor-error-boundary::after { content: "An error has occurred." }
.status-bar-safe-area { display: none; }
.page-title {
font-size: x-large;
font-weight: 800;
margin: 0;
line-height: normal;
color: var(--mud-palette-text-primary);
}
.custom-mudfab {
position: fixed !important;
bottom: 4rem;
margin-bottom: 16px;
right: 16px;
}
.custom_popover {
border-radius: 5px !important;
background-color: var(--mud-palette-drawer-background) !important;
box-shadow: 4px 4px 20px 0px rgba(0, 0, 0, 0.26), 0px 0px 0px 1px rgb(255 255 255 / 25%) !important;
color: var(--mud-palette-text-primary) !important;
}
.custom_popover .mud-divider {
border-color: var(--mud-palette-text-primary) !important;
}
.custom_popover .mud-list-padding { padding: 3px 0px 3px 0px !important; }
.custom_popover .mud-list-item { padding: 5px 12px 5px 12px; }
.custom_popover .mud-menu-item-text { font-weight: 600; }
.custom_popover .mud-list-item-icon {
min-width: fit-content !important;
padding-right: 12px !important;
}
.divider {
display: block;
width: 100%;
border: .05rem solid var(--card-border-color);
margin: 1rem 0;
}
/*Spinner*/
.spinner-container {
display: flex;
justify-content: center;
height: 100vh;
align-items: center;
color: var(--mud-palette-primary);
}
.not-fullScreen {
height: auto !important;
padding: 2rem 0 !important;
}
.loader {
width: 50px;
aspect-ratio: 1;
border-radius: 50%;
border: 8px solid #0000;
border-right-color: var(--mud-palette-secondary);
position: relative;
animation: l24 1s infinite linear;
}
.loader:before,
.loader:after {
content: "";
position: absolute;
inset: -8px;
border-radius: 50%;
border: inherit;
animation: inherit;
animation-duration: 2s;
}
.loader:after { animation-duration: 4s; }
@keyframes l24 {
100% { transform: rotate(1turn) }
}
/*MudBlazor Personalization*/
.mud-button-group-horizontal:not(.mud-button-group-rtl) > .mud-button-root:not(:last-child), .mud-button-group-horizontal:not(.mud-button-group-rtl) > :not(:last-child) .mud-button-root {
border-top-right-radius: 0 !important;
border-bottom-right-radius: 0 !important;
}
.mud-button-group-horizontal:not(.mud-button-group-rtl) > .mud-button-root:not(:first-child), .mud-button-group-horizontal:not(.mud-button-group-rtl) > :not(:first-child) .mud-button-root {
border-top-left-radius: 0 !important;
border-bottom-left-radius: 0 !important;
}
.customDialog-form .mud-dialog-content {
padding: 0 .75rem;
margin: 0;
}
.custom-item-select { padding: 6px 16px; }
.custom-item-select .mud-typography-body1 {
font-weight: 600;
font-size: .9rem;
}
.container {
padding-right: var(--m-page-x) !important;
padding-left: var(--m-page-x) !important;
}
.lm-container {
padding-right: calc(var(--m-page-x) * 0.5) !important;
padding-left: calc(var(--m-page-x) * 0.5) !important;
}
.mud-message-box > .mud-dialog-title > h6 {
font-weight: 800 !important;
}
.mud-dialog-actions button {
margin-left: .5rem !important;
margin-right: .5rem !important;
}
@supports (-webkit-touch-callout: none) {
.status-bar-safe-area {
display: flex;
position: fixed;
top: 0;
height: env(safe-area-inset-top);
width: 100%;
z-index: 1;
background-color: var(--mud-palette-surface);
}
.modal { padding-top: env(safe-area-inset-top); }
.safe-area-bottom { margin-bottom: env(safe-area-inset-bottom) !important; }
.pb-safe-area { padding-bottom: env(safe-area-inset-bottom) !important; }
#app {
margin-top: env(safe-area-inset-top);
margin-bottom: env(safe-area-inset-bottom);
height: calc(100vh - env(safe-area-inset-top) - env(safe-area-inset-bottom));
}
.flex-column, .navbar-brand { padding-left: env(safe-area-inset-left); }
.customDialog-form .mud-dialog-content { margin-top: env(safe-area-inset-top); }
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,52 @@
.bottom-sheet-backdrop {
position: fixed;
inset: 0;
background-color: rgba(165, 165, 165, 0.5);
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease;
z-index: 1002;
}
.bottom-sheet-backdrop.show {
opacity: 1;
pointer-events: auto;
}
.bottom-sheet-container {
position: fixed;
bottom: -200%;
left: 0;
right: 0;
transition: bottom 0.3s ease;
z-index: 1003;
}
.bottom-sheet-container.show { bottom: 0; }
.bottom-sheet {
background-color: var(--mud-palette-surface);
border-top-left-radius: 16px;
border-top-right-radius: 16px;
padding: 4px 16px 16px;
box-shadow: 0 -2px 10px rgba(165, 165, 165, 0.5);
}
.clearButton .mud-icon-button { padding: 4px !important; }
.bottom-sheet .closeIcon .mud-icon-root {
border-radius: 50%;
padding: 2px;
min-width: 15px;
min-height: 15px;
padding: 4px;
background: var(--mud-palette-gray-light);
color: var(--mud-palette-surface);
}
.button-section {
display: flex;
justify-content: end;
gap: .75rem;
margin-top: 2rem;
}

View File

@@ -0,0 +1,10 @@
:root {
/*Color*/
--card-border-color: hsl(from var(--mud-palette-gray-light) h s 86%);
--gray-for-shadow: hsl(from var(--mud-palette-overlay-dark)h s 40%);
/*Utility*/
--exception-box-shadow: 1px 2px 5px rgba(0, 0, 0, 0.3);
--custom-box-shadow: 1px 2px 5px var(--gray-for-shadow);
--mud-default-borderradius: 12px !important;
--m-page-x: 1.25rem;
}

View File

@@ -0,0 +1,147 @@
.title {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
}
.customDialog-form .content {
height: calc(100vh - (.6rem + 40px));
overflow: auto;
-ms-overflow-style: none;
scrollbar-width: none;
}
.customDialog-form .header { padding: 0 !important; }
.customDialog-form .content::-webkit-scrollbar { display: none; }
.input-card {
width: 100%;
background: var(--mud-palette-background-gray);
border-radius: 9px;
padding: .5rem 1rem;
margin-bottom: 1.5rem;
}
.input-card.clearButton {
display: flex;
align-items: center;
justify-content: space-between;
padding: .4rem 1rem !important;
}
.input-card > .divider { margin: 0 !important; }
.form-container {
display: flex;
justify-content: space-between;
align-items: center;
min-height: 35px;
}
.form-container > span {
font-weight: 600;
width: 50%;
margin-right: .3rem;
}
.form-container > .warning-text {
font-weight: 500;
color: var(--mud-palette-gray-darker);
width: unset;
margin: 0;
}
.form-container > .disable-full-width { width: unset !important; }
.dateTime-picker {
display: flex;
gap: .3rem;
flex-direction: row;
align-items: center;
}
/*Custom mudBlazor*/
.form-container .mud-input.mud-input-underline:before { border-bottom: none !important; }
.form-container .mud-input.mud-input-underline:after { border-bottom: none !important; }
.form-container.text-align-end .mud-input-slot { text-align: end; }
.input-card .mud-input.mud-input-underline:before { border-bottom: none !important; }
.input-card .mud-input.mud-input-underline:after { border-bottom: none !important; }
.form-container .customIcon-select .mud-icon-root.mud-svg-icon {
rotate: 90deg !important;
font-size: 1.1rem;
}
.form-container .customIcon-select .mud-input-slot { text-align: end; }
.input-card .mud-input {
width: 100%;
font-weight: 500;
}
.container-button {
width: 100%;
box-shadow: var(--custom-box-shadow);
padding: .5rem 0;
border-radius: 12px;
margin-bottom: 2rem;
}
.container-button .divider {
margin: .5rem 0 .5rem 3rem;
width: unset;
}
.container-button .button-settings { border: none !important; }
.container-button .button-settings .mud-icon-root {
border-radius: 6px;
padding: 2px;
min-width: 25px;
min-height: 25px;
}
.container-button > .mud-button-root {
padding-top: .15rem;
padding-bottom: .15rem;
}
.container-button .button-settings.gray-icon .mud-icon-root {
border: 1px solid hsl(from var(--mud-palette-gray-darker) h s 88%);
background: hsl(from var(--mud-palette-gray-darker) h s 88%);
color: var(--mud-palette-dark);
}
.container-button .button-settings.green-icon .mud-icon-root {
border: 1px solid hsl(from var(--mud-palette-success-lighten) h s 95%);
background: hsl(from var(--mud-palette-success-lighten) h s 95%);
color: var(--mud-palette-success-darken);
}
.container-button .button-settings.red-icon .mud-icon-root {
border: 1px solid hsl(from var(--mud-palette-error-lighten) h s 95%);
background: hsl(from var(--mud-palette-error-lighten) h s 95%);
color: var(--mud-palette-error-darken);
}
.container-button .button-settings .mud-button-label {
justify-content: flex-start;
text-transform: capitalize;
font-size: 1rem;
}
.container-button .button-settings.exit { padding: 0; }
.container-button .button-settings.exit .mud-button-label {
justify-content: center;
font-size: 1.1rem;
line-height: normal;
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.8 MiB

Some files were not shown because too many files have changed in this diff Show More