14 Commits

Author SHA1 Message Date
a5b09ae1c3 fix cancellazione reghe db 2025-12-03 10:37:05 +01:00
f1c6cb87e9 Migliorata sincronizzazione 2025-11-28 12:06:30 +01:00
dd115180f1 Iniziata implementazione cambio girno 2025-11-27 15:48:47 +01:00
8a6cfff93f Migliorie grafiche 2025-11-26 16:32:50 +01:00
da9abd5901 Aggiunto modal per ricalcolare l'importo 2025-11-26 12:38:36 +01:00
0757980e5d Aggiunto ripple effect 2025-11-26 09:42:37 +01:00
f5ee19515c Corretto currency input 2025-11-25 13:01:56 +01:00
f4d62885fb Sistemazioni grafiche 2025-11-25 12:18:11 +01:00
88d145c1ae Aggiunto log incassi 2025-11-25 10:50:13 +01:00
2be9031b15 Sistemazioni grafiche 2025-11-18 17:06:08 +01:00
26c378ad70 Aggiunto blocco se posizione negata 2025-11-18 16:26:39 +01:00
bba1004f42 Aggiunta a video versione app 2025-11-18 10:53:12 +01:00
aaa0418924 Sistemata composizione fileName 2025-11-18 10:08:24 +01:00
66ea493cfa Finish v1.1.11(18) 2025-10-13 12:04:41 +02:00
88 changed files with 2302 additions and 788 deletions

View File

@@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="androidx.lifecycle.lifecycle-common-java8">
<CLASSES>
<root url="jar://$USER_HOME$/.nuget/packages/xamarin.androidx.lifecycle.common.java8/2.8.7.2/jar/androidx.lifecycle.lifecycle-common-java8.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="org.jetbrains.kotlin.kotlin-stdlib-jdk8-2.0.0">
<CLASSES>
<root url="jar://$USER_HOME$/.nuget/packages/xamarin.kotlin.stdlib.jdk8/2.0.0/jar/org.jetbrains.kotlin.kotlin-stdlib-jdk8-2.0.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="playservicesbasement-18.7.0">
<CLASSES>
<root url="file://$USER_HOME$/AppData/Local/Temp/JetBrains/xamarinAarPackages/playservicesbasement-18.7.0/classes.jar" />
<root url="file://$USER_HOME$/AppData/Local/Temp/JetBrains/xamarinAarPackages/playservicesbasement-18.7.0/res" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="playservicestasks-18.3.0">
<CLASSES>
<root url="file://$USER_HOME$/AppData/Local/Temp/JetBrains/xamarinAarPackages/playservicestasks-18.3.0/classes.jar" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -1,7 +1,7 @@
using Microsoft.Extensions.Logging;
using System.Diagnostics;
using System.Diagnostics;
using Microsoft.Extensions.Logging;
namespace ConSegna.Maui.Services.Logger;
namespace ConSegna.Maui.Core.Logger;
public class FileLogger : ILogger
{

View File

@@ -1,6 +1,6 @@
using Microsoft.Extensions.Logging;
namespace ConSegna.Maui.Services.Logger;
namespace ConSegna.Maui.Core.Logger;
public class FileLoggerProvider(string path, string fileName) : ILoggerProvider
{

View File

@@ -1,12 +1,15 @@
using Microsoft.Extensions.Logging;
using System.Text.RegularExpressions;
using ConSegna.Shared.Core.Dto;
using ConSegna.Shared.Core.Dto;
using ConSegna.Shared.Core.Helpers;
using ConSegna.Shared.Core.Interfaces;
using Microsoft.Extensions.Logging;
namespace ConSegna.Maui.Services;
namespace ConSegna.Maui.Core.Services;
public class DataStorage(LocalDbService localDb, ILogger<DataStorage> logger) : IDataStorage
public class DataStorage(
LocalDbService localDb,
ILogger<DataStorage> logger,
IUtilityFile utilityFile
) : IDataStorage
{
public Task<List<DatiClientiDTO>> RetrieveDatiClienti() =>
localDb.RetrieveDatiClienti();
@@ -192,19 +195,7 @@ public class DataStorage(LocalDbService localDb, ILogger<DataStorage> logger) :
var filePath = Path.Combine(targetDirectory, file.Name);
if (!File.Exists(filePath)) continue;
const string pattern = @".*_(\d{6})_.*";
var match = Regex.Match(file.Name, pattern);
DateTime? dataToCheck = null;
if (match.Success &&
DateTime.TryParseExact(
match.Groups[1].Value,
"yyMMdd",
null,
System.Globalization.DateTimeStyles.None, out var date))
{
dataToCheck = date;
}
var dataToCheck = utilityFile.DetectDataDocFromFileName(file.Name);
if (dataToCheck == null) continue;
if (dataToCheck > twoWeeksAgo) continue;
@@ -219,4 +210,90 @@ public class DataStorage(LocalDbService localDb, ILogger<DataStorage> logger) :
}
}
}
private static string GetReceiptDirectoryPath()
{
var appDataPath = FileSystem.AppDataDirectory;
var targetDirectory = Path.Combine(appDataPath, "receipts");
if (!Directory.Exists(targetDirectory))
Directory.CreateDirectory(targetDirectory);
return targetDirectory;
}
private static string GetReceiptFilePath(string fileName)
{
var targetDirectory = GetReceiptDirectoryPath();
var filePath = Path.Combine(targetDirectory, fileName);
if (!File.Exists(filePath))
{
using var _ = File.Create(filePath);
}
return filePath;
}
public List<FileInfo> RetrieveAllReceipts()
{
var targetDirectory = GetReceiptDirectoryPath();
var directory = new DirectoryInfo(targetDirectory);
return directory.GetFiles().ToList();
}
public async Task<string> ReadReceiptFile(string? fileName)
{
if (fileName == null)
return "";
var targetDirectory = GetReceiptDirectoryPath();
var filePath = Path.Combine(targetDirectory, fileName);
if (!File.Exists(filePath))
return "";
try
{
await using var stream = File.OpenRead(filePath);
using var reader = new StreamReader(stream);
return await reader.ReadToEndAsync();
}
catch (Exception e)
{
logger.LogError(e, e.Message);
return "";
}
}
public async Task OpenReceiptFile(string fileName)
{
var filePath = GetReceiptFilePath(fileName);
await Launcher.OpenAsync(new OpenFileRequest
{
File = new ReadOnlyFile(filePath)
});
}
public async Task WriteReceiptTextAsync(string fileName, string content)
{
var filePath = GetReceiptFilePath(fileName);
try
{
await using var stream = new FileStream(filePath, FileMode.Append, FileAccess.Write, FileShare.None);
await using var writer = new StreamWriter(stream);
await writer.WriteLineAsync(content);
}
catch (Exception e)
{
logger.LogError(e, $"Errore durante la scrittura del file {fileName}");
}
}
public Task ResetDatabase() => localDb.ResetDatabase();
public Task SaveDatiCliente(List<DatiClientiDTO> clienti) => localDb.SaveDatiCliente(clienti);
}

View File

@@ -1,6 +1,6 @@
using ConSegna.Shared.Core.Interfaces;
namespace ConSegna.Maui.Services;
namespace ConSegna.Maui.Core.Services;
public class FormFactor : IFormFactor
{

View File

@@ -2,7 +2,7 @@
using Microsoft.Extensions.Logging;
using SQLite;
namespace ConSegna.Maui.Services;
namespace ConSegna.Maui.Core.Services;
public class LocalDbService
{
@@ -35,7 +35,7 @@ public class LocalDbService
_logger = logger;
}
private async Task ResetDatabase()
public async Task ResetDatabase()
{
try
{
@@ -103,17 +103,17 @@ public class LocalDbService
// Elimina le tabelle del database principale
await _connection.ExecuteAsync("DROP TABLE IF EXISTS DatiClienti;");
await _connection.ExecuteAsync("DROP TABLE IF EXISTS DatiConsegne;");
await _connection.ExecuteAsync("DROP TABLE IF EXISTS SospesiCliente;");
await _connection.ExecuteAsync("DROP TABLE IF EXISTS DettaglioRighe;");
await _connection.ExecuteAsync("DROP TABLE IF EXISTS PaymentData;");
await _connection.ExecuteAsync($"DELETE FROM DatiConsegne WHERE dataDoc <> {DateTime.Today:yyyy-MM-dd};");
await _connection.ExecuteAsync($"DELETE FROM SospesiCliente WHERE dataDoc <> {DateTime.Today:yyyy-MM-dd};");
await _connection.ExecuteAsync($"DELETE FROM DettaglioRighe WHERE dataDoc <> {DateTime.Today:yyyy-MM-dd};");
await _connection.ExecuteAsync($"DELETE FROM PaymentData WHERE dataDoc <> {DateTime.Today:yyyy-MM-dd};");
// Ricrea le tabelle nel database principale
await _connection.CreateTableAsync<DatiClientiDTO>();
await _connection.CreateTableAsync<DatiConsegneDTO>();
await _connection.CreateTableAsync<SospesiClienteDTO>();
await _connection.CreateTableAsync<DettaglioRigheDTO>();
await _connection.CreateTableAsync<PaymentDataDTO>();
// await _connection.CreateTableAsync<DatiConsegneDTO>();
// await _connection.CreateTableAsync<SospesiClienteDTO>();
// await _connection.CreateTableAsync<DettaglioRigheDTO>();
// await _connection.CreateTableAsync<PaymentDataDTO>();
Console.WriteLine("Database resettato con successo e backup storico creato.");
_logger.LogInformation("Database resettato con successo e backup storico creato.");
@@ -201,8 +201,6 @@ public class LocalDbService
public async Task SaveDatiCliente(List<DatiClientiDTO> clienti)
{
await ResetDatabase();
foreach (var cliente in clienti)
{
await SaveDatiCliente(cliente);

View File

@@ -1,6 +1,6 @@
using ConSegna.Shared.Core.Interfaces;
namespace ConSegna.Maui.Services;
namespace ConSegna.Maui.Core.Services;
public class NetworkService : INetworkService
{

View File

@@ -1,39 +1,36 @@
using ConSegna.Shared.Core.Dto;
using ConSegna.Maui.Core.Utility;
using ConSegna.Shared.Core.Dto;
using ConSegna.Shared.Core.Interfaces;
using Microsoft.Extensions.Logging;
namespace ConSegna.Maui.Services;
namespace ConSegna.Maui.Core.Services;
public class SyncStorage(IIntegryApiService integryApiService, IDataStorage dataStorage, LocalDbService localDb, ILogger<SyncStorage> logger)
: ISyncStorage
public class SyncStorage(
IIntegryApiService integryApiService,
IDataStorage dataStorage,
LocalDbService localDb,
ILogger<SyncStorage> logger,
IUtilityFile utilityFile
) : ISyncStorage
{
public async Task<bool> GetAndSaveDatiClienti()
public async Task<bool> SaveDatiClienti(List<DatiClientiDTO> dataToSave)
{
#if DEBUG
var dataDoc = new DateTime(2025, 06, 04);
//var dataDoc = DateTime.Today;
#else
var dataDoc = DateTime.Today;
#endif
try
{
var dataToSave = await integryApiService.GetDatiConsegne();
foreach (var datiClienti in dataToSave)
{
datiClienti.Sync = true;
foreach (var bolla in datiClienti.DatiConsegne)
{
bolla.FileName =
$"{bolla.CodDtip}_{bolla.SerDoc.Replace("/", "-").Replace("\\", "-")}_{bolla.NumDoc:D6}_{dataDoc:yyMMdd}_{bolla.CodAnag}.pdf";
bolla.FileName = utilityFile.ComposeFileName(
bolla.CodDtip, bolla.DataDoc, bolla.SerDoc, bolla.NumDoc, bolla.CodAnag
);
var filePath = dataStorage.GetFilePath(bolla.FileName, true);
if (filePath != null)
{
bolla.Firmato = true;
}
}
}

View File

@@ -0,0 +1,14 @@
using ConSegna.Shared.Core.Interfaces;
namespace ConSegna.Maui.Core.System;
public class GenericSystemService : IGenericSystemService
{
public string GetCurrentAppVersion() => AppInfo.VersionString;
public void OpenSettings() => AppInfo.Current.ShowSettingsUI();
public void CloseApp() => Application.Current?.Quit();
public DateTime DataApp { get; set; } = DateTime.Today;
}

View File

@@ -1,10 +1,16 @@
using ConSegna.Shared.Core.Interfaces;
using Microsoft.Extensions.Logging;
namespace ConSegna.Maui.Services;
namespace ConSegna.Maui.Core.System;
public class GeolocationService(ILogger<GeolocationService> logger) : IGeolocationService
{
public async Task<bool> RequestAccess()
{
var perm = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
return perm == PermissionStatus.Granted;
}
private async Task<Location?> GetLocationAsync()
{
try

View File

@@ -0,0 +1,50 @@
using System.Text.RegularExpressions;
using ConSegna.Shared.Core.Interfaces;
using System.Globalization;
namespace ConSegna.Maui.Core.Utility;
public partial class UtilityFile : IUtilityFile
{
[GeneratedRegex(@".*_(\d{6})_.*")]
private static partial Regex Date_yyMMdd();
[GeneratedRegex(@".*_(\d{8})_.*")]
private static partial Regex Date_yyyyMMdd();
public string ComposeFileName(string codDtip, DateTime dataDoc, string serDoc, int numDoc, string codAnag)
{
var serDocReplaced = serDoc.Replace("/", "-").Replace("\\", "-");
return $"{codDtip}_{dataDoc:yyyyMMdd}_{codAnag}_{serDocReplaced}_{numDoc:D5}.pdf";
}
public DateTime? DetectDataDocFromFileName(string fileName)
{
var match = Date_yyMMdd().Match(fileName);
if (match.Success &&
DateTime.TryParseExact(
match.Groups[1].Value,
"yyMMdd",
null,
DateTimeStyles.None, out var date))
{
return date;
}
match = Date_yyyyMMdd().Match(fileName);
if (match.Success &&
DateTime.TryParseExact(
match.Groups[1].Value,
"yyyyMMdd",
null,
DateTimeStyles.None, out date))
{
return date;
}
return null;
}
}

View File

@@ -1,4 +1,8 @@
using CommunityToolkit.Maui;
using ConSegna.Maui.Core.Logger;
using ConSegna.Maui.Core.Services;
using ConSegna.Maui.Core.System;
using ConSegna.Maui.Core.Utility;
using ConSegna.Shared;
using ConSegna.Shared.Core.Helpers;
using ConSegna.Shared.Core.Interfaces;
@@ -6,8 +10,9 @@ using ConSegna.Shared.Core.Services;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.Logging;
using ConSegna.Maui.Services;
using ConSegna.Maui.Services.Logger;
using ConSegna.Shared.Core.Interfaces.Sync;
using ConSegna.Shared.Core.Messages;
using ConSegna.Shared.Core.Services.Sync;
using IntegryApiClient.MAUI;
using Maui.Android.InAppUpdates;
@@ -81,7 +86,18 @@ namespace ConSegna.Maui
builder.Services.AddSingleton<IFormFactor, FormFactor>();
builder.Services.AddSingleton<IDeviceOrientationService, DeviceOrientationService>();
builder.Services.AddSingleton<IGeolocationService, GeolocationService>();
builder.Services.AddSingleton<IGenericSystemService, GenericSystemService>();
builder.Services.AddSingleton<IUtilityFile, UtilityFile>();
builder.Services.AddSingleton<IUtilityFile, UtilityFile>();
builder.Services.AddSingleton<ILogReceiptsService, LogReceiptsService>();
builder.Services.AddSingleton<ISyncManager, SyncManager>();
builder.Services.AddSingleton<ISyncDayService, SyncDayService>();
builder.Services.AddSingleton<ISyncUploadService, SyncUploadService>();
builder.Services.AddSingleton<ISyncDownloadService, SyncDownloadService>();
builder.Services.AddSingleton<ISyncMergeService, SyncMergeService>();
builder.Services.AddSingleton<ISyncCleanupService, SyncCleanupService>();
return builder.Build();
}
}

View File

@@ -1,8 +1,8 @@
@using System.Text.Json
@inject IJSRuntime JS
@inject IJSRuntime Js
<div class="header">
<div class="header-content px-5">
<div class="header-content">
<div class="side">
@if (!HideBack)
{
@@ -13,6 +13,10 @@
</div>
<div class="center">
<h5>@Title</h5>
@if (ChildContent != null)
{
@ChildContent
}
</div>
<div class="side">
@if (!HideSearch)
@@ -32,7 +36,7 @@
<div class="modalSearch-searchBox">
<i class="px-2 ri-search-2-line"></i>
<InputText class="form-control" @bind-Value="SearchText" @bind-Value:after="Search"/>
<i @onclick="() => RapidSearch(null)" class="@(SearchText.IsNullOrEmpty() ? _closeButton : _closeButtonActive) px-2 ri-close-circle-fill"></i>
<i @onclick="() => RapidSearch(null)" class="@(SearchText.IsNullOrEmpty() ? CloseButton : CloseButtonActive) px-2 ri-close-circle-fill"></i>
</div>
<span @onclick="CloseModal" class="modalSearch-close-button ps-3">Annulla</span>
</div>
@@ -64,20 +68,22 @@
@code
{
[Parameter] public string Title { get; set; }
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public string Title { get; set; } = "";
[Parameter] public bool HideSearch { get; set; }
[Parameter] public EventCallback<string> OnSearch { get; set; }
[Parameter] public bool HideBack { get; set; } = true;
private Modal modal = default!;
private Modal modal = null!;
//Filter search
private string? SearchText { get; set; }
private List<string>? LatestResearch { get; set; } = [];
private string _closeButton = "close-button";
private string _closeButtonActive = "close-button active";
private const string CloseButton = "close-button";
private const string CloseButtonActive = "close-button active";
protected override Task OnInitializedAsync()
{
@@ -116,8 +122,7 @@
{
LatestResearch.Insert(0, SearchText!);
}
//Limita la grandezza della lista a 10 elementi
while (LatestResearch.Count > 10)
{
LatestResearch.RemoveAt(LatestResearch.Count - 1);
@@ -139,6 +144,6 @@
private async Task Back()
{
await JS.InvokeVoidAsync("goBack");
await Js.InvokeVoidAsync("goBack");
}
}

View File

@@ -1,11 +1,13 @@
.header {
background-color: var(--primary-color);
border-radius: 0 0 50px 50px;
border-radius: 2em;
position: fixed;
top: 0;
top: .5rem;
left: 0;
width: 100%;
width: calc(100% - 2rem);
z-index: 1000;
margin-left: 1rem;
box-shadow: 15px 15px 20px 0px #00000017;
}
.header-content {
@@ -13,20 +15,25 @@
justify-content: space-between;
color: var(--lighter-color);
align-items: center;
padding-top: .5rem;
padding: .25rem 1rem;
min-height: 44px;
}
.header-content > .side {
display: block;
min-width: 20px;
min-height: 30px;
margin-bottom: .5rem;
}
.center {
width: 100%;
}
.header-content > .center > h5 {
color: var(--lighter-color);
text-align: center;
font-weight: 800;
margin-bottom: 0 !important;
}
.header-content > .side ::deep > button {
@@ -35,9 +42,13 @@
font-size: large;
}
.header-content > .side ::deep > button:focus { box-shadow: none !important; }
.header-content > .side ::deep > button:focus {
box-shadow: none !important;
}
.sub-content { color: var(--lighter-color); }
.sub-content {
color: var(--lighter-color);
}
.latest {
display: flex;
@@ -50,7 +61,9 @@
font-weight: 500;
}
.latest > .body { cursor: pointer; }
.latest > .body {
cursor: pointer;
}
.latest > .body > div > i {
font-size: large;
@@ -65,12 +78,16 @@
.line-separator {
display: block;
width: 100%;
border: 1px solid hsl(from var(--disable-color) h s 90%);
border: .05rem solid hsl(from var(--disable-color) h s 90%);
}
.close-button { display: none; }
.close-button {
display: none;
}
.close-button.active { display: block; }
.close-button.active {
display: block;
}
@supports (-webkit-touch-callout: none) {
.header {

View File

@@ -8,6 +8,12 @@ main {
flex: 1;
}
.container{
display: flex;
flex-direction: column;
gap: 1rem;
}
.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}

View File

@@ -1,9 +1,16 @@
.navbar {
background-color: var(--lighter-color);
border-radius: 50px 50px 0 0;
position: fixed;
bottom: 0;
bottom: .5rem;
width: 100%;
z-index: 1000;
}
.container-fluid{
margin: 0 1rem;
padding: 0;
background: white;
border-radius: 3em;
box-shadow: 15px 15px 20px 0px #00000017;
}
.navbar-expand { padding: 0 !important; }

View File

@@ -2,7 +2,7 @@
@attribute [Authorize]
@using ConSegna.Shared.Core.Interfaces
@using ConSegna.Shared.Components.Layout
@using ConSegna.Shared.Components.SingleElements
@using ConSegna.Shared.Components.SingleElements.Input
@inject DetailConsegnaDto DetailConsegnaDto
@inject ModalRef ModalRef
@inject IFormFactor FormFactor
@@ -10,13 +10,15 @@
@inject IDataStorage DataStorage
@inject IEditFileServices EditFileServices
@inject SignaturePageData SignaturePageData
@inject ILogReceiptsService LogReceiptsService
<HeaderLayout HideBack="false" HideSearch="true" Title="Dettagli"/>
<div class="content">
<div class="my-2 top-detail">
<span class="cliente">@DetailConsegnaDto.DatiConsegne[0].Cliente</span>
<span class="indirizzo">@DetailConsegnaDto.DatiConsegne[0].Indirizzo - @DetailConsegnaDto.DatiConsegne[0].Citta</span>
<span
class="indirizzo">@DetailConsegnaDto.DatiConsegne[0].Indirizzo - @DetailConsegnaDto.DatiConsegne[0].Citta</span>
</div>
<div class="row card-container mb-3">
@@ -30,7 +32,7 @@
<span class="card-subtitle">Da incassare</span>
</div>
<div @onclick="OpenMap" class="col card-body">
<div @onclick="OpenMap" class="col card-body ripple-container">
<span class="card-title icon">
<i class="ri-navigation-line"></i>
</span>
@@ -62,12 +64,22 @@
<div class="carousel-sub-header">
<span class="note-title">
Note: <span class="note-text">@(doc.Note.IsNullOrEmpty() ? "Nessuna nota presente" : doc.Note)</span>
Note: <span
class="note-text">@(doc.Note.IsNullOrEmpty() ? "Nessuna nota presente" : doc.Note)</span>
</span>
<div @onclick="() => NuovaNota(doc)" class="button-container">
<div class="card-button note">
<div class="button-container">
<div @onclick="() => NuovaNota(doc)" class="card-button ripple-container note">
<span>Aggiungi note</span>
</div>
@if (DetailConsegnaDto.DatiConsegne[0].ImpIncasso > 0)
{
<div @onclick="() => RicalcolaImporto(doc)" class="card-button ripple-container note">
<i class="ri-calculator-line"></i>
<span>Ricalcola importo</span>
</div>
}
</div>
</div>
@@ -85,11 +97,11 @@
</div>
<div class="button-container">
<div @onclick="() => OpenPdf(doc)" class="card-button">
<div @onclick="() => OpenPdf(doc)" class="card-button ripple-container">
<span>Visualizza</span>
</div>
<div @onclick="() => OnClickFirma(doc)" class="card-button firma">
<div @onclick="() => OnClickFirma(doc)" class="card-button ripple-white firma ripple-container">
<i class="ri-edit-line"></i>
<span>Firma</span>
</div>
@@ -109,139 +121,145 @@
</div>
}
<div class="card-body">
<div class="carousel-header mb-2">
<div class="carousel-title">Registra incasso</div>
</div>
<div class="mb-3 d-flex justify-content-center">
<div class="row d-flex align-items-center" style="width: 100%">
<div class="col modalSearch-searchBox">
<i class="px-2 ri-wallet-line"></i>
<CurrencyInput
TValue="decimal"
Locale="fr-FR"
@bind-Value="@Incasso"
@bind-Value:after="CalcTotIncasso"
class="form-control"/>
<i @onclick="ClearIncasso" class="@(Incasso == 0 ? _closeButton : _closeButtonActive) px-2 ri-close-circle-fill"></i>
</div>
<span @onclick="AutoIncasso" class="col-auto modalSearch-close-button pe-0">Seleziona tutto</span>
@if (SystemService.DataApp == DateTime.Today)
{
<div class="card-body">
<div class="carousel-header mb-2">
<div class="carousel-title">Registra incasso</div>
</div>
</div>
@if (DetailConsegnaDto.Sospesi != null)
{
<div class="mb-3">
@foreach (var sospeso in DetailConsegnaDto.Sospesi.Where(sospeso => !sospeso.Pagato))
{
<div class="sospeso">
<div class="checkbox-wrapper">
<div>
<InputCheckbox AdditionalAttributes="@(Incasso <= 0 ? new Dictionary<string, object> { { "disabled", "disabled" } } : new Dictionary<string, object>())"
Value="sospeso.IsSelected"
ValueChanged="@(value => OnSospesoChanged(value, sospeso))"
ValueExpression="(() => sospeso.IsSelected)"/>
</div>
<div class="checkbox-info">
<div class="info-doc">
<span>@sospeso.CodDtip n.@sospeso.NumDoc</span>
<span>del @sospeso.DataDoc.ToString("d")</span>
</div>
<span class="importo">@($"{sospeso.ImpSospeso:C}")</span>
</div>
</div>
<div class="line-separator my-1"></div>
<div class="mb-3 d-flex justify-content-center">
<div class="row d-flex align-items-center" style="width: 100%">
<div class="col modalSearch-searchBox">
<i class="px-2 ri-wallet-line"></i>
<IyCurrencyInput @bind-Value="@Incasso"
@bind-Value:after="CalcTotIncasso"
Class="form-control"/>
<i @onclick="ClearIncasso"
class="@(Incasso == 0 ? _closeButton : _closeButtonActive) px-2 ri-close-circle-fill"></i>
</div>
}
<span @onclick="AutoIncasso" class="col-auto modalSearch-close-button pe-0">Seleziona tutto</span>
</div>
</div>
}
<div class="row card-container mb-3">
<div @onclick="@(() => SelectTipoIncasso("Contanti"))" class="col card-body bordered @Contanti">
@if (DetailConsegnaDto.Sospesi != null)
{
<div class="mb-3">
@foreach (var sospeso in DetailConsegnaDto.Sospesi.Where(sospeso => !sospeso.Pagato))
{
<div class="sospeso">
<div class="checkbox-wrapper">
<div>
<InputCheckbox
AdditionalAttributes="@(Incasso <= 0 ? new Dictionary<string, object> { { "disabled", "disabled" } } : new Dictionary<string, object>())"
Value="sospeso.IsSelected"
ValueChanged="@(value => OnSospesoChanged(value, sospeso))"
ValueExpression="(() => sospeso.IsSelected)"/>
</div>
<div class="checkbox-info">
<div class="info-doc">
<span>@sospeso.CodDtip n.@sospeso.NumDoc</span>
<span>del @sospeso.DataDoc.ToString("d")</span>
</div>
<span class="importo">@($"{sospeso.ImpSospeso:C}")</span>
</div>
</div>
<div class="line-separator my-1"></div>
</div>
}
</div>
}
<div class="row card-container mb-3">
<div @onclick="@(() => SelectTipoIncasso("Contanti"))"
class="ripple-container col card-body bordered @Contanti">
<span class="card-title icon">
<i class="ri-coin-line"></i>
</span>
<span class="card-subtitle">Contanti</span>
</div>
<span class="card-subtitle">Contanti</span>
</div>
<div @onclick="@(() => SelectTipoIncasso("Assegni"))" class="col card-body bordered @Assegni">
<div @onclick="@(() => SelectTipoIncasso("Assegni"))"
class="ripple-container col card-body bordered @Assegni">
<span class="card-title icon">
<i class="ri-refund-line"></i>
</span>
<span class="card-subtitle">Assegni</span>
</div>
<span class="card-subtitle">Assegni</span>
</div>
<div @onclick="@(() => SelectTipoIncasso("Carte"))" class="col card-body bordered @Carte">
<div @onclick="@(() => SelectTipoIncasso("Carte"))"
class="ripple-container col card-body bordered @Carte">
<span class="card-title icon">
<i class="ri-bank-card-line"></i>
</span>
<span class="card-subtitle">Carte</span>
</div>
</div>
<div class="dettaglio-incasso">
@if (!DocumentiPagati.IsNullOrEmpty())
{
<div class="sospeso mb-2">
<div class="checkbox-wrapper">
<div class="checkbox-info title">
<div class="info-doc">
<span>Documento</span>
</div>
<span class="importo">Tot. Pagato</span>
</div>
</div>
<span class="card-subtitle">Carte</span>
</div>
</div>
@foreach (var documento in DocumentiPagati)
<div class="dettaglio-incasso">
@if (!DocumentiPagati.IsNullOrEmpty())
{
<div>
<div class="sospeso mb-2">
<div class="checkbox-wrapper">
<div class="checkbox-info">
<div class="checkbox-info title">
<div class="info-doc">
<span>@documento.CodDtip n.@documento.NumDoc</span>
<span>del @documento.DataDoc.ToString("d")</span>
<span>Documento</span>
</div>
<span class="importo">@documento.ImportoPagato</span>
<span class="importo">Tot. Pagato</span>
</div>
</div>
<div class="line-separator my-1"></div>
</div>
@foreach (var documento in DocumentiPagati)
{
<div>
<div class="checkbox-wrapper">
<div class="checkbox-info">
<div class="info-doc">
<span>@documento.CodDtip n.@documento.NumDoc</span>
<span>del @documento.DataDoc.ToString("d")</span>
</div>
<span class="importo">@documento.ImportoPagato</span>
</div>
</div>
<div class="line-separator my-1"></div>
</div>
}
}
}
<div class="container-dettaglio">
<span class="title">Incassato</span>
<span class="price">@Incasso.ToString("C")</span>
<div class="container-dettaglio">
<span class="title">Incassato</span>
<span class="price">@Incasso.ToString("C")</span>
</div>
<div class="container-dettaglio">
<span class="title">Da incassare</span>
<span class="price">@DaPagare.ToString("C")</span>
</div>
<div class="line-separator my-1"></div>
<div class="container-dettaglio">
<span class="title">@TextTot</span>
<span class="price">@TotIncasso.ToString("C")</span>
</div>
</div>
<div class="container-dettaglio">
<span class="title">Da incassare</span>
<span class="price">@DaPagare.ToString("C")</span>
</div>
<div class="line-separator my-1"></div>
<div class="container-dettaglio">
<span class="title">@TextTot</span>
<span class="price">@TotIncasso.ToString("C")</span>
<div class="button-container justify-content-center">
<div @onclick="OnClickSaveSospesi" class="card-button firma ripple-container">
<span>Registra</span>
</div>
</div>
</div>
<div class="button-container justify-content-center">
<div @onclick="OnClickSaveSospesi" class="card-button firma">
<span>Registra</span>
</div>
</div>
</div>
}
</div>
<RicalcolaImportoModal DettaglioRighe="DettaglioRighe"/>
<ConfirmModal ConfirmText="@ConfirmModalText" ConfirmClick="@ConfirmModalClick"/>
<InputModal MaxLength="350" Title="Aggiungi nota" InputText="@Nota" ReturnText="OnReturnInputNote"/>
<SpinnerModal/>
@@ -272,6 +290,7 @@
private string _closeButtonActive = "close-button active";
private List<SospesiClienteDTO> DocumentiPagati { get; set; } = [];
private List<DettaglioRigheDTO> DettaglioRighe { get; set; } = [];
protected override async Task OnInitializedAsync()
{
@@ -420,6 +439,14 @@
await ModalRef.InputModal.ShowAsync();
}
private async Task RicalcolaImporto(DatiConsegneDTO doc)
{
DettaglioRighe = doc.DettaglioRighe;
StateHasChanged();
await ModalRef.RicalcolaImporto.ShowAsync();
}
private async Task OnReturnInputNote(string nota)
{
await ModalRef.SpinnerModal.ShowAsync();
@@ -591,6 +618,15 @@
private async Task SaveSospesi()
{
await ModalRef.SpinnerModal.ShowAsync();
string paymentMethod;
if (Contanti) paymentMethod = "Contanti";
else if (Assegni) paymentMethod = "Assegni";
else if (Carte) paymentMethod = "Carta";
else paymentMethod = "Non dichiarato";
await LogReceiptsService.AddReceipt(DocumentiPagati, Incasso, DaPagare, paymentMethod);
var dataPagamento = DateTime.Now;
if (DetailConsegnaDto.Sospesi == null) return;

View File

@@ -28,7 +28,7 @@
.card-body {
background-color: var(--lighter-color);
border-radius: 15px;
border-radius: 1.5em;
padding: .5rem 1.1rem;
}
@@ -125,7 +125,6 @@
.card-button.note {
padding: .1rem;
width: 40%;
}
.card-button.firma {

View File

@@ -4,23 +4,85 @@
@using ConSegna.Shared.Components.Layout.Spinner
@inject IFormFactor FormFactor
@inject INetworkService NetworkService
@inject IGeolocationService GeolocationService
@inject ModalRef ModalRef
@inject NavigationManager NavigationManager
@implements IDisposable
<SpinnerLayout/>
<SpinnerLayout />
<PermissionModal Title="Posizione obbligatoria!"
Message="@PositionException"
ActionButtonText="Impostazioni"
ActionButton="() => SystemService.OpenSettings()"/>
@code {
private const string PositionException = "L'accesso alla posizione <b>è obbligatoria</b> per le funzionalità del app; abilitala nell'app <b>Impostazioni</b> per continuare.";
private CancellationTokenSource? _cts;
private bool _isModalOpen;
@code
{
protected override Task OnInitializedAsync()
{
var lastSyncDate = DateOnly.FromDateTime(LocalStorage.Get<DateTime>("last-sync"));
var syncAntherDay = LocalStorage.Get<bool>("sync-another-day");
if (!FormFactor.IsWeb() && NetworkService.IsNetworkAvailable() && lastSyncDate < DateOnly.FromDateTime(DateTime.Now))
{
NavigationManager.NavigateTo("/sync");
return base.OnInitializedAsync();
}
if (FormFactor.IsWeb() || !NetworkService.IsNetworkAvailable() || (lastSyncDate == DateOnly.FromDateTime(DateTime.Now) && !syncAntherDay)) return Task.CompletedTask;
var returnPath = System.Web.HttpUtility.UrlEncode("/");
NavigationManager.NavigateTo($"/sync?path={returnPath}");
NavigationManager.NavigateTo("/Consegne");
return base.OnInitializedAsync();
return Task.CompletedTask;
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
_cts = new CancellationTokenSource();
_ = PermissionLoop(_cts.Token);
}
}
private async Task PermissionLoop(CancellationToken token)
{
try
{
while (!token.IsCancellationRequested)
{
var hasPermission = await GeolocationService.RequestAccess();
if (hasPermission)
{
if (_isModalOpen)
{
await ModalRef.PermissionModal.HideAsync();
_isModalOpen = false;
}
NavigationManager.NavigateTo("/Consegne", replace: true);
await _cts?.CancelAsync()!;
return;
}
if (!_isModalOpen)
{
await ModalRef.PermissionModal.ShowAsync();
_isModalOpen = true;
}
await Task.Delay(2000, token);
}
}
catch (TaskCanceledException)
{
// Il task è stato cancellato
}
}
public void Dispose()
{
_cts?.Cancel();
_cts?.Dispose();
}
}

View File

@@ -1,9 +1,6 @@
@page "/settings"
@attribute [Authorize]
@using ConSegna.Shared.Core.Helpers
@using ConSegna.Shared.Core.Interfaces
@using ConSegna.Shared.Components.Layout
@using ConSegna.Shared.Components.SingleElements
@inject IDataStorage DataStorage
@inject ModalRef ModalRef
@@ -20,7 +17,7 @@
</div>
</div>
<div @onclick="() => ViewLog()" class="card-body my-3">
<div @onclick="ViewLog" class="card-body ripple-container my-3">
<div class="titleCard">
<div class="textTitle">
<i class="ri-bug-line"></i>
@@ -38,6 +35,16 @@
</div>
</div>
</div>
<div @onclick="OpenListReceipts" class="card-body ripple-container my-3">
<div class="button">
<div>
<i class="ri-receipt-line"></i>
<span>Log incassi</span>
</div>
<i class="ri-arrow-right-s-line"></i>
</div>
</div>
<div class="card-body my-3">
<div class="titleCard">
@@ -73,7 +80,7 @@
@foreach (var doc in DocList!)
{
<div class="documentList">
<div @onclick="() => OpenPdf(doc.Name, false)" class="document">
<div @onclick="() => OpenPdf(doc.Name, false)" class="ripple-container document">
<i class="ri-file-pdf-2-line"></i>
<span>@doc.Name</span>
</div>
@@ -127,21 +134,23 @@
</div>
<ViewLogModal Log="@LogText" />
<SelectReceipt FileList="Receipts" SelectClick="ViewReceipts" />
@code {
private List<FileInfo>? DocList { get; set; }
private List<FileInfo>? SignedDocList { get; set; }
private List<FileInfo>? Certificate { get; set; }
private List<FileInfo> Receipts { get; set; } = [];
private FileInfo? LogFile { get; set; }
private string LogText { get; set; } = "";
protected override Task OnInitializedAsync()
{
DocList = DataStorage.RetrieveAllFile(false, false);
SignedDocList = DataStorage.RetrieveAllFile(true, false);
DocList = DataStorage.RetrieveAllFile(false, false)?.OrderBy(x => x.LastWriteTime).ToList();
SignedDocList = DataStorage.RetrieveAllFile(true, false)?.OrderBy(x => x.LastWriteTime).ToList();
Certificate = DataStorage.RetrieveAllFile(false, true);
Receipts = DataStorage.RetrieveAllReceipts().OrderByDescending(x => x.LastWriteTime).ToList();
LogFile = DataStorage.RetrieveLogFile();
return base.OnInitializedAsync();
@@ -157,4 +166,13 @@
await ModalRef.ViewLogModal.ShowAsync();
}
private async Task ViewReceipts(string fileName)
{
await DataStorage.OpenReceiptFile(fileName);
}
private async Task OpenListReceipts()
{
await ModalRef.SelectReceiptModal.ShowAsync();
}
}

View File

@@ -1,6 +1,6 @@
.card-body {
background-color: var(--lighter-color);
border-radius: 15px;
border-radius: 1.5em;
padding: .5rem 1.1rem;
font-weight: 600;
}

View File

@@ -1,14 +1,16 @@
@page "/Incassi"
@attribute [Authorize]
@using ConSegna.Shared.Core.Interfaces
@using ConSegna.Shared.Components.Layout
@using ConSegna.Shared.Components.Layout.Spinner
@using ConSegna.Shared.Components.SingleElements
@inject IDataStorage DataStorage
@inject IIntegryApiService IntegryApiService
@inject ModalRef ModalRef
<HeaderLayout HideSearch="true" Title="Incassi"/>
<HeaderLayout HideSearch="true" Title="Incassi">
<div class="center">
<h5 style="font-size: small">@($"Incassi del {SystemService.DataApp:d}")</h5>
</div>
</HeaderLayout>
@if (DatiClienti.IsNullOrEmpty())
{
@@ -17,220 +19,218 @@
else
{
<div class="content">
<div class="card-body mb-3">
<div class="titleCard">
<div class="textTitle">
<span>Incassi del @($"{DateTime.Now:d}")</span>
</div>
</div>
</div>
<div class="card-body subTitle my-2">
<div class="subTitleCard">
<div class="textTitle">
<span>Incasso bolle della giornata</span>
</div>
</div>
</div>
<div class="row card-container mb-3">
<div class="col card-body">
<div class="totalCashDay">
<div class="titleTotal">
<span>Tot. previsto</span>
</div>
<div class="totPrev">
<span>@($"{TotAtteso:C}")</span>
@if (SystemService.DataApp == DateTime.Today)
{
<div class="card-body my-2">
<div class="subTitleCard">
<div class="textTitle">
<span>Incasso bolle della giornata</span>
</div>
</div>
</div>
<div class="col card-body">
<div class="totalCashDay">
<div class="titleTotal">
<span>Tot. incasso</span>
</div>
<div class="row card-container">
<div class="col card-body">
<div class="totalCashDay">
<div class="titleTotal">
<span>Tot. previsto</span>
</div>
<div class="totIncasso">
<span>@($"{TotIncassato:C}")</span>
</div>
</div>
</div>
<div class="col card-body">
<div class="totalCashDay">
<div class="titleTotal">
<span>Tot. sospeso</span>
</div>
<div class="totSospeso">
<span>@($"{TotSospeso:C}")</span>
</div>
</div>
</div>
</div>
<div class="card-body subTitle my-2">
<div class="subTitleCard">
<div class="textTitle">
<span>Incasso generale</span>
</div>
</div>
</div>
<div class="row card-container mb-3">
<div class="col card-body">
<div class="totalCashDay">
<div class="titleTotal">
<span>Tot. incassato</span>
</div>
<div class="totIncasso">
<span>@($"{TotIncassoGiorno:C}")</span>
</div>
</div>
</div>
</div>
<div class="card-body my-3">
<div class="titleCard">
<div class="textTitle">
<span>Dettaglio incasso</span>
</div>
</div>
</div>
<div class="row card-container mb-3">
<div class="col card-body">
<span class="card-title icon">
<i class="ri-coin-line"></i>
</span>
<span class="card-subtitle">@($"{TotCash:C}")</span>
</div>
<div class="col card-body">
<span class="card-title icon">
<i class="ri-refund-line"></i>
</span>
<span class="card-subtitle">@($"{TotCheck:C}")</span>
</div>
<div class="col card-body">
<span class="card-title icon">
<i class="ri-bank-card-line"></i>
</span>
<span class="card-subtitle">@($"{TotCard:C}")</span>
</div>
<div class="col card-body">
<span class="card-title icon">
<i class="ri-more-fill"></i>
</span>
<span class="card-subtitle">@($"{TotGeneric:C}")</span>
</div>
</div>
<div class="card-body">
<div class="bodyCard">
<div class="titleTotal">
<span>Pagamenti in contanti</span>
<span>@CountCash</span>
</div>
</div>
<div class="line-separator my-1"></div>
<div class="bodyCard">
<div class="titleTotal">
<span>Pagamenti con assegni</span>
<span>@CountCheck</span>
</div>
</div>
<div class="line-separator my-1"></div>
<div class="bodyCard">
<div class="titleTotal">
<span>Pagamenti con carta</span>
<span>@CountCard</span>
</div>
</div>
<div class="line-separator my-1"></div>
<div class="bodyCard">
<div class="titleTotal">
<span>Altri pagamenti</span>
<span>@CountGeneric</span>
</div>
</div>
</div>
<div class="card-body my-3">
<div class="titleCard">
<div class="textTitle">
<span>Dettaglio sospesi</span>
</div>
</div>
</div>
<div class="card-body">
<div class="bodyCard">
<div class="titleTotal">
<span>Tot. documenti sospesi</span>
<span>@(DocSospesi.IsNullOrEmpty() ? "0" : DocSospesi!.Count)</span>
</div>
</div>
@if (DocSospesi is { Count: > 0 })
{
<div class="line-separator my-1"></div>
@foreach (var docSospeso in DocSospesi.Select((value, index) => new { value, index }))
{
var paymentData = docSospeso.value.PaymentData;
var paid = false;
var partialImp = false;
var totalPaid = decimal.Zero;
if (paymentData?.Any() == true)
{
if (paymentData.Count >= 1)
{
totalPaid = paymentData.Sum(x => x.ImportoPagato);
if (totalPaid < docSospeso.value.TotDoc)
{
partialImp = true;
}
}
}
<div class="bodyCard">
<div class="titleTotal">
<span class="docInfo">@($"{docSospeso.value.CodDtip} del {docSospeso.value.DataDoc:d} n. {docSospeso.value.SerDoc}/{docSospeso.value.NumDoc}")</span>
<div>
<span class="@("detail-info-text " + (partialImp ? "ritardo" : ""))">@($"{docSospeso.value.TotDoc:C}")</span>
@if (partialImp)
{
<span class="detail-info-text">@($"{docSospeso.value.TotDoc - totalPaid:C}")</span>
}
<div class="totPrev">
<span>@($"{TotAtteso:C}")</span>
</div>
</div>
</div>
@if (docSospeso.index < DocSospesi.Count - 1)
<div class="col card-body">
<div class="totalCashDay">
<div class="titleTotal">
<span>Tot. incasso</span>
</div>
<div class="totIncasso">
<span>@($"{TotIncassato:C}")</span>
</div>
</div>
</div>
<div class="col card-body">
<div class="totalCashDay">
<div class="titleTotal">
<span>Tot. sospeso</span>
</div>
<div class="totSospeso">
<span>@($"{TotSospeso:C}")</span>
</div>
</div>
</div>
</div>
</div>
<div class="card-body my-2">
<div class="subTitleCard">
<div class="textTitle">
<span>Incasso generale</span>
</div>
</div>
<div class="row card-container">
<div class="col card-body">
<div class="totalCashDay">
<div class="titleTotal">
<span>Tot. incassato</span>
</div>
<div class="totIncasso">
<span>@($"{TotIncassoGiorno:C}")</span>
</div>
</div>
</div>
</div>
</div>
<div class="card-body my-3">
<div class="titleCard">
<div class="textTitle">
<span>Dettaglio incasso</span>
</div>
</div>
<div class="row card-container">
<div class="col card-body">
<span class="card-title icon">
<i class="ri-coin-line"></i>
</span>
<span class="card-subtitle">@($"{TotCash:C}")</span>
</div>
<div class="col card-body">
<span class="card-title icon">
<i class="ri-refund-line"></i>
</span>
<span class="card-subtitle">@($"{TotCheck:C}")</span>
</div>
<div class="col card-body">
<span class="card-title icon">
<i class="ri-bank-card-line"></i>
</span>
<span class="card-subtitle">@($"{TotCard:C}")</span>
</div>
<div class="col card-body">
<span class="card-title icon">
<i class="ri-more-fill"></i>
</span>
<span class="card-subtitle">@($"{TotGeneric:C}")</span>
</div>
</div>
</div>
<div class="card-body">
<div class="bodyCard">
<div class="titleTotal">
<span>Pagamenti in contanti</span>
<span>@CountCash</span>
</div>
</div>
<div class="line-separator my-1"></div>
<div class="bodyCard">
<div class="titleTotal">
<span>Pagamenti con assegni</span>
<span>@CountCheck</span>
</div>
</div>
<div class="line-separator my-1"></div>
<div class="bodyCard">
<div class="titleTotal">
<span>Pagamenti con carta</span>
<span>@CountCard</span>
</div>
</div>
<div class="line-separator my-1"></div>
<div class="bodyCard">
<div class="titleTotal">
<span>Altri pagamenti</span>
<span>@CountGeneric</span>
</div>
</div>
</div>
<div class="card-body my-3">
<div class="titleCard">
<div class="textTitle">
<span>Dettaglio sospesi</span>
</div>
</div>
</div>
<div class="card-body">
<div class="bodyCard">
<div class="titleTotal">
<span>Tot. documenti sospesi</span>
<span>@(DocSospesi.IsNullOrEmpty() ? "0" : DocSospesi!.Count)</span>
</div>
</div>
@if (DocSospesi is { Count: > 0 })
{
<div class="line-separator my-1"></div>
foreach (var docSospeso in DocSospesi.Select((value, index) => new { value, index }))
{
<div class="line-separator my-1"></div>
var paymentData = docSospeso.value.PaymentData;
var paid = false;
var partialImp = false;
var totalPaid = decimal.Zero;
if (paymentData?.Any() == true)
{
if (paymentData.Count >= 1)
{
totalPaid = paymentData.Sum(x => x.ImportoPagato);
if (totalPaid < docSospeso.value.TotDoc)
{
partialImp = true;
}
}
}
<div class="bodyCard">
<div class="titleTotal">
<span
class="docInfo">@($"{docSospeso.value.CodDtip} del {docSospeso.value.DataDoc:d} n. {docSospeso.value.SerDoc}/{docSospeso.value.NumDoc}")</span>
<div>
<span
class="@("detail-info-text " + (partialImp ? "ritardo" : ""))">@($"{docSospeso.value.TotDoc:C}")</span>
@if (partialImp)
{
<span
class="detail-info-text">@($"{docSospeso.value.TotDoc - totalPaid:C}")</span>
}
</div>
</div>
</div>
@if (docSospeso.index < DocSospesi.Count - 1)
{
<div class="line-separator my-1"></div>
}
}
}
}
</div>
</div>
}
<div class="card-body my-3">
<div class="card-body ripple-container my-3">
<div @onclick="SelectPrinter" class="button">
<div>
<i class="ri-printer-line"></i>
@@ -289,6 +289,7 @@ else
TotIncassato = sospesiClienteList.Sum(x => x.ImportoPagato > x.ImpSospeso ? x.ImpSospeso : x.ImportoPagato);
TotSospeso = TotAtteso - TotIncassato;
TotSospeso = TotAtteso < 0 ? 0 : TotAtteso;
if (allPaymentData is not null)
{
@@ -336,16 +337,11 @@ else
{
await ModalRef.SelectPrinterModal.ShowAsync();
}
private async Task PrintReport(string printer)
{
await ModalRef.SpinnerModal.ShowAsync();
#if DEBUG
var dataDoc = new DateTime(2025, 06, 04);
//var dataDoc = DateTime.Today;
#else
var dataDoc = DateTime.Today;
#endif
var dataDoc = SystemService.DataApp;
var jasperDto = new JasperDTO
{

View File

@@ -1,6 +1,6 @@
.card-body {
background-color: var(--lighter-color);
border-radius: 15px;
border-radius: 1.5em;
padding: .5rem 1.1rem;
font-weight: 600;
}
@@ -17,7 +17,6 @@
}
.card-container > .card-body {
min-height: 5rem;
display: flex;
flex-direction: column;
align-items: center;
@@ -135,4 +134,24 @@
font-size: 1.5rem;
line-height: normal;
font-weight: normal;
}
.center {
width: 100%;
color: var(--lighter-color);
text-align: center;
display: flex;
justify-content: center;
align-items: center;
gap: 1rem;
line-height: normal;
}
.center > h5 {
margin-bottom: 0 !important;
font-weight: 800;
}
.center > i {
font-weight: 800;
}

View File

@@ -1,27 +1,28 @@
@page "/Consegne"
@attribute [Authorize]
@using ConSegna.Shared.Core.Interfaces
@using ConSegna.Shared.Components.Layout.Spinner
@using ConSegna.Shared.Components.Layout
@using ConSegna.Shared.Components.SingleElements.Input
@inject IDataStorage DataStorage
@inject Consegne Consegne
@inject INetworkService NetworkService
<HeaderLayout HideSearch="true" Title="Consegne"/>
<HeaderLayout HideSearch="true" Title="Consegne">
<div class="center" @onclick="OpenCalendar">
@* <div class="center"> *@
<h5 style="font-size: small">@($"Giro consegne del {SystemService.DataApp:d}")</h5>
<DatePickerPopup @ref="_datePicker"
SelectedDate="SystemService.DataApp"
SelectedDateChanged="OnDateChangedWithValue"/>
</div>
</HeaderLayout>
<div class="content">
@if (!GroupedList.IsNullOrEmpty())
{
<div class="card-body mb-3">
<div class="titleCard">
<div class="textTitle">
<span>Giro consegne del @($"{DateTime.Now:d}")</span>
</div>
</div>
</div>
@foreach (var dati in GroupedList!)
foreach (var dati in GroupedList!)
{
<div class="card-body mb-2">
<div class="card-body mb-3 ripple-container">
<div class="city" @onclick="() => OpenPageConsegne(dati.Value)">
<div class="right">
<i class="ri-building-2-line"></i>
@@ -49,6 +50,8 @@
private bool RetrieveFinished { get; set; }
private DatePickerPopup? _datePicker;
protected override async Task OnInitializedAsync()
{
RetrieveFinished = false;
@@ -83,4 +86,18 @@
NavigationManager.NavigateTo("/Consegne/List");
}
private Task OnDateChangedWithValue(DateTime? newDate)
{
SystemService.DataApp = newDate!.Value;
NavigationManager.NavigateTo("sync");
return Task.CompletedTask;
}
private void OpenCalendar()
{
if (!NetworkService.IsNetworkAvailable()) return;
_datePicker?.ToggleDropdown();
}
}

View File

@@ -1,6 +1,6 @@
.card-body {
background-color: var(--lighter-color);
border-radius: 15px;
border-radius: 1.5em;
padding: .5rem 1.1rem;
font-weight: 600;
}
@@ -19,7 +19,9 @@
align-items: center;
}
.city > i { color: var(--disable-color); }
.city > i {
color: var(--disable-color);
}
.city i {
font-size: 1.5rem;
@@ -30,4 +32,24 @@
.titleCard {
font-size: medium;
font-weight: 800;
}
.center {
width: 100%;
color: var(--lighter-color);
text-align: center;
display: flex;
justify-content: center;
align-items: center;
gap: 1rem;
line-height: normal;
}
.center > h5 {
margin-bottom: 0 !important;
font-weight: 800;
}
.center > i {
font-weight: 800;
}

View File

@@ -1,164 +1,10 @@
@page "/sync"
@using ConSegna.Shared.Core.Interfaces
@using ConSegna.Shared.Components.Layout.Spinner
@using Microsoft.Extensions.Logging
@attribute [Authorize]
@inject ISyncStorage SyncStorage
@inject IDataStorage DataStorage
@inject IIntegryApiService IntegryApiService
@inject ILogger<SyncPage> Logger
@inherits SyncPageBase
<NavigationLock ConfirmExternalNavigation="true"
OnBeforeInternalNavigation="PreventNavigation" />
<SyncSpinner Text="@SyncText"/>
@code {
private string SyncText { get; set; }
protected override async Task OnInitializedAsync()
{
var lastSyncDate = DateOnly.FromDateTime(LocalStorage.Get<DateTime>("last-sync"));
SyncText = "Download certificato";
StateHasChanged();
var certificateName = await SyncStorage.GetAndSaveCertificate();
LocalStorage.SetString("certificate-name", certificateName);
SyncText = "Sincronizzazione sospesi";
StateHasChanged();
var sospesi = await DataStorage.RetrieveAllSospesi();
if (!sospesi.IsNullOrEmpty()) await IntegryApiService.SyncSospesi(sospesi!);
var paymentData = await DataStorage.RetrieveAllPaymentData();
SyncText = "Sincronizzazione bolle";
StateHasChanged();
var data = await DataStorage.RetrieveDatiClienti();
var bolle = data
.Where(x => !x.Sync)
.SelectMany(x => x.DatiConsegne.FindAll(x => !x.Note.IsNullOrEmpty() || x.Firmato))
.ToList();
var counter = 0;
if (!bolle.IsNullOrEmpty())
{
SyncText = "Sincronizzazione bolle (0%)";
StateHasChanged();
foreach (var bolla in from bolla in bolle let filesAttached = (FilesAttachedDTO?)null select bolla)
{
if (bolla.Firmato)
{
var filePath = DataStorage.GetFilePath(bolla.FileName, true);
if (filePath != null)
{
var file = DataStorage.GetFile(filePath!);
await IntegryApiService.Upload(file, bolla);
}
else
{
Logger.LogWarning("Path file firmato null");
}
}
await IntegryApiService.SyncDocument(bolla);
var currentIndex = Interlocked.Increment(ref counter);
var percentage = (int)((double)currentIndex / bolle.Count * 100);
SyncText = $"Sincronizzazione bolle ({percentage}%)";
StateHasChanged();
}
}
SyncText = "Sincronizzazione consegne";
StateHasChanged();
await SyncStorage.GetAndSaveDatiClienti();
var retrieveDatiClienti = await DataStorage.RetrieveDatiClienti();
if (!paymentData.IsNullOrEmpty() && lastSyncDate == DateOnly.FromDateTime(DateTime.Now))
{
paymentData!.ForEach(x => x.Id = 0);
await SyncStorage.InsertPaymantData(paymentData!);
}
DataStorage.ClearSignedDocuments();
SyncText = "Download bolle";
StateHasChanged();
var jasperDtoList = CreateJasperDtoList(retrieveDatiClienti);
DataStorage.ClearOriginalDocumentsDirectory();
counter = 0;
foreach (var jasperDto in jasperDtoList)
{
await SyncStorage.GetAndSaveFile(jasperDto.Item1, jasperDto.Item2);
var currentIndex = Interlocked.Increment(ref counter);
var percentage = (int)((double)currentIndex / jasperDtoList.Count * 100);
SyncText = $"Download bolle ({percentage}%)";
StateHasChanged();
}
SyncText = "";
StateHasChanged();
LocalStorage.Set("last-sync", DateTime.Now);
NavigationManager.NavigateTo("/Consegne");
}
private static List<(JasperDTO, string)> CreateJasperDtoList(List<DatiClientiDTO> retrieveDatiClienti)
{
#if DEBUG
var dataDoc = new DateTime(2025, 06, 04);
//var dataDoc = DateTime.Today;
#else
var dataDoc = DateTime.Today;
#endif
List<(JasperDTO, string)> returnData = [];
foreach (var bolla in retrieveDatiClienti.SelectMany(datiClienti => datiClienti.DatiConsegne))
{
var jasperDto = new JasperDTO
{
ReportName = bolla.ReportName,
TypeExport = "PDF",
Params =
[
new PairsDTO
{
Name = "cod_anag",
Value = bolla.CodAnag
},
new PairsDTO
{
Name = "ser_doc",
Value = bolla.SerDoc
},
new PairsDTO
{
Name = "num_doc",
Value = bolla.NumDoc
},
new PairsDTO
{
Name = "data_doc",
Value = $"{dataDoc:yyyy-MM-dd}"
},
new PairsDTO
{
Name = "cod_dtip",
Value = bolla.CodDtip
},
new PairsDTO
{
Name = "note",
Value = "N"
}
]
};
returnData.Add((jasperDto, $"{bolla.CodDtip}_{bolla.SerDoc.Replace("/", "-").Replace("\\", "-")}_{bolla.NumDoc:D6}_{dataDoc:yyMMdd}_{bolla.CodAnag}.pdf"));
}
return returnData;
}
}
<AppVersion/>

View File

@@ -0,0 +1,80 @@
using ConSegna.Shared.Core.Interfaces;
using ConSegna.Shared.Core.Interfaces.Sync;
using IntegryApiClient.Core.Domain.Abstraction.Contracts.Storage;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Routing;
namespace ConSegna.Shared.Components.Pages;
public class SyncPageBase : ComponentBase
{
[Inject] public NavigationManager Nav { get; set; }
[Inject] public ISyncDayService DayService { get; set; }
[Inject] public ISyncUploadService UploadService { get; set; }
[Inject] public ISyncDownloadService DownloadService { get; set; }
[Inject] public ISyncMergeService MergeService { get; set; }
[Inject] public ISyncCleanupService CleanupService { get; set; }
[Inject] public ILocalStorage LocalStorage { get; set; }
[Inject] public IGenericSystemService SystemService { get; set; }
protected string SyncText = "Avvio sincronizzazione";
private bool _allowNavigation;
protected override async Task OnInitializedAsync()
{
await Task.Delay(150);
await RunSync();
}
private async Task RunSync()
{
UpdateProgress("Preparazione");
if (await DayService.ShouldResetDatabase())
await DayService.ResetDatabaseAndArchive();
await CleanupService.Cleanup();
UpdateProgress("Download certificato");
await DownloadService.DownloadCertificate();
UpdateProgress("Sincronizzazione incassi");
await UploadService.UploadData();
UpdateProgress("Sincronizzazione bolle");
await UploadService.UploadDocument();
UpdateProgress("Download dati consegne");
var server = await DownloadService.DownloadConsegne();
UpdateProgress("Download bolle");
await DownloadService.DownloadFile(server);
UpdateProgress("Preparazione");
await MergeService.Merge(server);
UpdateProgress("Completamento");
await Task.Delay(300);
_allowNavigation = true;
if (SystemService.DataApp == DateTime.Today)
LocalStorage.Set("last-sync", DateTime.Now);
LocalStorage.Set("sync-another-day", SystemService.DataApp != DateTime.Today);
Nav.NavigateTo("/Consegne", replace: true);
}
protected void PreventNavigation(LocationChangingContext ctx)
{
if (!_allowNavigation)
ctx.PreventNavigation();
}
private void UpdateProgress(string msg)
{
SyncText = msg;
InvokeAsync(StateHasChanged);
}
}

View File

@@ -1,7 +1,6 @@
@page "/Utente"
@attribute [Authorize]
@using ConSegna.Shared.Core.Interfaces
@using ConSegna.Shared.Core.Services
@using Microsoft.Extensions.Logging
@using ConSegna.Shared.Components.Layout
@inject AppAuthenticationStateProvider AuthenticationStateProvider
@@ -14,44 +13,40 @@
<div class="content">
<div class="card-body">
<div class="userInfoContainer">
<div class="userIcon">
<i class="ri-user-line"></i>
</div>
<div class="userInfo">
<span class="username">@Fullname</span>
<div class="line-separator my-1"></div>
@if (NetworkService.IsNetworkAvailable())
{
<div class="status online">
<i class="ri-wifi-line"></i>
<span>Online</span>
<div class="d-flex justify-content-between">
<div class="date-container">
<span>Stato connessione</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>
}
else
{
<div class="status offline">
<i class="ri-wifi-off-line"></i>
<span>Offline</span>
<div class="date-container">
<span>Ultima sincronizzazione</span>
<span class="date">@LastSync.ToString("g")</span>
</div>
}
</div>
</div>
</div>
</div>
<div class="card-body mt-3">
<div class="button">
<div>
<i class="ri-time-line"></i>
<div class="date-container">
<span>Ultima sincronizzazione</span>
<span class="date">@LastSync.ToString("g")</span>
</div>
</div>
</div>
<div class="line-separator my-1"></div>
<div class="card-body ripple-container mt-3">
<div @onclick="Sync" class="button @(Unavailable ? "unavailable" : "")">
<div>
<i class="ri-loop-left-line"></i>
@@ -60,8 +55,8 @@
<i class="ri-arrow-right-s-line"></i>
</div>
</div>
<div class="card-body mt-3">
<div class="card-body ripple-container mt-3">
<div @onclick="OpenSettings" class="button">
<div>
<i class="ri-code-s-slash-line"></i>
@@ -71,7 +66,7 @@
</div>
</div>
<div class="card-body mt-3">
<div class="card-body ripple-container mt-3">
<div @onclick="SignOut" class="button">
<div>
<i class="ri-logout-box-line"></i>
@@ -80,6 +75,8 @@
<i class="ri-arrow-right-s-line"></i>
</div>
</div>
<AppVersion/>
</div>
@code {
@@ -130,4 +127,5 @@
{
NavigationManager.NavigateTo("settings");
}
}

View File

@@ -1,6 +1,6 @@
.card-body {
background-color: var(--lighter-color);
border-radius: 15px;
border-radius: 1.5em;
padding: .5rem 1.1rem;
font-weight: 600;
}
@@ -23,18 +23,27 @@
color: var(--dark-gray);
}
.userInfo { line-height: normal; }
.userInfo {
line-height: normal;
width: 100%;
}
.username {
font-size: large;
font-weight: 900;
}
.status { font-weight: 700; }
.status {
font-weight: 700;
}
.status.online { color: var(--lighter-green); }
.status.online {
color: var(--lighter-green);
}
.status.offline { color: var(--red); }
.status.offline {
color: var(--red);
}
.button {
font-size: 1.1rem;
@@ -50,9 +59,13 @@
align-items: center;
}
.button > i { color: var(--disable-color); }
.button > i {
color: var(--disable-color);
}
.button.unavailable { color: var(--disable-color) !important; }
.button.unavailable {
color: var(--disable-color) !important;
}
.button i {
font-size: 1.5rem;

View File

@@ -1,7 +1,3 @@
@using Microsoft.Extensions.Logging
@using ConSegna.Shared.Components.SingleElements
@inject ILogger<Routes> Logger
<ErrorBoundary @ref="ErrorBoundary">
<ChildContent>
<CascadingAuthenticationState>
@@ -36,13 +32,11 @@
<ErrorContent>
<ExceptionModal @ref="ExceptionModal"
Exception="@context"
ErrorBoundary="@ErrorBoundary"
OnRetry="() => ErrorBoundary?.Recover()"/>
Exception="@context"/>
</ErrorContent>
</ErrorBoundary>
@code {
private ErrorBoundary? ErrorBoundary { get; set; }
private ExceptionModal ExceptionModal { get; set; }
private ExceptionModal? ExceptionModal { get; set; }
}

View File

@@ -0,0 +1,17 @@
<div class="app-version">
<span>@AppVersionString</span>
</div>
@code
{
private string AppVersionString { get; set; } = "";
protected override void OnInitialized()
{
#if DEBUG
AppVersionString = $"v{SystemService.GetCurrentAppVersion()} [DEBUG]";
#else
AppVersionString = $"v{SystemService.GetCurrentAppVersion()}";
#endif
}
}

View File

@@ -0,0 +1,11 @@
.app-version{
width: 100%;
display: flex;
justify-content: center;
margin: 8px 0;
}
.app-version span{
font-size: smaller;
color: #616161;
}

View File

@@ -0,0 +1,117 @@
@using System.Globalization
<div class="datepicker-wrapper">
<i class="bi bi-calendar-event"
@onclick="ToggleDropdown"
@onclick:stopPropagation>
</i>
@if (_isOpen)
{
<div class="datepicker-backdrop"
@onclick="CloseDropdown"
@onclick:stopPropagation>
</div>
<div class="datepicker-dialog card shadow-lg"
@onclick:stopPropagation>
<div class="card-header bg-white border-bottom-0 d-flex justify-content-between align-items-center">
<i @onclick="PrevMonth"
@onclick:stopPropagation
class="ri-arrow-left-wide-line ripple-container"></i>
<span class="fw-bold text-capitalize">
@_currentMonth.ToString("MMMM yyyy", CultureInfo.CreateSpecificCulture("it-IT"))
</span>
<i @onclick="NextMonth"
@onclick:stopPropagation
class="ri-arrow-right-wide-line ripple-container"></i>
</div>
<div class="card-body p-2">
<div class="calendar-grid">
@foreach (var dayName in _dayNames)
{
<div class="text-center text-muted small fw-bold">@dayName</div>
}
@for (var i = 0; i < OffsetDays; i++)
{
<div></div>
}
@for (var day = 1; day <= DaysInMonth; day++)
{
var date = new DateTime(_currentMonth.Year, _currentMonth.Month, day);
var isSelected = SelectedDate.HasValue && SelectedDate.Value.Date == date;
var isToday = date == DateTime.Today;
if (date > DateTime.Today)
{
<div class="day-cell disabled">@day</div>
}
else
{
<div class="day-cell @(isSelected ? "selected-day" : "") @(isToday ? "today-day" : "")"
@onclick="() => SelectDate(date)"
@onclick:stopPropagation>
@day
</div>
}
}
</div>
</div>
<div class="card-footer bg-white border-top-0 text-end">
<div class="card-button"
@onclick="CloseDropdown"
@onclick:stopPropagation>
<span>Annulla</span>
</div>
</div>
</div>
}
</div>
@code {
[Parameter] public DateTime? SelectedDate { get; set; }
[Parameter] public EventCallback<DateTime?> SelectedDateChanged { get; set; }
private bool _isOpen;
private DateTime _currentMonth = DateTime.Today;
private readonly string[] _dayNames = ["Lu", "Ma", "Me", "Gi", "Ve", "Sa", "Do"];
private int DaysInMonth => DateTime.DaysInMonth(_currentMonth.Year, _currentMonth.Month);
private int OffsetDays
{
get
{
var firstDay = new DateTime(_currentMonth.Year, _currentMonth.Month, 1);
var dayOfWeek = (int)firstDay.DayOfWeek;
return (dayOfWeek == 0 ? 7 : dayOfWeek) - 1;
}
}
public void ToggleDropdown()
{
_isOpen = !_isOpen;
if (_isOpen && SelectedDate.HasValue)
_currentMonth = SelectedDate.Value;
}
private void CloseDropdown() => _isOpen = false;
private void PrevMonth() => _currentMonth = _currentMonth.AddMonths(-1);
private void NextMonth() => _currentMonth = _currentMonth.AddMonths(1);
private async Task SelectDate(DateTime date)
{
SelectedDate = date;
await SelectedDateChanged.InvokeAsync(SelectedDate);
_isOpen = false;
}
}

View File

@@ -0,0 +1,92 @@
.datepicker-wrapper {
display: inline-block;
font-size: smaller;
}
.card-header:first-child {
border-radius: 1.5rem 1.5rem 0 0;
}
.card-footer:last-child {
border-radius: 0 0 1.5rem 1.5rem;
}
.card-header > i {
font-size: large;
font-weight: 700;
padding: .2rem;
}
.datepicker-backdrop {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.4);
z-index: 1050;
}
.datepicker-dialog {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1055;
background-color: white;
border-radius: 8px;
animation: fadeIn 0.2s ease-out;
}
.calendar-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 5px;
}
.day-cell {
height: 38px;
width: 38px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 50%;
margin: 0 auto;
font-size: 0.9rem;
}
.day-cell:hover {
background-color: #f0f0f0;
}
.selected-day {
background-color: var(--primary-color) !important;
color: white;
}
.today-day {
border: 1px solid var(--primary-color);
font-weight: bold;
}
.card-button {
background-color: transparent;
padding: .5rem;
font-weight: 700;
color: var(--primary-color);
}
.disabled{
color: gray;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translate(-50%, -60%);
}
to {
opacity: 1;
transform: translate(-50%, -50%);
}
}

View File

@@ -0,0 +1,56 @@
@using System.Globalization
<input type="text"
inputmode="decimal"
class="@Class"
value="@_displayValue"
@onfocus="OnFocus"
@onblur="OnBlur"
@oninput="OnInput"
placeholder="@Placeholder"/>
@code {
[Parameter] public decimal Value { get; set; }
[Parameter] public EventCallback<decimal> ValueChanged { get; set; }
[Parameter] public string Class { get; set; } = "form-control text-end";
[Parameter] public string Placeholder { get; set; } = "";
[Parameter] public string CurrencyFormat { get; set; } = "C2";
[Parameter] public CultureInfo Culture { get; set; } = CultureInfo.CurrentCulture;
private string? _displayValue;
private bool _isFocused;
protected override void OnParametersSet()
{
if (!_isFocused)
_displayValue = Value.ToString(CurrencyFormat, Culture);
}
private void OnFocus()
{
_isFocused = true;
_displayValue = Value == 0 ? "" : Value.ToString("0.##", Culture);
}
private async Task OnBlur()
{
_isFocused = false;
var inputClean = _displayValue;
if (decimal.TryParse(inputClean, NumberStyles.Any, Culture, out var result))
Value = result;
await ValueChanged.InvokeAsync(Value);
_displayValue = Value.ToString(CurrencyFormat, Culture);
}
private void OnInput(ChangeEventArgs e)
{
_displayValue = e.Value?.ToString();
}
}

View File

@@ -9,7 +9,7 @@
</div>
<div class="text">
<span> @((MarkupString)ConfirmText)</span>
<span>@((MarkupString)ConfirmText)</span>
</div>
<div class="button-container">

View File

@@ -63,14 +63,6 @@
}
}
public async Task HideModalAsync()
{
if (Modal != null)
{
await Modal.HideAsync();
}
}
private async Task OnRetryClick()
{
await OnRetry.InvokeAsync();

View File

@@ -1,5 +1,4 @@
@using ConSegna.Shared.Core.Helpers
@inject ModalRef ModalRef
@inject ModalRef ModalRef
<Modal @ref="ModalRef.InputModal" BodyCssClass="@("input-body-modal")" HeaderCssClass="@("hide-header")" IsVerticallyCentered="true">
<BodyTemplate>

View File

@@ -0,0 +1,36 @@
@inject ModalRef ModalRef
<Modal @ref="ModalRef.PermissionModal" HeaderCssClass="@("hide-header")" UseStaticBackdrop="true" IsVerticallyCentered="true">
<BodyTemplate>
<div class="exception-header mb-2">
<i class="ri-emotion-unhappy-line"></i>
<span>@Title</span>
</div>
<div class="text">
<span>@((MarkupString)Message)</span>
</div>
<div class="button-container">
<div @onclick="() => SystemService.CloseApp()" class="card-button">
<span>Chiudi app</span>
</div>
<div @onclick="OnActionButtonClick" class="card-button">
<span>@ActionButtonText</span>
</div>
</div>
</BodyTemplate>
</Modal>
@code {
[Parameter] public string Message { get; set; } = "";
[Parameter] public EventCallback ActionButton { get; set; }
[Parameter] public string ActionButtonText { get; set; } = "Riprova";
[Parameter] public string Title { get; set; } = "Ops";
private async Task OnActionButtonClick()
{
await ActionButton.InvokeAsync();
}
}

View File

@@ -0,0 +1,48 @@
.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(--primary-color);
}
.exception-header {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.exception-header > i {
font-size: 3rem;
line-height: normal;
color: var(--red);
}
.exception-header > span {
font-size: x-large;
font-weight: 700;
}
code {
width: 100%;
height: auto;
color: var(--dark-gray);
}

View File

@@ -0,0 +1,91 @@
@inject ModalRef ModalRef
<Modal @ref="ModalRef.RicalcolaImporto" BodyCssClass="@("input-body-modal")" HeaderCssClass="@("hide-header")"
IsVerticallyCentered="true">
<BodyTemplate>
<div class="header mt-2 mb-1">
<span>Ricalcola importo</span>
</div>
<div class="card-body">
@foreach (var dettaglio in DettaglioRighe.Where(dettaglio => dettaglio.ImportoRiga > 0))
{
<div class="row">
<span class="col descrizione">@dettaglio.Descrizione</span>
</div>
<div class="row">
<span class="col prezzo">
@($"Prezzo netto: {@dettaglio.PrezzoUnit:C}")
</span>
<div class="col-auto qta">
<i @onclick="() => RemoveQta(dettaglio)" class="ri-subtract-line ripple-container"></i>
<span class="pezzi">
@dettaglio.QtaDoc.ToString("0") <span class="untMis">@dettaglio.UntMis</span>
</span>
<i @onclick="() => AddQta(dettaglio)" class="ri-add-large-line ripple-container"></i>
</div>
</div>
<div class="line-separator my-1"></div>
}
</div>
<div class="card-body mb-1 mt-3">
<span class="tot-doc">@($"Totale netto: {TotDoc:C}")</span>
</div>
<div class="button-container">
<div @onclick="OnHideModalClick" class="card-button">
<span>Chiudi</span>
</div>
</div>
</BodyTemplate>
</Modal>
@code {
[Parameter] public List<DettaglioRigheDTO> DettaglioRighe { get; set; } = [];
private decimal TotDoc { get; set; } = new(0);
protected override void OnParametersSet()
{
TotDoc = 0;
foreach (var row in DettaglioRighe)
{
row.TempQta = row.QtaDoc;
TotDoc += row.TempQta * row.PrezzoUnit;
}
StateHasChanged();
}
private async Task OnHideModalClick()
{
await ModalRef.RicalcolaImporto.HideAsync();
}
private void RemoveQta(DettaglioRigheDTO row)
{
if (row.TempQta == 0) return;
row.TempQta--;
TotDoc -= row.PrezzoUnit;
StateHasChanged();
}
private void AddQta(DettaglioRigheDTO row)
{
if (row.TempQta == 0) return;
row.TempQta++;
TotDoc += row.PrezzoUnit;
StateHasChanged();
}
}

View File

@@ -0,0 +1,80 @@
.header {
font-weight: 800;
font-size: large;
}
.card-body {
margin: .5rem 0;
max-height: 50vh;
overflow: auto;
}
.bodyCard {
line-height: normal;
padding: .8rem 0;
}
.titleCard {
font-size: 1rem;
font-weight: 700;
display: flex;
justify-content: space-between;
}
.titleCard .docInfo { width: 50%; }
.titleCard > div {
display: flex;
gap: 2vw;
align-items: flex-end;
}
.titleCard > span {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.button-container {
display: flex;
gap: 1rem;
flex-direction: row;
align-items: center;
width: 100%;
justify-content: end;
}
.card-button {
text-align: center;
background-color: transparent;
padding: .3rem 1rem;
font-weight: 700;
color: var(--primary-color);
}
.card-body > .row { line-height: normal; }
.card-body > .row > * {
font-weight: 500;
display: flex;
align-items: center;
}
.qta > .pezzi {
font-weight: 700;
font-size: 1.1rem;
text-align: end;
}
.untMis { font-size: 1rem; }
.qta > i{
padding: 1rem;
font-weight: 700;
border-radius: 50%;
}
.tot-doc{
font-weight: 700;
font-size: larger;
}

View File

@@ -5,7 +5,7 @@
.card-body {
background-color: var(--lighter-color);
border-radius: 15px;
border-radius: 1.5em;
padding: .5rem 1.1rem;
font-weight: 600;
height: 50vh;

View File

@@ -0,0 +1,46 @@
@inject ModalRef ModalRef
<Modal @ref="ModalRef.SelectReceiptModal" HeaderCssClass="@("hide-header")" IsVerticallyCentered="true">
<BodyTemplate>
<div class="header mb-2">
<span>Seleziona il file</span>
</div>
<div class="card-body">
@foreach (var file in FileList)
{
<div @onclick="() => OnConfirmClick(file.Name)" class="bodyCard">
<div class="titleCard">
<span>@file.Name</span>
<i class="ri-arrow-right-s-line"></i>
</div>
</div>
<div class="line-separator my-1"></div>
}
</div>
<div class="button-container">
<div @onclick="HideModalAsync" class="card-button">
<span>Chiudi</span>
</div>
</div>
</BodyTemplate>
</Modal>
@code {
[Parameter] public EventCallback<string> SelectClick { get; set; }
[Parameter] public List<FileInfo> FileList { get; set; } = [];
private async Task OnConfirmClick(string file)
{
await ModalRef.SelectReceiptModal.HideAsync();
await SelectClick.InvokeAsync(file);
}
private async Task HideModalAsync()
{
await ModalRef.SelectReceiptModal.HideAsync();
}
}

View File

@@ -0,0 +1,57 @@
.header {
font-weight: 800;
font-size: large;
}
.card-body {
background-color: var(--lighter-color);
border-radius: 1.5em;
padding: .5rem 1.1rem;
font-weight: 600;
height: 50vh;
overflow: auto;
}
.bodyCard {
line-height: normal;
padding: .8rem 0;
}
.titleCard {
font-size: 1rem;
font-weight: 700;
display: flex;
justify-content: space-between;
}
.titleCard .docInfo { width: 50%; }
.titleCard > div {
display: flex;
gap: 2vw;
align-items: flex-end;
}
.titleCard > span {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.button-container {
display: flex;
gap: 1rem;
flex-direction: row;
align-items: center;
width: 100%;
justify-content: end;
margin: 1.5rem 0 0 0;
}
.card-button {
text-align: center;
background-color: transparent;
padding: .3rem 1rem;
font-weight: 700;
color: var(--primary-color);
}

View File

@@ -1,7 +1,7 @@
@using ConSegna.Shared.Components.Layout.Spinner
@inject ModalRef ModalRef
<Modal UseStaticBackdrop="true" @ref="ModalRef.SpinnerModal" HeaderCssClass="@("hide-header")" IsVerticallyCentered="true">
<Modal DialogCssClass="@(ModalRef.SpinnerModalSign ? "" : "remove-background")" UseStaticBackdrop="true" @ref="ModalRef.SpinnerModal" HeaderCssClass="@("hide-header")" IsVerticallyCentered="true">
<BodyTemplate>
@if (ModalRef.SpinnerModalSign)
{

View File

@@ -0,0 +1,4 @@
.remove-background > .modal-content{
background: unset !important;
border: unset !important;
}

View File

@@ -12,7 +12,7 @@
</div>
</div>
<div class="px-3 py-2 rounded-4 border card-bg box-area">
<div class="px-3 py-2 rounded-4 border card-bg box-area ripple-container">
<div>
<span>@Consegne[0].Indirizzo - @Consegne[0].Citta</span>
</div>

View File

@@ -74,7 +74,7 @@
.line-separator {
display: block;
width: 100%;
border: 1px solid hsl(from var(--disable-color) h s 90%);
border: .05rem solid hsl(from var(--disable-color) h s 90%);
}
.sospeso {

View File

@@ -32,6 +32,16 @@ public class DettaglioRigheDTO
[JsonPropertyName("untMis")]
public string UntMis { get; set; }
[JsonPropertyName("importoRiga")]
public decimal ImportoRiga { get; set; }
[JsonIgnore] public decimal PrezzoUnit =>
QtaDoc == 0 ? 0 : Math.Round(ImportoRiga / QtaDoc, 2, MidpointRounding.AwayFromZero);
[JsonIgnore] public bool CanBeUpdated => ImportoRiga > 0;
[JsonIgnore] public DateTime LastUpdate { get; set; }
[JsonIgnore] public decimal TempQta { get; set; }
}

View File

@@ -7,6 +7,9 @@ public class ModalRef
public Modal SpinnerModal { get; set; } = default!;
public Modal ViewLogModal { get; set; } = default!;
public Modal SelectPrinterModal { get; set; } = default!;
public Modal SelectReceiptModal { get; set; } = default!;
public Modal PermissionModal { get; set; } = default!;
public Modal RicalcolaImporto { get; set; } = default!;
public bool SpinnerModalSign { get; set; }
}

View File

@@ -9,18 +9,28 @@ public interface IDataStorage
Task<List<SospesiClienteDTO>> RetrieveSospesi(string codVdes);
Task<List<SospesiClienteDTO>?> RetrieveAllSospesi();
Task<List<PaymentDataDTO>?> RetrieveAllPaymentData();
List<FileInfo> RetrieveAllReceipts();
string? GetFilePath(string fileName, bool signed = false);
string GetDirectoryPath(bool signed = false);
string? GetCertificatePath(string fileName);
FileStream GetFile(string filePath);
FileStream GetFileOutput(string fileName, bool signed = false);
Task OpenReceiptFile(string fileName);
Task OpenFile(string fileName, bool signed = false);
Task WriteReceiptTextAsync(string fileName, string content);
List<FileInfo>? RetrieveAllFile(bool signed, bool certificate);
FileInfo? RetrieveLogFile();
Task OpenLogFile(string? filename);
Task<string> ReadLogFile(string? filename);
Task<string> ReadReceiptFile(string? fileName);
void ClearOriginalDocumentsDirectory();
void ClearSignedDocuments();
Task ResetDatabase();
Task SaveDatiCliente(List<DatiClientiDTO> clienti);
}

View File

@@ -0,0 +1,10 @@
namespace ConSegna.Shared.Core.Interfaces;
public interface IGenericSystemService
{
string GetCurrentAppVersion();
void OpenSettings();
void CloseApp();
DateTime DataApp { get; set; }
}

View File

@@ -2,5 +2,6 @@
public interface IGeolocationService
{
Task<bool> RequestAccess();
Task<string> GetCoordinateOfCurrentLocation();
}

View File

@@ -0,0 +1,8 @@
using ConSegna.Shared.Core.Dto;
namespace ConSegna.Shared.Core.Interfaces;
public interface ILogReceiptsService
{
Task AddReceipt(List<SospesiClienteDTO> documentiPagati, decimal incassato, decimal daIncassare, string paymentMethod);
}

View File

@@ -4,7 +4,7 @@ namespace ConSegna.Shared.Core.Interfaces;
public interface ISyncStorage
{
public Task<bool> GetAndSaveDatiClienti();
public Task<bool> SaveDatiClienti(List<DatiClientiDTO> dataToSave);
public Task<bool> GetAndSaveFile(JasperDTO jasperDtoList, string fileName);
public Task<string?> GetAndSaveCertificate();
void OverwriteFile(string filePath, Stream newFile, bool signed = false, string? filename = null);

View File

@@ -0,0 +1,8 @@
namespace ConSegna.Shared.Core.Interfaces;
public interface IUtilityFile
{
string ComposeFileName(string codDtip, DateTime dataDoc, string serDoc, int numDoc, string codAnag);
DateTime? DetectDataDocFromFileName(string fileName);
}

View File

@@ -0,0 +1,6 @@
namespace ConSegna.Shared.Core.Interfaces.Sync;
public interface ISyncCleanupService
{
Task Cleanup();
}

View File

@@ -0,0 +1,7 @@
namespace ConSegna.Shared.Core.Interfaces.Sync;
public interface ISyncDayService
{
Task<bool> ShouldResetDatabase();
Task ResetDatabaseAndArchive();
}

View File

@@ -0,0 +1,10 @@
using ConSegna.Shared.Core.Dto;
namespace ConSegna.Shared.Core.Interfaces.Sync;
public interface ISyncDownloadService
{
Task DownloadCertificate();
Task<List<DatiClientiDTO>> DownloadConsegne();
Task DownloadFile(List<DatiClientiDTO> datiClienti);
}

View File

@@ -0,0 +1,3 @@
namespace ConSegna.Shared.Core.Interfaces.Sync;
public interface ISyncManager { Task RunFullSync(); }

View File

@@ -0,0 +1,5 @@
using ConSegna.Shared.Core.Dto;
namespace ConSegna.Shared.Core.Interfaces.Sync;
public interface ISyncMergeService { Task Merge(List<DatiClientiDTO> data); }

View File

@@ -0,0 +1,7 @@
namespace ConSegna.Shared.Core.Interfaces.Sync;
public interface ISyncUploadService
{
Task UploadDocument();
Task UploadData();
}

View File

@@ -8,20 +8,18 @@ using IntegryApiClient.Core.Domain.RestClient.Contacts;
namespace ConSegna.Shared.Core.Services;
public class IntegryApiService(IIntegryApiRestClient integryApiRestClient, IUserSession userSession)
: IIntegryApiService
public class IntegryApiService(
IIntegryApiRestClient integryApiRestClient,
IUserSession userSession,
IGenericSystemService systemService
) : IIntegryApiService
{
public Task<List<DatiClientiDTO>> GetDatiConsegne()
{
var requestDataDto = new RequestDataDTO
{
Username = userSession.User.Username,
#if DEBUG
DataDoc = new DateTime(2025, 06, 04)
//DataDoc = DateTime.Today
#else
DataDoc = DateTime.Today
#endif
DataDoc = systemService.DataApp
};
return integryApiRestClient.AuthorizedPost<List<DatiClientiDTO>>("consegna/getDatiConsegne", requestDataDto)!;

View File

@@ -0,0 +1,83 @@
using System.Text;
using ConSegna.Shared.Core.Dto;
using ConSegna.Shared.Core.Interfaces;
namespace ConSegna.Shared.Core.Services;
public class LogReceiptsService(
IDataStorage dataStorage)
: ILogReceiptsService
{
private const string BaseFileName = "_receipts.txt";
public Task AddReceipt(List<SospesiClienteDTO> documentiPagati, decimal incassato, decimal daIncassare, string paymentMethod)
{
return Task.Run(async () =>
{
var fileName = $"{DateTime.Today:yyyyMMdd}{BaseFileName}";
await dataStorage.WriteReceiptTextAsync(
fileName, ComposeReceiptText(documentiPagati, incassato, daIncassare, paymentMethod)
);
});
}
private static string ComposeReceiptText(
List<SospesiClienteDTO> documentiPagati,
decimal incassato,
decimal daIncassare,
string paymentMethod)
{
var sb = new StringBuilder();
const string separator = "------------------------------------";
var lineWidth = separator.Length;
// HEADER
var header = $"----Incasso-del-{DateTime.Now:g}----";
sb.AppendLine(header.PadRight(lineWidth));
// TITOLO COLONNE
const string col1 = "Documento";
const string col2 = "Tot. Pagato";
sb.AppendLine(ComposeTwoColumns(col1, col2, lineWidth));
// DOCUMENTI PAGATI
foreach (var doc in documentiPagati)
{
var docLine = $"{doc.CodDtip} n.{doc.NumDoc}";
var importo = $"{doc.ImportoPagato:C}";
sb.AppendLine(ComposeTwoColumns(docLine, importo, lineWidth));
sb.AppendLine($"del {doc.DataDoc:d}".PadRight(lineWidth));
sb.AppendLine(separator);
}
// RIEPILOGO
sb.AppendLine(ComposeTwoColumns("Incassato", incassato.ToString("C"), lineWidth));
sb.AppendLine(ComposeTwoColumns("Da incassare", daIncassare.ToString("C"), lineWidth));
sb.AppendLine(separator);
var totIncasso = incassato > 0 ? incassato - daIncassare : 0;
var label = totIncasso < 0 ? "Differenza" : "Resto";
sb.AppendLine(ComposeTwoColumns(label, totIncasso.ToString("C"), lineWidth));
sb.AppendLine($"Modalità - {paymentMethod}".PadRight(lineWidth));
sb.AppendLine();
return sb.ToString();
}
private static string ComposeTwoColumns(string left, string right, int totalWidth)
{
const int spacing = 1;
var leftWidth = totalWidth - right.Length - spacing;
if (leftWidth < left.Length)
leftWidth = left.Length + spacing;
return left.PadRight(leftWidth) + right;
}
}

View File

@@ -0,0 +1,14 @@
using ConSegna.Shared.Core.Interfaces;
using ConSegna.Shared.Core.Interfaces.Sync;
namespace ConSegna.Shared.Core.Services.Sync;
public class SyncCleanupService(IDataStorage data) : ISyncCleanupService
{
public Task Cleanup()
{
data.ClearOriginalDocumentsDirectory();
data.ClearSignedDocuments();
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,20 @@
using ConSegna.Shared.Core.Interfaces;
using ConSegna.Shared.Core.Interfaces.Sync;
using IntegryApiClient.Core.Domain.Abstraction.Contracts.Storage;
namespace ConSegna.Shared.Core.Services.Sync;
public class SyncDayService(
IGenericSystemService sys,
IDataStorage dataStorage,
ILocalStorage localStorage
) : ISyncDayService
{
public Task<bool> ShouldResetDatabase()
{
var syncAntherDay = localStorage.Get<bool>("sync-another-day");
return Task.FromResult(sys.DataApp.Date != DateTime.Today || syncAntherDay);
}
public Task ResetDatabaseAndArchive() => dataStorage.ResetDatabase();
}

View File

@@ -0,0 +1,108 @@
using ConSegna.Shared.Core.Dto;
using ConSegna.Shared.Core.Interfaces;
using ConSegna.Shared.Core.Interfaces.Sync;
using IntegryApiClient.Core.Domain.Abstraction.Contracts.Storage;
namespace ConSegna.Shared.Core.Services.Sync;
public class SyncDownloadService(
IIntegryApiService api,
IDataStorage data,
ILocalStorage localStorage,
ISyncStorage syncStorage,
IGenericSystemService sys,
IUtilityFile utilityFile
) : ISyncDownloadService
{
public async Task DownloadCertificate()
{
var certificateName = await syncStorage.GetAndSaveCertificate();
localStorage.SetString("certificate-name", certificateName);
}
public async Task<List<DatiClientiDTO>> DownloadConsegne()
{
var clienti = await api.GetDatiConsegne();
foreach (var cliente in clienti)
{
cliente.Sync = true;
foreach (var bolla in cliente.DatiConsegne)
{
bolla.FileName = utilityFile.ComposeFileName(
bolla.CodDtip, bolla.DataDoc, bolla.SerDoc, bolla.NumDoc, bolla.CodAnag
);
var filePath = data.GetFilePath(bolla.FileName, true);
bolla.Firmato = filePath != null;
}
}
return clienti;
}
public async Task DownloadFile(List<DatiClientiDTO> datiClienti)
{
var jasperDtoList = CreateJasperDtoList(datiClienti);
foreach (var jasperDto in jasperDtoList)
{
await syncStorage.GetAndSaveFile(jasperDto.Item1, jasperDto.Item2);
}
}
private List<(JasperDTO, string)> CreateJasperDtoList(List<DatiClientiDTO> retrieveDatiClienti)
{
List<(JasperDTO, string)> returnData = [];
foreach (var bolla in retrieveDatiClienti.SelectMany(datiClienti => datiClienti.DatiConsegne))
{
var jasperDto = new JasperDTO
{
ReportName = bolla.ReportName,
TypeExport = "PDF",
Params =
[
new PairsDTO
{
Name = "cod_anag",
Value = bolla.CodAnag
},
new PairsDTO
{
Name = "ser_doc",
Value = bolla.SerDoc
},
new PairsDTO
{
Name = "num_doc",
Value = bolla.NumDoc
},
new PairsDTO
{
Name = "data_doc",
Value = $"{sys.DataApp:yyyy-MM-dd}"
},
new PairsDTO
{
Name = "cod_dtip",
Value = bolla.CodDtip
},
new PairsDTO
{
Name = "note",
Value = "N"
}
]
};
returnData.Add((
jasperDto,
utilityFile.ComposeFileName(bolla.CodDtip, bolla.DataDoc, bolla.SerDoc, bolla.NumDoc, bolla.CodAnag)
));
}
return returnData;
}
}

View File

@@ -0,0 +1,20 @@
using ConSegna.Shared.Core.Interfaces.Sync;
namespace ConSegna.Shared.Core.Services.Sync;
public class SyncManager(
ISyncDayService d,
ISyncUploadService u,
ISyncDownloadService dn,
ISyncMergeService m,
ISyncCleanupService c)
: ISyncManager
{
public async Task RunFullSync()
{
if(await d.ShouldResetDatabase())
await d.ResetDatabaseAndArchive();
await c.Cleanup();
}
}

View File

@@ -0,0 +1,13 @@
using ConSegna.Shared.Core.Dto;
using ConSegna.Shared.Core.Interfaces;
using ConSegna.Shared.Core.Interfaces.Sync;
namespace ConSegna.Shared.Core.Services.Sync;
public class SyncMergeService(IDataStorage dataStorage) : ISyncMergeService
{
public async Task Merge(List<DatiClientiDTO> data)
{
await dataStorage.SaveDatiCliente(data);
}
}

View File

@@ -0,0 +1,45 @@
using ConSegna.Shared.Core.Dto;
using ConSegna.Shared.Core.Helpers;
using ConSegna.Shared.Core.Interfaces;
using ConSegna.Shared.Core.Interfaces.Sync;
namespace ConSegna.Shared.Core.Services.Sync;
public class SyncUploadService(IIntegryApiService api, IDataStorage data)
: ISyncUploadService
{
public async Task UploadData()
{
var sospesi = await data.RetrieveAllSospesi();
if (sospesi != null)
await api.SyncSospesi(sospesi);
}
public async Task UploadDocument()
{
var datiClienti = await data.RetrieveDatiClienti();
var bolle = datiClienti
.Where(x => !x.Sync)
.SelectMany(x => x.DatiConsegne.FindAll(x => !x.Note.IsNullOrEmpty() || x.Firmato))
.ToList();
if (bolle.IsNullOrEmpty()) return;
foreach (var bolla in from bolla in bolle let filesAttached = (FilesAttachedDTO?)null select bolla)
{
if (bolla.Firmato)
{
var f = data.GetFilePath(bolla.FileName, true);
if (f != null)
{
await using var fs = data.GetFile(f);
await api.Upload(fs, bolla);
}
}
await api.SyncDocument(bolla);
}
}
}

View File

@@ -4,6 +4,8 @@
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using ConSegna.Shared.Components.SingleElements
@using ConSegna.Shared.Components.SingleElements.Modal
@using Microsoft.JSInterop
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@@ -14,9 +16,11 @@
@using IntegryApiClient.Core.Domain.Abstraction.Contracts.Storage
@using ConSegna.Shared.Core.Dto
@using ConSegna.Shared.Core.Helpers
@using ConSegna.Shared.Core.Interfaces
@using Microsoft.AspNetCore.Components
@using static InteractiveRenderSettings
@inject NavigationManager NavigationManager
@inject IUserSession UserSession
@inject ILocalStorage LocalStorage
@inject ILocalStorage LocalStorage
@inject IGenericSystemService SystemService

View File

@@ -4,7 +4,7 @@
font-weight: 400;
line-height: 1.8;
color: black;
background-color: var(--background-color)
background-color: var(--background-color);
}
a, .btn-link {
@@ -17,11 +17,13 @@ a, .btn-link {
background-color: var(--primary-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 var(--primary-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 var(--primary-color);
}
.content {
padding-top: calc(3rem + 1.1rem);
padding-bottom: 6rem;
padding-top: 5rem;
}
.modalSearch-searchBox {
@@ -58,9 +60,11 @@ a, .btn-link {
font-weight: 800;
}
.hide-header { display: none; }
.hide-header {
display: none;
}
.input-body-modal{
.input-body-modal {
padding-top: 0 !important;
}
@@ -68,11 +72,17 @@ h1:focus {
outline: none;
}
.valid.modified:not([type=checkbox]) { outline: 1px solid #26b050; }
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
.invalid { outline: 1px solid #e50000; }
.invalid {
outline: 1px solid #e50000;
}
.validation-message { color: #e50000; }
.validation-message {
color: #e50000;
}
#blazor-error-ui {
background: lightyellow;
@@ -99,9 +109,13 @@ h1:focus {
color: white;
}
.blazor-error-boundary::after { content: "An error has occurred." }
.blazor-error-boundary::after {
content: "An error has occurred."
}
.status-bar-safe-area { display: none; }
.status-bar-safe-area {
display: none;
}
.page-title {
/*text-align: center;*/
@@ -109,11 +123,17 @@ h1:focus {
color: var(--darker-color);
}
.carousel-control-next, .carousel-control-prev { border-radius: 15px !important; }
.carousel-control-next, .carousel-control-prev {
border-radius: 15px !important;
}
.carousel-item-next, .carousel-item-prev { border-radius: 15px !important; }
.carousel-item-next, .carousel-item-prev {
border-radius: 15px !important;
}
.carousel-item-start, .carousel-item-end { border-radius: 15px !important; }
.carousel-item-start, .carousel-item-end {
border-radius: 15px !important;
}
@supports (-webkit-touch-callout: none) {
.status-bar-safe-area {
@@ -127,7 +147,9 @@ h1:focus {
background-color: var(--primary-color);
}
.modal { padding-top: env(safe-area-inset-top); }
.modal {
padding-top: env(safe-area-inset-top);
}
.content {
padding-top: 3rem !important;
@@ -139,13 +161,15 @@ h1:focus {
height: 100vh;
}
.flex-column, .navbar-brand { padding-left: env(safe-area-inset-left); }
.flex-column, .navbar-brand {
padding-left: env(safe-area-inset-left);
}
}
.line-separator {
display: block;
width: 100%;
border: 1px solid hsl(from var(--disable-color) h s 90%);
border: .05rem solid hsl(from var(--disable-color) h s 90%);
}
/*Spinner*/
@@ -179,10 +203,14 @@ h1:focus {
animation-duration: 2s;
}
.loader:after { animation-duration: 4s; }
.loader:after {
animation-duration: 4s;
}
@keyframes l24 {
100% { transform: rotate(1turn) }
100% {
transform: rotate(1turn)
}
}
/*Checkbox*/
@@ -210,124 +238,167 @@ h1:focus {
transition: background 0.3s, border-color 0.3s, box-shadow 0.2s;
}
input[type='checkbox']:after, input[type='radio']:after {
content: '';
display: block;
left: 0;
top: 0;
position: absolute;
transition: transform var(--d-t, 0.3s) var(--d-t-e, ease), opacity var(--d-o, 0.2s);
}
input[type='checkbox']:after, input[type='radio']:after {
content: '';
display: block;
left: 0;
top: 0;
position: absolute;
transition: transform var(--d-t, 0.3s) var(--d-t-e, ease), opacity var(--d-o, 0.2s);
}
input[type='checkbox']:checked, input[type='radio']:checked {
--b: var(--active);
--bc: var(--active);
--d-o: 0.3s;
--d-t: 0.6s;
--d-t-e: cubic-bezier(0.2, 0.85, 0.32, 1.2);
}
input[type='checkbox']:checked, input[type='radio']:checked {
--b: var(--active);
--bc: var(--active);
--d-o: 0.3s;
--d-t: 0.6s;
--d-t-e: cubic-bezier(0.2, 0.85, 0.32, 1.2);
}
input[type='checkbox']:disabled, input[type='radio']:disabled {
--b: var(--disabled);
cursor: not-allowed;
opacity: 0.9;
}
input[type='checkbox']:disabled, input[type='radio']:disabled {
--b: var(--disabled);
cursor: not-allowed;
opacity: 0.9;
}
input[type='checkbox']:disabled:checked, input[type='radio']:disabled:checked {
--b: var(--disabled-inner);
--bc: var(--border);
}
input[type='checkbox']:disabled:checked, input[type='radio']:disabled:checked {
--b: var(--disabled-inner);
--bc: var(--border);
}
input[type='checkbox']:disabled + label, input[type='radio']:disabled + label {
cursor: not-allowed;
}
input[type='checkbox']:disabled + label, input[type='radio']:disabled + label {
cursor: not-allowed;
}
input[type='checkbox']:hover:not(:checked):not(:disabled), input[type='radio']:hover:not(:checked):not(:disabled) {
--bc: var(--border-hover);
}
input[type='checkbox']:hover:not(:checked):not(:disabled), input[type='radio']:hover:not(:checked):not(:disabled) {
--bc: var(--border-hover);
}
input[type='checkbox']:focus, input[type='radio']:focus {
box-shadow: 0 0 0 var(--focus);
}
input[type='checkbox']:focus, input[type='radio']:focus {
box-shadow: 0 0 0 var(--focus);
}
input[type='checkbox']:not(.switch), input[type='radio']:not(.switch) {
width: 21px;
}
input[type='checkbox']:not(.switch), input[type='radio']:not(.switch) {
width: 21px;
}
input[type='checkbox']:not(.switch):after, input[type='radio']:not(.switch):after {
opacity: var(--o, 0);
}
input[type='checkbox']:not(.switch):after, input[type='radio']:not(.switch):after {
opacity: var(--o, 0);
}
input[type='checkbox']:not(.switch):checked, input[type='radio']:not(.switch):checked {
--o: 1;
}
input[type='checkbox']:not(.switch):checked, input[type='radio']:not(.switch):checked {
--o: 1;
}
input[type='checkbox'] + label, input[type='radio'] + label {
font-size: 14px;
line-height: 21px;
display: inline-block;
vertical-align: top;
cursor: pointer;
margin-left: 4px;
}
input[type='checkbox'] + label, input[type='radio'] + label {
font-size: 14px;
line-height: 21px;
display: inline-block;
vertical-align: top;
cursor: pointer;
margin-left: 4px;
}
input[type='checkbox']:not(.switch) {
border-radius: 7px;
}
input[type='checkbox']:not(.switch) {
border-radius: 7px;
}
input[type='checkbox']:not(.switch):after {
width: 5px;
height: 9px;
border: 2px solid var(--active-inner);
border-top: 0;
border-left: 0;
left: 7px;
top: 4px;
transform: rotate(var(--r, 20deg));
}
input[type='checkbox']:not(.switch):after {
width: 5px;
height: 9px;
border: 2px solid var(--active-inner);
border-top: 0;
border-left: 0;
left: 7px;
top: 4px;
transform: rotate(var(--r, 20deg));
}
input[type='checkbox']:not(.switch):checked {
--r: 43deg;
}
input[type='checkbox']:not(.switch):checked {
--r: 43deg;
}
input[type='checkbox'].switch {
width: 38px;
border-radius: 11px;
}
input[type='checkbox'].switch {
width: 38px;
border-radius: 11px;
}
input[type='checkbox'].switch:after {
left: 2px;
top: 2px;
border-radius: 50%;
width: 15px;
height: 15px;
background: var(--ab, var(--border));
transform: translateX(var(--x, 0));
}
input[type='checkbox'].switch:after {
left: 2px;
top: 2px;
border-radius: 50%;
width: 15px;
height: 15px;
background: var(--ab, var(--border));
transform: translateX(var(--x, 0));
}
input[type='checkbox'].switch:checked {
--ab: var(--active-inner);
--x: 17px;
}
input[type='checkbox'].switch:checked {
--ab: var(--active-inner);
--x: 17px;
}
input[type='checkbox'].switch:disabled:not(:checked):after {
opacity: 0.6;
}
input[type='checkbox'].switch:disabled:not(:checked):after {
opacity: 0.6;
}
input[type='radio'] {
border-radius: 50%;
}
input[type='radio']:after {
width: 19px;
height: 19px;
border-radius: 50%;
background: var(--active-inner);
opacity: 0;
transform: scale(var(--s, 0.7));
}
input[type='radio']:after {
width: 19px;
height: 19px;
border-radius: 50%;
background: var(--active-inner);
opacity: 0;
transform: scale(var(--s, 0.7));
}
input[type='radio']:checked {
--s: 0.5;
}
input[type='radio']:checked {
--s: 0.5;
}
}
/*Custom*/
.modal-fullscreen > .modal-content {
border-radius: unset !important;
}
.modal-content {
border-radius: 1.5em !important;
border: unset !important;
}
.modal-backdrop {
--bs-backdrop-opacity: 0.2 !important;
}
/*Ripple*/
.ripple-container {
position: relative;
overflow: hidden;
cursor: pointer;
transform: translateZ(0);
}
.ripple {
position: absolute;
border-radius: 50%;
transform: scale(0);
animation: ripple-effect 0.6s linear;
background-color: rgba(0, 0, 0, 0.2);
pointer-events: none;
}
@keyframes ripple-effect {
to {
transform: scale(4);
opacity: 0;
}
}
.ripple-white .ripple {
background-color: rgba(255, 255, 255, 0.5);
}

View File

@@ -22,3 +22,41 @@ observer.observe(document.body, {
subtree: false
});
(function () {
// Ascoltiamo l'evento 'mousedown' su tutto il documento
document.addEventListener('mousedown', function (event) {
// Cerca se l'elemento cliccato (o un suo genitore) ha la classe .ripple-container
const target = event.target.closest('.ripple-container');
if (target) {
createRipple(event, target);
}
});
function createRipple(event, element) {
const circle = document.createElement("span");
const diameter = Math.max(element.clientWidth, element.clientHeight);
const radius = diameter / 2;
const rect = element.getBoundingClientRect();
circle.style.width = circle.style.height = `${diameter}px`;
circle.style.left = `${event.clientX - rect.left - radius}px`;
circle.style.top = `${event.clientY - rect.top - radius}px`;
circle.classList.add("ripple");
// Rimuovi l'elemento dal DOM alla fine dell'animazione per non intasare la memoria
const ripple = element.getElementsByClassName("ripple")[0];
if (ripple) {
ripple.remove();
}
element.appendChild(circle);
// Pulizia automatica dopo 600ms (durata animazione CSS)
setTimeout(() => {
circle.remove();
}, 600);
}
})();

View File

@@ -5,6 +5,16 @@ namespace ConSegna.Web.Services;
public class DataStorage(IIntegryApiService integryApiService) : IDataStorage
{
public Task ResetDatabase()
{
throw new NotImplementedException();
}
public Task SaveDatiCliente(List<DatiClientiDTO> clienti)
{
throw new NotImplementedException();
}
public Task<List<DatiClientiDTO>> RetrieveDatiClienti()
{
throw new NotImplementedException();
@@ -15,7 +25,7 @@ public class DataStorage(IIntegryApiService integryApiService) : IDataStorage
throw new NotImplementedException();
}
public Task<List<SospesiClienteDTO>> RetrieveSospesi(string cliente)
public Task<List<SospesiClienteDTO>> RetrieveSospesi(string codVdes)
{
throw new NotImplementedException();
}
@@ -30,6 +40,11 @@ public class DataStorage(IIntegryApiService integryApiService) : IDataStorage
throw new NotImplementedException();
}
public List<FileInfo> RetrieveAllReceipts()
{
throw new NotImplementedException();
}
public string? GetFilePath(string fileName, bool signed = false)
{
throw new NotImplementedException();
@@ -55,22 +70,32 @@ public class DataStorage(IIntegryApiService integryApiService) : IDataStorage
throw new NotImplementedException();
}
public Task OpenReceiptFile(string fileName)
{
throw new NotImplementedException();
}
public Task OpenFile(string fileName, bool signed = false)
{
throw new NotImplementedException();
}
public Task WriteReceiptTextAsync(string fileName, string content)
{
throw new NotImplementedException();
}
public List<FileInfo>? RetrieveAllFile(bool signed, bool certificate)
{
throw new NotImplementedException();
}
public FileInfo RetrieveLogFile()
public FileInfo? RetrieveLogFile()
{
throw new NotImplementedException();
}
public Task OpenLogFile(string filename)
public Task OpenLogFile(string? filename)
{
throw new NotImplementedException();
}
@@ -80,6 +105,11 @@ public class DataStorage(IIntegryApiService integryApiService) : IDataStorage
throw new NotImplementedException();
}
public Task<string> ReadReceiptFile(string? fileName)
{
throw new NotImplementedException();
}
public void ClearOriginalDocumentsDirectory()
{
throw new NotImplementedException();

View File

@@ -4,6 +4,11 @@ namespace ConSegna.Web.Services;
public class GeolocationService : IGeolocationService
{
public Task<bool> RequestAccess()
{
throw new NotImplementedException();
}
public async Task<string> GetCoordinateOfCurrentLocation()
{
return "Posizione non rilevata";

View File

@@ -5,7 +5,7 @@ namespace ConSegna.Web.Services;
public class SyncStorage : ISyncStorage
{
public Task<bool> GetAndSaveDatiClienti()
public Task<bool> SaveDatiClienti(List<DatiClientiDTO> dataToSave)
{
throw new NotImplementedException();
}