Compare commits

...

3 Commits

Author SHA1 Message Date
3b66fedd2d pp 2025-10-14 11:57:57 +02:00
8505dbf1db pp 2025-10-14 11:46:20 +02:00
1dee3880c3 modifica root per salvataggio logo azienda 2025-09-30 11:50:16 +02:00
14 changed files with 267 additions and 548 deletions

View File

@ -245,11 +245,6 @@ namespace VirtualTask.Controllers
//model.chdtapp = model.chdata; //model.chdtapp = model.chdata;
model.chdtass = model.chdata; model.chdtass = model.chdata;
model.chtipo = "A";//X=creato da app, A creato da adhoc. DEVO METTERE A perche altrimenti l'app lo tratta come una chiamata da commessa model.chtipo = "A";//X=creato da app, A creato da adhoc. DEVO METTERE A perche altrimenti l'app lo tratta come una chiamata da commessa
if (model.chtchiam != null)
{
model.chstato = "C";
}
model.chmodrac = "EMAIL"; model.chmodrac = "EMAIL";
//int year=adesso.Year; //int year=adesso.Year;
//int ora = adesso.Hour; //int ora = adesso.Hour;

View File

@ -40,9 +40,6 @@ namespace VirtualTask.Controllers
_urlLoghi = _configuration["ApplicationInsights:rootUrlApi2"]; _urlLoghi = _configuration["ApplicationInsights:rootUrlApi2"];
} }
#region INDEX
public IActionResult Index() public IActionResult Index()
{ {
SessionHelper helper = new SessionHelper(this); SessionHelper helper = new SessionHelper(this);
@ -81,7 +78,6 @@ namespace VirtualTask.Controllers
return RedirectToAction("Error"); return RedirectToAction("Error");
} }
} }
#endregion
#region CREATE #region CREATE
@ -95,89 +91,99 @@ namespace VirtualTask.Controllers
} }
[HttpPost] [HttpPost]
public async Task<IActionResult> Create(DatiAzienda model) public IActionResult Create(DatiAzienda model)
{ {
DatiAziendaTable dat=new DatiAziendaTable();
SessionHelper helper = new SessionHelper(this); SessionHelper helper = new SessionHelper(this);
string token = helper.GetStringValue("tok"); admin = helper.GetStringValue("admin");
string tenant2 = helper.GetStringValue("tenant2"); // tenant = azienda token = helper.GetStringValue("tok");
string apiUrl = helper.GetStringValue("apiUrl"); tenant = helper.GetStringValue("tenant");
string admin = helper.GetStringValue("admin"); tenant2 = helper.GetStringValue("tenant2");
ViewBag.Admin = admin; ViewBag.Admin = admin;
ViewBag.AllTecnici = getTecnici(); if (model.logo != null)
// ❌ Validazione fallita → restituisco errori in JSON
if (!ModelState.IsValid)
{ {
var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList(); string pic = System.IO.Path.GetFileName(model.logo.FileName);
return BadRequest(string.Join("\n", errors)); //2025-05-05: gestione directory iommagine nel caso che l'azienda sia più corta di 5 caratteri
string dir = tenant2;
if (!string.IsNullOrEmpty(tenant2) && tenant2.Trim().Length < 5)
{
dir = tenant2.Trim();
}
string path = string.Format("{0}{1}\\{2}",_pathLoghi, dir, pic);
//string projectRootPath = _hostingEnvironment.ContentRootPath;
//// file is uploaded
using (Stream fileStream = new FileStream(path, FileMode.Create))
{
model.logo.CopyToAsync(fileStream);
} }
string logoUrl = null; //// save the image path path to the database or you can send image
//// directly to database
// 1⃣ Upload del logo tramite API UploadLogo //// in-case if you want to store byte[] ie. for DB
if (model.logo != null && model.logo.Length > 0) using (MemoryStream ms = new MemoryStream())
{ {
string uploadUrl = $"{apiUrl}datiazienda/upload_logo?token={token}"; model.logo.CopyTo(ms);
byte[] array = ms.GetBuffer();
using (var httpClient = new HttpClient()) dat.logo = array;
using (var form = new MultipartFormDataContent()) }
{ dat.azienda = tenant2;
var fileContent = new StreamContent(model.logo.OpenReadStream()); dat.testo_buono = model.testo_buono;
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue( dat.url_logo = string.Format("{0}{1}/{2}", _urlLoghi, dir, pic);
string.IsNullOrWhiteSpace(model.logo.ContentType) ? "application/octet-stream" : model.logo.ContentType dat.ragsoc = model.ragsoc;
); dat.tecnico = model.tecnico;
form.Add(fileContent, "file", model.logo.FileName);
HttpResponseMessage response = await httpClient.PostAsync(uploadUrl, form);
if (!response.IsSuccessStatusCode)
{
string err = await response.Content.ReadAsStringAsync();
return BadRequest("Errore upload logo: " + err);
} }
dynamic result = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync());
logoUrl = result.url;
}
}
// 2⃣ Creo l'oggetto da inviare all'API
var dat = new DatiAziendaTable
{
azienda = tenant2,
tecnico = model.tecnico,
ragsoc = model.ragsoc,
testo_buono = model.testo_buono,
url_logo = logoUrl,
logo = model.logo != null ? await ConvertToByteArrayAsync(model.logo) : null
};
// 3⃣ Invio dati all'API datiazienda/add apiUrl = helper.GetStringValue("apiUrl");
string apiAddUrl = apiUrl + "datiazienda/add?token=" + token; admin = helper.GetStringValue("admin");
using (var httpClient = new HttpClient()) ViewBag.Admin = admin;
{ urlBase = apiUrl + "datiazienda/add";
urlBase = urlBase + "?token=" + token;
Uri baseAddress = new Uri(urlBase);
client = new HttpClient();
client.BaseAddress = baseAddress;
string data = JsonConvert.SerializeObject(dat); string data = JsonConvert.SerializeObject(dat);
StringContent content = new StringContent(data, Encoding.UTF8, "application/json"); StringContent content = new StringContent(data, Encoding.UTF8, "application/json");
HttpResponseMessage saveResponse = await httpClient.PostAsync(apiAddUrl, content); HttpResponseMessage response = client.PostAsync(baseAddress, content).Result;
if (!saveResponse.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
string err = await saveResponse.Content.ReadAsStringAsync(); //prima di andare alla pagina devo chiamare il metodo per
return BadRequest("Errore salvataggio dati: " + err); //salvare il file in locale in modo che sia visibile sul web dall'app
} //C:\ZAPIPOLO\loghi
} //https://api.poloinformatico.it:9000/api/Polo/datiazienda/saveFile?azienda=AZI02&tecnico=aaaaa%20%20%20%20%20%20%20%20%20%20&pathSrv=C%3A%5CZAPIPOLO%5Cloghi'
apiUrl = helper.GetStringValue("apiUrl");
urlBase = apiUrl + "datiazienda/savefile";
urlBase = urlBase + "?azienda=" + model.azienda;
urlBase = urlBase + "&tecnico=" + model.tecnico;
//urlBase = urlBase + "&pathSrv=" + "C:\\ZAPIPOLO\\loghi";
urlBase = urlBase + "&pathSrv=" + _pathLoghi;
// ✅ Risposta JSON usata dalla view per aprire la finestra popup baseAddress = new Uri(urlBase);
return Json(new { success = true }); client = new HttpClient();
} client.BaseAddress = baseAddress;
HttpResponseMessage response2 = client.GetAsync(baseAddress).Result;
/// <summary> if (response2.IsSuccessStatusCode)
/// Pagina popup mostrata dopo il salvataggio
/// </summary>
public IActionResult Dialog()
{ {
return View(); return RedirectToAction("Index");
}
else
{
errMes = response.Content.ReadAsStringAsync().Result;
helper.SetStringValue("errMsg", errMes);
return RedirectToAction("Error");
}
}
else
{
errMes = response.Content.ReadAsStringAsync().Result;
helper.SetStringValue("errMsg", errMes);
return RedirectToAction("Error");
}
} }
#endregion CREATE #endregion CREATE
@ -222,75 +228,103 @@ namespace VirtualTask.Controllers
} }
[HttpPost] [HttpPost]
public async Task<IActionResult> Edit(DatiAziendaTable model) public IActionResult Edit(DatiAziendaTable model)
{ {
SessionHelper helper = new SessionHelper(this); SessionHelper helper = new SessionHelper(this);
string token = helper.GetStringValue("tok"); token = helper.GetStringValue("tok");
string tenant2 = helper.GetStringValue("tenant2"); tenant = helper.GetStringValue("tenant");
tenant2 = helper.GetStringValue("tenant2");
if (string.IsNullOrEmpty(token)) if (string.IsNullOrEmpty(token))
{
return RedirectToAction("Login2", "Login"); return RedirectToAction("Login2", "Login");
}
string apiUrl = helper.GetStringValue("apiUrl"); //model.azienda = tenant;
if(model.logo2!=null)
// 1⃣ Upload del logo tramite API UploadLogo
if (model.logo2 != null && model.logo2.Length > 0)
{ {
string uploadUrl = $"{apiUrl}datiazienda/upload_logo?token={token}"; string pic = System.IO.Path.GetFileName(model.logo2.FileName);
using (var httpClient = new HttpClient()) //2025-05-05: gestione directory iommagine nel caso che l'azienda sia più corta di 5 caratteri
using (var form = new MultipartFormDataContent()) string dir = tenant2;
if (!string.IsNullOrEmpty(tenant2) && tenant2.Trim().Length < 5)
{ {
var fileContent = new StreamContent(model.logo2.OpenReadStream()); dir = tenant2.Trim();
fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue( }
string.IsNullOrWhiteSpace(model.logo2.ContentType) ? "application/octet-stream" : model.logo2.ContentType string path = string.Format("{0}{1}\\{2}", _pathLoghi, dir, pic);
); //string projectRootPath = _hostingEnvironment.ContentRootPath;
form.Add(fileContent, "file", model.logo2.FileName);
HttpResponseMessage response = await httpClient.PostAsync(uploadUrl, form); //// file is uploaded
if (!response.IsSuccessStatusCode) using (Stream fileStream = new FileStream(path, FileMode.Create))
{ {
string err = await response.Content.ReadAsStringAsync(); model.logo2.CopyToAsync(fileStream);
ModelState.AddModelError("", "Errore upload logo: " + err);
return View(model);
} }
dynamic result = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); //// save the image path path to the database or you can send image
model.url_logo = result.url; //// directly to database
} //// in-case if you want to store byte[] ie. for DB
// Salvo i byte del file nel modello se serve per il DB
using (MemoryStream ms = new MemoryStream()) using (MemoryStream ms = new MemoryStream())
{ {
await model.logo2.CopyToAsync(ms); model.logo2.CopyTo(ms);
model.logo = ms.ToArray(); byte[] array = ms.GetBuffer();
model.logo = array;
} }
model.logo2 = null; model.logo2 = null;
model.url_logo = string.Format("{0}{1}/{2}", _urlLoghi, dir, pic);
} }
// 2⃣ Invio dati all'API datiazienda/mod
string modUrl = $"{apiUrl}datiazienda/mod?token={token}"; apiUrl = helper.GetStringValue("apiUrl");
using (var client = new HttpClient()) urlBase = apiUrl + "datiazienda/mod";
{ urlBase = urlBase + "?token=" + token;
Uri baseAddress = new Uri(urlBase);
client = new HttpClient();
client.BaseAddress = baseAddress;
string data = JsonConvert.SerializeObject(model); string data = JsonConvert.SerializeObject(model);
StringContent content = new StringContent(data, Encoding.UTF8, "application/json"); StringContent content = new StringContent(data, Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.PostAsync(modUrl, content); HttpResponseMessage response = client.PostAsync(baseAddress, content).Result;
if (!response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
string errMes = await response.Content.ReadAsStringAsync(); //prima di andare alla pagina devo chiamare il metodo per
//salvare il file in locale in modo che sia visibile sul web dall'app
//C:\ZAPIPOLO\loghi
//https://api.poloinformatico.it:9000/api/Polo/datiazienda/saveFile?azienda=AZI02&tecnico=aaaaa%20%20%20%20%20%20%20%20%20%20&pathSrv=C%3A%5CZAPIPOLO%5Cloghi'
apiUrl = helper.GetStringValue("apiUrl");
urlBase = apiUrl + "datiazienda/savefile";
if(!string.IsNullOrEmpty(model.azienda) && model.azienda.Length<5)
{
model.azienda= model.azienda.Trim();
}
urlBase = urlBase + "?azienda=" + model.azienda;
urlBase = urlBase + "&tecnico=" + model.tecnico.Trim();
urlBase = urlBase + "&pathSrv=" + _pathLoghi;
baseAddress = new Uri(urlBase);
client = new HttpClient();
client.BaseAddress = baseAddress;
data = JsonConvert.SerializeObject(model);
content = new StringContent(data, Encoding.UTF8, "application/json");
HttpResponseMessage response2 = client.PostAsync(baseAddress, content).Result;
if (response2.IsSuccessStatusCode)
{
return RedirectToAction("Index");
}
else
{
errMes = response2.Content.ReadAsStringAsync().Result;
helper.SetStringValue("errMsg", errMes); helper.SetStringValue("errMsg", errMes);
return RedirectToAction("Error"); return RedirectToAction("Error");
} }
} }
else
//return RedirectToAction("Index"); {
// ✅ Risposta OK per far apparire la dialog nella view errMes = response.Content.ReadAsStringAsync().Result;
return Ok(); helper.SetStringValue("errMsg", errMes);
return RedirectToAction("Error");
}
} }
#endregion #endregion
#region DETAIL #region DETAIL
@ -401,7 +435,7 @@ namespace VirtualTask.Controllers
} }
#endregion DELETE #endregion DELETE
#region ALTRI METODI
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error() public IActionResult Error()
{ {
@ -409,7 +443,6 @@ namespace VirtualTask.Controllers
string e = helper.GetStringValue("errMsg"); string e = helper.GetStringValue("errMsg");
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier, ErrMsg = e }); return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier, ErrMsg = e });
} }
private List<SelectListItem> getTecnici() private List<SelectListItem> getTecnici()
{ {
SessionHelper helper = new SessionHelper(this); SessionHelper helper = new SessionHelper(this);
@ -447,27 +480,5 @@ namespace VirtualTask.Controllers
} }
return selectItems; return selectItems;
} }
// Metodo helper per convertire IFormFile in byte[]
private async Task<byte[]> ConvertToByteArrayAsync(IFormFile file)
{
using (var ms = new MemoryStream())
{
await file.CopyToAsync(ms);
return ms.ToArray();
} }
}
#endregion ALTRI METODI
// Classe per deserializzare la risposta JSON dell'API
private class UploadResponse
{
public string? message { get; set; }
public string? url { get; set; }
public string? fileName { get; set; }
}
}
} }

View File

@ -68,7 +68,7 @@ namespace VirtualTask.Controllers
string app = ""; string app = "";
byte[] fileBytes = System.IO.File.ReadAllBytes(file); byte[] fileBytes = System.IO.File.ReadAllBytes(file);
var response = new FileContentResult(fileBytes, "application/octet-stream"); var response = new FileContentResult(fileBytes, "application/octet-stream");
response.FileDownloadName = "app_Virtual_Task_1_34.apk"; response.FileDownloadName = "appTest.apk";
return response; return response;
} }

View File

@ -47,7 +47,7 @@ namespace VirtualTask.Models
[Display(Name = "Tecnico")] [Display(Name = "Tecnico")]
public string? chtchiam { get; set; } public string? chtchiam { get; set; }
[Display(Name = "Stato")/*, Required(ErrorMessage = "Selezionare uno stato di assegnazione")*/] [Display(Name = "Stato")]
public string? chstato { get; set; } public string? chstato { get; set; }
[Display(Name = "Rifiutata")] [Display(Name = "Rifiutata")]

View File

@ -25,12 +25,12 @@
@Html.DropDownListFor(x => x.chtchiam, (IEnumerable<SelectListItem>)ViewBag.Tecnici, new { @class = "agy-form-field require" }) @Html.DropDownListFor(x => x.chtchiam, (IEnumerable<SelectListItem>)ViewBag.Tecnici, new { @class = "agy-form-field require" })
<span asp-validation-for="chtchiam" class="text-danger"></span> <span asp-validation-for="chtchiam" class="text-danger"></span>
</div> </div>
@* <div class="col-lg-6 col-md-6 col-sm-12 col-12">&nbsp;</div> <div class="col-lg-6 col-md-6 col-sm-12 col-12">&nbsp;</div>
<div class="form-group" style="width: 40%;"> <div class="form-group" style="width: 40%;">
<h5><label asp-for="chstato" class="agy-client-quote"></label></h5> <h5><label asp-for="chstato" class="agy-client-quote"></label></h5>
@Html.DropDownListFor(x => x.chstato, (IEnumerable<SelectListItem>)ViewBag.StatiChiamata, new { @class = "agy-form-field require" }) @Html.DropDownListFor(x => x.chstato, (IEnumerable<SelectListItem>)ViewBag.StatiChiamata, new { @class = "agy-form-field require" })
<span asp-validation-for="chstato" class="text-danger"></span> <span asp-validation-for="chstato" class="text-danger"></span>
</div> *@ </div>
<div class="col-lg-6 col-md-6 col-sm-12 col-12">&nbsp;</div> <div class="col-lg-6 col-md-6 col-sm-12 col-12">&nbsp;</div>
<div class="form-group" style="width: 40%;"> <div class="form-group" style="width: 40%;">
<h5><label asp-for="chdtapp" class="agy-client-quote"></label></h5> <h5><label asp-for="chdtapp" class="agy-client-quote"></label></h5>

View File

@ -4,133 +4,61 @@
ViewData["Title"] = "Nuova Intestazione Buoni Intervento"; ViewData["Title"] = "Nuova Intestazione Buoni Intervento";
Layout = "~/Views/Shared/_LayoutAreaRiservata.cshtml"; Layout = "~/Views/Shared/_LayoutAreaRiservata.cshtml";
} }
<div class="agy-project-wrapper agy-project-page-wrapper"> <div class="agy-project-wrapper agy-project-page-wrapper">
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="row"> <div class="row">
<div class="col-md-4"> <div class="col-md-4">
<form id="createForm" asp-action="Create" enctype="multipart/form-data"> <form asp-action="Create" enctype="multipart/form-data">
<div asp-validation-summary="ModelOnly" class="text-danger"></div> <div asp-validation-summary="ModelOnly" class="text-danger"></div>
@Html.HiddenFor(x => x.azienda) @Html.HiddenFor(x => x.azienda)
@Html.HiddenFor(x => x.url_logo) @Html.HiddenFor(x => x.url_logo)
<div class="form-group"> <div class="form-group">
<h5><label asp-for="tecnico" class="control-label"></label></h5> <h5><label asp-for="tecnico" class="control-label"></label></h5>
@Html.DropDownListFor(x => x.tecnico, (IEnumerable<SelectListItem>)ViewBag.AllTecnici, new { @class = "form-control" }) @Html.DropDownListFor(x =>x.tecnico,(IEnumerable<SelectListItem>)ViewBag.AllTecnici,new {@class = "form-control"})
<span asp-validation-for="tecnico" class="text-danger"></span> <span asp-validation-for="tecnico" class="text-danger"></span>
</div> </div>
<div class="col-lg-6 col-md-6 col-sm-12 col-12">&nbsp;</div> <div class="col-lg-6 col-md-6 col-sm-12 col-12">&nbsp;</div>
<div class="form-group"> <div class="form-group">
<h5><label asp-for="ragsoc" class="control-label"></label></h5> <h5><label asp-for="ragsoc" class="control-label"></label></h5>
<input asp-for="ragsoc" class="form-control" /> <input asp-for="ragsoc" class="form-control" />
<span asp-validation-for="ragsoc" class="text-danger"></span> <span asp-validation-for="ragsoc" class="text-danger"></span>
</div> </div>
<div class="col-lg-6 col-md-6 col-sm-12 col-12">&nbsp;</div> <div class="col-lg-6 col-md-6 col-sm-12 col-12">&nbsp;</div>
<div class="form-group"> <div class="form-group">
<h5><label asp-for="logo" class="control-label"></label></h5> <h5><label asp-for="logo" class="control-label"></label></h5>
<input type="file" asp-for="logo" /> <input type="file" asp-for="logo" />
<span asp-validation-for="logo" class="text-danger"></span> <span asp-validation-for="logo" class="text-danger"></span>
</div> </div>
<div class="col-lg-6 col-md-6 col-sm-12 col-12">&nbsp;</div> <div class="col-lg-6 col-md-6 col-sm-12 col-12">&nbsp;</div>
<div class="form-group"> <div class="form-group">
<h5><label asp-for="testo_buono" class="control-label"></label></h5> <h5><label asp-for="testo_buono" class="control-label"></label></h5>
@*<input asp-for="testo_buono" class="form-control" />*@
<textarea asp-for="testo_buono" class="form-control"></textarea> <textarea asp-for="testo_buono" class="form-control"></textarea>
<span asp-validation-for="testo_buono" class="text-danger"></span> <span asp-validation-for="testo_buono" class="text-danger"></span>
</div> </div>
<div class="col-lg-6 col-md-6 col-sm-12 col-12">&nbsp;</div> <div class="col-lg-6 col-md-6 col-sm-12 col-12">&nbsp;</div>
<div class="form-group"> <div class="form-group">
<input type="submit" value="Salva" class="agy-btn submitForm" /> <input type="submit" value="Salva" class="agy-btn submitForm" />
<a asp-action="Index" class="agy-btn submitForm">Torna alla lista</a> <a asp-action="Index" value="Torna alla lista" class="agy-btn submitForm">Torna alla lista</a>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div> <div>
</div> @* <a asp-action="Index">Torna alla lista</a> *@
</div>
<!-- ✅ Modale di conferma identica a quella della Edit -->
<div class="modal fade" id="saveModal" tabindex="-1" role="dialog" aria-labelledby="saveModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="saveModalLabel">Dati salvati</h5>
</div>
<div class="modal-body">
I dati salvati non saranno visibili nell'app finché non effettuerai il logout.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" id="modalOkBtn">OK</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script src="~/js/tinymce/tinymce.min.js" referrerpolicy="origin"></script> <script src="~/js/tinymce/tinymce.min.js" referrerpolicy="origin"></script>
@section Scripts { @section Scripts {
@{ @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
<script> <script>
// inizializza TinyMCE
tinymce.init({ tinymce.init({
selector: 'textarea#testo_buono', selector: 'textarea#testo_buono'
height: 300,
menubar: false,
plugins: 'lists link table code',
toolbar: 'undo redo | bold italic | bullist numlist | link table | code'
});
// intercetta il submit per gestire la chiamata AJAX + popup
document.getElementById('createForm').addEventListener('submit', function (e) {
e.preventDefault();
tinymce.triggerSave();
const form = e.target;
const formData = new FormData(form);
fetch(form.action, {
method: 'POST',
body: formData
})
.then(response => {
if (response.ok) {
// ✅ Mostra la modale di conferma
$('#saveModal').modal('show');
// reset del form
form.reset();
if (tinymce.get('testo_buono')) {
tinymce.get('testo_buono').setContent('');
}
} else {
response.text().then(text => {
alert("Errore nel salvataggio: " + text);
});
}
})
.catch(err => {
alert("Errore di rete: " + err);
});
});
// quando l'utente clicca OK sulla modale, vai alla Index
document.getElementById('modalOkBtn').addEventListener('click', function () {
window.location.href = '@Url.Action("Index")';
}); });
</script> </script>
} }

View File

@ -60,7 +60,9 @@
<input type="submit" value="Elimina" class="agy-btn submitForm" /> <input type="submit" value="Elimina" class="agy-btn submitForm" />
<input type="hidden" id="azienda" value=@Html.DisplayFor(model => model.azienda) name="azienda" /> <input type="hidden" id="azienda" value=@Html.DisplayFor(model => model.azienda) name="azienda" />
<input type="hidden" id="tecnico" value=@Html.DisplayFor(model => model.tecnico) name="tecnico" /> <input type="hidden" id="tecnico" value=@Html.DisplayFor(model => model.tecnico) name="tecnico" />
<a asp-action="Index" value="Torna alla lista" class="agy-btn submitForm">Torna alla lista</a> <div>
<a asp-action="Index">Torna alla lista</a>
</div>
</form> </form>
</div> </div>
</div> </div>

View File

@ -10,113 +10,61 @@
<div class="row"> <div class="row">
<div class="row"> <div class="row">
<div class="col-md-4"> <div class="col-md-4">
<form id="editForm" asp-action="Edit" enctype="multipart/form-data"> <form asp-action="Edit" enctype="multipart/form-data">
<div asp-validation-summary="ModelOnly" class="text-danger"></div> <div asp-validation-summary="ModelOnly" class="text-danger"></div>
@Html.HiddenFor(x => x.azienda) @Html.HiddenFor(x => x.azienda)
@Html.HiddenFor(x => x.url_logo) @Html.HiddenFor(x => x.url_logo)
@Html.HiddenFor(x => x.logo) @Html.HiddenFor(x => x.logo)
<div class="form-group"> <div class="form-group">
<h5><label asp-for="tecnico"></label></h5> <h5><label asp-for="tecnico" class="agy-client-quote"></label></h5>
@Html.DropDownListFor(x => x.tecnico, (IEnumerable<SelectListItem>)ViewBag.AllTecnici, new { @class = "form-control" }) @Html.DropDownListFor(x => x.tecnico,(IEnumerable<SelectListItem>)ViewBag.AllTecnici, new {@class = "form-control"})
<span asp-validation-for="tecnico" class="text-danger"></span> <span asp-validation-for="tecnico" class="text-danger"></span>
</div> </div>
<div class="col-lg-6 col-md-6 col-sm-12 col-12">&nbsp;</div>
<div class="form-group"> <div class="form-group">
<h5><label asp-for="ragsoc"></label></h5> <h5><label asp-for="ragsoc" class="agy-client-quote"></label></h5>
<input asp-for="ragsoc" class="form-control" /> <input asp-for="ragsoc" class="form-control" />
<span asp-validation-for="ragsoc" class="text-danger"></span> <span asp-validation-for="ragsoc" class="text-danger"></span>
</div> </div>
<div class="col-lg-6 col-md-6 col-sm-12 col-12">&nbsp;</div>
<div class="form-group"> <div class="form-group">
<h5><label asp-for="logo"></label></h5> <h5><label asp-for="logo" class="agy-client-quote"></label></h5>
@{ @{
if (Model.logo != null && Model.logo.Length > 0) byte[] appo = Model.logo;
{ var base64 = Convert.ToBase64String(appo);
var base64 = Convert.ToBase64String(Model.logo); var imgSrc = String.Format("data:image/gif;base64,{0}", base64);
var imgSrc = $"data:image/png;base64,{base64}";
<img src="@imgSrc" height="80" class="mb-2" />
}
} }
<img src="@imgSrc" height="80" />
<input type="file" asp-for="logo2" /> <input type="file" asp-for="logo2" />
<span asp-validation-for="logo" class="text-danger"></span> <span asp-validation-for="logo" class="text-danger"></span>
</div> </div>
<div class="col-lg-6 col-md-6 col-sm-12 col-12">&nbsp;</div>
<div class="form-group"> <div class="form-group">
<h5><label asp-for="testo_buono"></label></h5> <h5><label asp-for="testo_buono" class="agy-client-quote"></label></h5>
<textarea asp-for="testo_buono" id="testo_buono" class="form-control"></textarea> <textarea asp-for="testo_buono" class="form-control"></textarea>
<span asp-validation-for="testo_buono" class="text-danger"></span> <span asp-validation-for="testo_buono" class="text-danger"></span>
</div> </div>
<div class="form-group">
<div class="form-group mt-3">
<input type="submit" value="Salva" class="agy-btn submitForm" /> <input type="submit" value="Salva" class="agy-btn submitForm" />
<a asp-action="Index" class="agy-btn submitForm">Torna alla lista</a> <a asp-action="Index" value="Torna alla lista" class="agy-btn submitForm">Torna alla lista</a>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
<div>
@* <a asp-action="Index">Back to List</a> *@
</div>
</div> </div>
</div> </div>
<!-- TinyMCE -->
<script src="~/js/tinymce/tinymce.min.js" referrerpolicy="origin"></script> <script src="~/js/tinymce/tinymce.min.js" referrerpolicy="origin"></script>
<!-- ✅ Dialog identica a quella della Create -->
<div class="modal fade" id="saveModal" tabindex="-1" role="dialog" aria-labelledby="saveModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="saveModalLabel">Dati salvati</h5>
</div>
<div class="modal-body">
I dati salvati non saranno visibili nell'app finché non effettuerai il logout.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" id="modalOkBtn">OK</button>
</div>
</div>
</div>
</div>
@section Scripts { @section Scripts {
@{ @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
<script> <script>
tinymce.init({ tinymce.init({
selector: 'textarea#testo_buono', selector: 'textarea#testo_buono'
height: 300,
menubar: false,
plugins: 'lists link table code',
toolbar: 'undo redo | bold italic | bullist numlist | link table | code'
});
document.getElementById('editForm').addEventListener('submit', function (e) {
e.preventDefault();
tinymce.triggerSave();
const form = e.target;
const formData = new FormData(form);
fetch(form.action, {
method: 'POST',
body: formData
})
.then(response => {
if (response.ok) {
// ✅ Mostra la stessa dialog della Create
$('#saveModal').modal('show');
} else {
alert("Errore durante il salvataggio.");
}
})
.catch(err => console.error(err));
});
document.getElementById('modalOkBtn').addEventListener('click', function () {
window.location.href = '@Url.Action("Index")';
}); });
</script> </script>
} }

View File

@ -4,6 +4,6 @@
} }
<h1>@ViewData["Title"]</h1> <h1>@ViewData["Title"]</h1>
<p> <a href="Download?file=wwwroot/APP/app-env_vt_1_34.apk" target="_blank">Download Android App (ver. 1.34)</a> </p> <p> <a href="Download?file=wwwroot/APP/app-env_vt_1_25.apk" target="_blank">Download Android App (ver. 1.25)</a> </p>

View File

@ -4,68 +4,6 @@
ViewData["Title"] = "Virtual Task Pro"; ViewData["Title"] = "Virtual Task Pro";
} }
<!-- CSS lightbox -->
<style>
.lightbox {
display: none;
position: fixed;
z-index: 9999;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.8);
justify-content: center;
align-items: center;
}
/* .lightbox img {
max-width: 90%;
max-height: 80%;
border-radius: 10px;
box-shadow: 0 0 20px rgba(255,255,255,0.3);
animation: fadeIn 0.3s ease;
} */
.lightbox img {
width: 400px;
height: auto; /* mantiene proporzioni */
border-radius: 10px;
box-shadow: 0 0 20px rgba(255,255,255,0.3);
animation: fadeIn 0.3s ease;
}
.lightbox .close {
position: absolute;
top: 20px;
right: 40px;
font-size: 40px;
color: #fff;
cursor: pointer;
}
@@keyframes fadeIn {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
.lightbox-img {
cursor: pointer;
transition: transform 0.2s;
}
.lightbox-img:hover {
transform: scale(1.05);
}
</style>
<div class="agy-project-wrapper agy-project-page-wrapper"> <div class="agy-project-wrapper agy-project-page-wrapper">
<div class="container" style="width:100%; text-align:justify"> <div class="container" style="width:100%; text-align:justify">
<div> <div>
@ -92,8 +30,8 @@
<div class="col-lg-6 col-md-6 col-sm-12 col-12"> <div class="col-lg-6 col-md-6 col-sm-12 col-12">
<div class="agy-about-img relative"> <div class="agy-about-img relative">
<div class=""> <div class="">
<img src="~/assets/images/app1.png" alt="Virtual Task App" style="width:30%;height:30%;" class="lightbox-img" /> <img src="~/assets/images/app1.png" alt="Virtual Task App" style="width:30%;height:30%;" />
<img src="~/assets/images/app2.png" alt="Virtual Task App" style="width:30%;height:30%;" class="lightbox-img" /> <img src="~/assets/images/app2.png" alt="Virtual Task App" style="width:30%;height:30%;" />
</div> </div>
</div> </div>
</div> </div>
@ -122,7 +60,7 @@
<table style="width:100%"> <table style="width:100%">
<tr> <tr>
<th style="width:20%"> <th style="width:20%">
<img src="~/assets/images/app3.png" alt="Virtual Task App" style="width:70%;height:70%;" class="lightbox-img" /> <img src="~/assets/images/app3.png" alt="Virtual Task App" style="width:70%;height:70%;" />
</th> </th>
<th style="width:70%"> <th style="width:70%">
<ol> <ol>
@ -186,8 +124,8 @@
<div class="col-lg-6 col-md-6 col-sm-12 col-12"> <div class="col-lg-6 col-md-6 col-sm-12 col-12">
<div class="agy-about-img relative"> <div class="agy-about-img relative">
<div class=""> <div class="">
<img src="~/assets/images/app5.png" alt="Virtual Task App" style="width:30%;height:30%;" class="lightbox-img" /> <img src="~/assets/images/app5.png" alt="Virtual Task App" style="width:30%;height:30%;" />
<img src="~/assets/images/app6.png" alt="Virtual Task App" style="width:30%;height:30%;" class="lightbox-img" /> <img src="~/assets/images/app6.png" alt="Virtual Task App" style="width:30%;height:30%;" />
</div> </div>
</div> </div>
</div> </div>
@ -226,37 +164,6 @@
</div> </div>
</div> </div>
<!-- Lightbox HTML -->
<div class="lightbox" id="lightbox">
<span class="close" id="lightbox-close">&times;</span>
<img id="lightbox-img" src="" alt="Anteprima immagine">
</div>
@section Scripts {
<script>
const lightbox = document.getElementById('lightbox');
const lightboxImg = document.getElementById('lightbox-img');
const closeBtn = document.getElementById('lightbox-close');
document.querySelectorAll('.lightbox-img').forEach(img => {
img.addEventListener('click', () => {
lightboxImg.src = img.src;
lightbox.style.display = 'flex';
});
});
closeBtn.addEventListener('click', () => {
lightbox.style.display = 'none';
});
lightbox.addEventListener('click', (e) => {
if (e.target === lightbox) {
lightbox.style.display = 'none';
}
});
</script>
}
@* <script> @* <script>
$('.zoom').click(function () { $('.zoom').click(function () {
var imageUrl = $(this).attr('src'); var imageUrl = $(this).attr('src');

View File

@ -1,63 +1,8 @@
@{  @{
Layout = "~/Views/Shared/_LayoutAreaRiservata.cshtml"; Layout = "~/Views/Shared/_LayoutAreaRiservata.cshtml";
ViewData["Title"] = "Task Manager"; ViewData["Title"] = "Task Manager";
} }
<!-- CSS lightbox -->
<style>
.lightbox {
display: none;
position: fixed;
z-index: 9999;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.8);
justify-content: center;
align-items: center;
}
/* .lightbox img {
max-width: 90%;
max-height: 80%;
border-radius: 10px;
box-shadow: 0 0 20px rgba(255,255,255,0.3);
animation: fadeIn 0.3s ease;
} */
.lightbox img {
width:1200px;
height: auto; /* mantiene proporzioni */
border-radius: 10px;
box-shadow: 0 0 20px rgba(255,255,255,0.3);
animation: fadeIn 0.3s ease;
}
.lightbox .close {
position: absolute;
top: 20px;
right: 40px;
font-size: 40px;
color: #fff;
cursor: pointer;
}
@@keyframes fadeIn {
from { opacity: 0; transform: scale(0.9); }
to { opacity: 1; transform: scale(1); }
}
.lightbox-img {
cursor: pointer;
transition: transform 0.2s;
}
.lightbox-img:hover {
transform: scale(1.05);
}
</style>
<div class="agy-project-wrapper agy-project-page-wrapper"> <div class="agy-project-wrapper agy-project-page-wrapper">
<div class="container" style="width:100%; text-align:justify"> <div class="container" style="width:100%; text-align:justify">
<div> <div>
@ -65,7 +10,7 @@
<br /> <br />
<div class="agy-about-img relative"> <div class="agy-about-img relative">
<div class=""> <div class="">
<img src="~/assets/images/pro1.png" alt="Virtual Task App" style="width:50%;height:50%;" class="lightbox-img" /> <img src="~/assets/images/pro1.png" alt="Virtual Task App" style="width:55%;height:55%;" />
</div> </div>
</div> </div>
</div> </div>
@ -80,15 +25,33 @@
<br /> <br />
<p> <p>
<ul> <ul>
<li>A. Gestione anagrafica IMPIANTO con dati tecnici ed eventuale composizione specifica componenti.</li> <li>
<li>B. Gestione Manutenzioni programmate previste per l'IMPIANTO.</li> A. Gestione anagrafica IMPIANTO con dati tecnici ed eventuale composizione specifica componenti.
<li>C. Gestione Contratti con specifica degli impianti, calcolo delle rate e relativa fatturazione.</li> </li>
<li>D. Gestione Commesse di lavoro con aggancio all'analitica standard.</li> <li>
<li>E. Completa gestione delle chiamate di intervento, dall'inserimento alla sua assegnazione a tecnico e consuntivazione.</li> B. Gestione Manutenzioni programmate previste per l'IMPIANTO.
<li>F. Gestione RAPPORTINI DI INTERVENTO come consuntivi tecnici siano essi legati a manutenzioni, commesse o chiamate.</li> </li>
<li>G. Fatturazione canoni dei contratti.</li> <li>
<li>H. Fatturazione rapportini di intervento.</li> C. Gestione Contratti con specifica degli impianti, calcolo delle rate e relativa fatturazione.
<li>I. Fatturazione commesse ad avanzamento lavoro.</li> </li>
<li>
D. Gestione Commesse di lavoro con aggancio all'analitica standard.
</li>
<li>
E. Completa gestione delle chiamate di intervento, dall'inserimento alla sua assegnazione a tecnico e consuntivazione.
</li>
<li>
F. Gestione RAPPORTINI DI INTERVENTO come consuntivi tecnici siano essi legati a manutenzioni, commesse o chiamate.
</li>
<li>
G. Fatturazione canoni dei contratti.
</li>
<li>
H. Fatturazione rapportini di intervento.
</li>
<li>
I. Fatturazione commesse ad avanzamento lavoro.
</li>
</ul> </ul>
</p> </p>
</div> </div>
@ -99,32 +62,40 @@
<br /> <br />
<div class="agy-about-img relative"> <div class="agy-about-img relative">
<div class=""> <div class="">
<img src="~/assets/images/pro2.png" alt="Virtual Task App" style="width:55%;height:55%;" class="lightbox-img" /> <img src="~/assets/images/pro2.png" alt="Virtual Task App" style="width:55%;height:55%;" />
</div> </div>
</div> </div>
</div> </div>
<br /> <br />
<div class="container" style="width:100%; text-align:justify"> <div class="container" style="width:100%; text-align:justify">
<p><h4>Cruscotto Operativo Chiamate</h4></p> <p>
<h4>
Cruscotto Operativo Chiamate
</h4>
</p>
<br /> <br />
<div class="agy-about-img relative"> <div class="agy-about-img relative">
<div class=""> <div class="">
<img src="~/assets/images/pro3.png" alt="Virtual Task App" style="width:55%;height:55%;" class="lightbox-img" /> <img src="~/assets/images/pro3.png" alt="Virtual Task App" style="width:55%;height:55%;" />
<img src="~/assets/images/pro4.png" alt="Virtual Task App" style="width:55%;height:55%;" class="lightbox-img" /> <img src="~/assets/images/pro4.png" alt="Virtual Task App" style="width:55%;height:55%;" />
</div> </div>
</div> </div>
</div> </div>
<br /> <br />
<div class="container" style="width:100%; text-align:justify"> <div class="container" style="width:100%; text-align:justify">
<p><h4>Gestione Rapportini</h4></p> <p>
<h4>
Gestione Rapportini
</h4>
</p>
<br /> <br />
<p>Dettaglio rapportini</p> <p>Dettaglio rapportini</p>
<br /> <br />
<div class="agy-about-img relative"> <div class="agy-about-img relative">
<div class=""> <div class="">
<img src="~/assets/images/pro5.png" alt="Virtual Task App" style="width:55%;height:55%;" class="lightbox-img" /> <img src="~/assets/images/pro5.png" alt="Virtual Task App" style="width:55%;height:55%;" />
<img src="~/assets/images/pro6.png" alt="Virtual Task App" style="width:55%;height:55%;" class="lightbox-img" /> <img src="~/assets/images/pro6.png" alt="Virtual Task App" style="width:55%;height:55%;" />
<img src="~/assets/images/pro7.png" alt="Virtual Task App" style="width:55%;height:55%;" class="lightbox-img" /> <img src="~/assets/images/pro7.png" alt="Virtual Task App" style="width:55%;height:55%;" />
</div> </div>
</div> </div>
</div> </div>
@ -138,33 +109,3 @@
</div> </div>
</div> </div>
<!-- Lightbox HTML -->
<div class="lightbox" id="lightbox">
<span class="close" id="lightbox-close">&times;</span>
<img id="lightbox-img" src="" alt="Anteprima immagine">
</div>
@section Scripts {
<script>
const lightbox = document.getElementById('lightbox');
const lightboxImg = document.getElementById('lightbox-img');
const closeBtn = document.getElementById('lightbox-close');
document.querySelectorAll('.lightbox-img').forEach(img => {
img.addEventListener('click', () => {
lightboxImg.src = img.src;
lightbox.style.display = 'flex';
});
});
closeBtn.addEventListener('click', () => {
lightbox.style.display = 'none';
});
lightbox.addEventListener('click', (e) => {
if (e.target === lightbox) {
lightbox.style.display = 'none';
}
});
</script>
}

View File

@ -1,30 +1,19 @@
@{ @{
ViewData["Title"] = "Visualizza immagine"; ViewData["Title"] = "Visualizza immagine";
Layout = "~/Views/Shared/_LayoutAreaRiservata.cshtml";
var fileName = ViewBag.FileName as string; var fileName = ViewBag.FileName as string;
} }
<h2>@ViewData["Title"]</h2> <h2>@ViewData["Title"]</h2>
@if (!string.IsNullOrEmpty(fileName))
<div class="agy-project-wrapper agy-project-page-wrapper"> {
<div class="container">
<div class="row">
<div class="col-md-4">
@if (!string.IsNullOrEmpty(fileName))
{
<div> <div>
<img src="@Url.Action("GetImage", "Rapp_New", new { filePath = fileName })" <img src="@Url.Action("GetImage", "Rapp_New", new { filePath = fileName })"
alt="Immagine: @fileName" alt="Immagine: @fileName"
style="max-width:600px; height:auto; border:1px solid #ccc;" /> style="max-width:600px; height:auto; border:1px solid #ccc;" />
</div> </div>
} }
else else
{ {
<p>Nessuna immagine specificata.</p> <p>Nessuna immagine specificata.</p>
} }
</div>
</div>
</div>
</div>

View File

@ -7,12 +7,11 @@
}, },
"ApplicationInsights": { "ApplicationInsights": {
//PRODUZIONE ////PRODUZIONE
"rootUrlApi": "https://api-vt.poloinformatico.it/api/Polo/", "rootUrlApi": "https://api-vt.poloinformatico.it/api/Polo/",
"rootUrlApi2": "https://api-vt.poloinformatico.it/VIRTU/", "rootUrlApi2": "https://api-vt.poloinformatico.it/VIRTU/",
//"rootWebLoghi": "C:\\ZAPIPOLO\\api_polo\\wwwroot\\VIRTU\\", //"rootWebLoghi": "C:\\ZAPIPOLO\\api_polo\\wwwroot\\VIRTU\\",
//"rootWebLoghi": "/zucchetti/api/api-vt.poloinformatico.it/app/wwwroot/VIRTU/", "rootWebLoghi": "/zucchetti/api/api-vt.poloinformatico.it/app/wwwroot/VIRTU/",
"rootWebLoghi": "./wwwroot/VIRTU",
"rootUrl": "https://virtualtask.it/", "rootUrl": "https://virtualtask.it/",
"rootPath": "/mnt/storagebox", "rootPath": "/mnt/storagebox",
@ -22,11 +21,10 @@
//"rootWebLoghi": "C:\\Users\\audif\\source\\repos\\VirtualTask\\wwwroot\\VIRTU\\", //"rootWebLoghi": "C:\\Users\\audif\\source\\repos\\VirtualTask\\wwwroot\\VIRTU\\",
//"rootUrl": "https://localhost:7140/", //"rootUrl": "https://localhost:7140/",
////MICHELE: PUNTAMENTO A MIO PC PER FARE I TEST //MICHELE: PUNTAMENTO A MIO PC PER FARE I TEST
//"rootUrlApi": "https://localhost:7068/api/Polo/", //"rootUrlApi": "https://localhost:7068/api/Polo/",
//"rootUrlApi2": "https://localhost:7068//VIRTU/", //"rootUrlApi2": "https://localhost:7068//VIRTU/",
////"rootWebLoghi": "C:\\Users\\audif\\source\\repos\\VirtualTask\\wwwroot\\VIRTU\\", //"rootWebLoghi": "C:\\Users\\audif\\source\\repos\\VirtualTask\\wwwroot\\VIRTU\\",
//"rootWebLoghi": "/zucchetti/api/api-vt.poloinformatico.it/app/wwwroot/VIRTU/",
//"rootUrl": "https://localhost:7068/", //"rootUrl": "https://localhost:7068/",
"mittenteMail": "info@virtualtask.it", "mittenteMail": "info@virtualtask.it",