Compare commits

..

3 Commits

Author SHA1 Message Date
416b283341 merge 2025-06-04 15:49:41 +08:00
82192fde0d Revert "fix test"
This reverts commit 457ee8f5d3.
2025-05-30 11:30:46 +08:00
457ee8f5d3 fix test 2025-05-30 11:15:11 +08:00
3 changed files with 128 additions and 169 deletions

View File

@ -11,7 +11,6 @@ using System.Collections.Generic;
using Microsoft.Data.SqlClient; using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using MySqlConnector; using MySqlConnector;
using Org.BouncyCastle.Asn1.Cms;
namespace PSTW_CentralSystem.Areas.MMS.Controllers namespace PSTW_CentralSystem.Areas.MMS.Controllers
{ {
@ -22,8 +21,8 @@ namespace PSTW_CentralSystem.Areas.MMS.Controllers
public required string StationID { get; set; } public required string StationID { get; set; }
public required string Longitude { get; set; } public required string Longitude { get; set; }
public required string Latitude { get; set; } public required string Latitude { get; set; }
public DateTime DateSample { get; set; } public required DateTime DateSample { get; set; }
public TimeSpan TimeSample { get; set; } public required TimeSpan TimeSample { get; set; }
public required string ClassifyID { get; set; } public required string ClassifyID { get; set; }
public string? OptionalName1 { get; set; } public string? OptionalName1 { get; set; }
public string? OptionalName2 { get; set; } public string? OptionalName2 { get; set; }
@ -38,21 +37,6 @@ namespace PSTW_CentralSystem.Areas.MMS.Controllers
public required string LevelName { get; set; } // From tbl_level public required string LevelName { get; set; } // From tbl_level
} }
public class TarBallGroupViewModel
{
public List<int> Id { get; set; } = new List<int>();
public required string StationID { get; set; }
public required string Date { get; set; }
public required int TimeSampleCount { get; set; }
public required string LatestTime {get; set; }
public List<TarBallTimeSample> TimeSamples { get; set; } = new List<TarBallTimeSample>();
}
public class TarBallTimeSample
{
public required int Id { get; set; }
public string Time { get; set; }
}
[Area("MMS")] [Area("MMS")]
public class MarineController : Controller public class MarineController : Controller
{ {
@ -71,37 +55,28 @@ namespace PSTW_CentralSystem.Areas.MMS.Controllers
return View(); return View();
} }
public class StationDateGroup public IActionResult TarBallForm()//Queries the database and returns a view with tarball data
{
public required string StationID { get; set; }
public DateTime DateSample { get; set; }
public List<TarballPdfDto> TimeSamples { get; set; } = new List<TarballPdfDto>();
}
public IActionResult TarBallForm()
{ {
try try
{ {
var marineTarballs = _context.MarineTarballs var marineTarballs = _context.MarineTarballs //ERRORRRRRRR======================================
.Select(t => new .Select(t => new
{ {
t.Id, t.Id,
station = t.StationID, Date = t.DateSample.ToString("yyyy/MM/dd"),
date = t.DateSample.ToString("yyyy/MM/dd"), Station = t.StationID
time = t.TimeSample.ToString(@"hh\:mm\:ss"),
fullDate = t.DateSample, // Keep DateTime for sorting
t.TimeSample
}) })
.AsEnumerable()
.OrderByDescending(t => t.fullDate)
.ThenByDescending(t => t.TimeSample)
.ToList(); .ToList();
Console.WriteLine($"Marine Tarballs Count: {marineTarballs.Count}");
return View(marineTarballs); return View(marineTarballs);
} }
catch (Exception ex) catch (Exception ex)
{ {
// Show the real error in the browser (for debugging only)
return Content($"Error: {ex.Message}<br/>{ex.StackTrace}", "text/html"); return Content($"Error: {ex.Message}<br/>{ex.StackTrace}", "text/html");
} }
} }
[HttpGet] // Explicitly mark as a GET endpoint [HttpGet] // Explicitly mark as a GET endpoint
@ -201,7 +176,8 @@ namespace PSTW_CentralSystem.Areas.MMS.Controllers
// Determine content type based on extension // Determine content type based on extension
string contentType = "image/jpeg"; // default string contentType = "image/jpeg"; // default
string extension = Path.GetExtension(sanitizedFileName)?.ToLower(); string extension = Path.GetExtension(sanitizedFileName).ToLower();
//string extension = Path.GetExtension(sanitizedFileName)?.ToLower();
if (extension == ".png") if (extension == ".png")
{ {
@ -247,7 +223,7 @@ namespace PSTW_CentralSystem.Areas.MMS.Controllers
} }
} }
public async Task<IActionResult> GenerateReport(int id) public async Task<IActionResult> GenerateReport(int id)//calls GeneratePdfResponse to generate a PDF for inline viewing
{ {
return await GeneratePdfResponse(id, true); return await GeneratePdfResponse(id, true);
} }
@ -264,7 +240,7 @@ namespace PSTW_CentralSystem.Areas.MMS.Controllers
{ {
// Add timeout for safety // Add timeout for safety
var task = Task.Run(() => GeneratePdfResponse(id, false)); var task = Task.Run(() => GeneratePdfResponse(id, false));
if (task.Wait(TimeSpan.FromSeconds(30))) // 30-second timeout if (task.Wait(TimeSpan.FromSeconds(30))) // 30 second timeout
{ {
return task.Result; return task.Result;
} }
@ -279,15 +255,24 @@ namespace PSTW_CentralSystem.Areas.MMS.Controllers
private async Task<IActionResult> GeneratePdfResponse(int id, bool forceDownload) private async Task<IActionResult> GeneratePdfResponse(int id, bool forceDownload)
{ {
Console.WriteLine($"Requested ID in {(forceDownload ? "GenerateReport" : "ViewPDF")}: {id}");
// Test network connection first
try
{
_networkAccessService.ConnectToNetworkPath();
_networkAccessService.DisconnectFromNetworkShare();
}
catch (Exception ex)
{
return StatusCode(500, $"Cannot access network: {ex.Message}");
}
try try
{ {
_networkAccessService.ConnectToNetworkPath(); _networkAccessService.ConnectToNetworkPath();
var record = await _context.MarineTarballs.FirstOrDefaultAsync(t => t.Id == id); // ===== 1. Get Data from Database =====
if (record == null)
return NotFound($"No record found for Id: {id}");
// Retrieve all entries for the same StationID and DateSample
var query = @" var query = @"
SELECT SELECT
marine.*, marine.*,
@ -300,135 +285,116 @@ namespace PSTW_CentralSystem.Areas.MMS.Controllers
JOIN tbl_state state ON station.StateID = state.StateID JOIN tbl_state state ON station.StateID = state.StateID
JOIN tbl_user user ON marine.FirstSampler = user.FullName JOIN tbl_user user ON marine.FirstSampler = user.FullName
JOIN tbl_level level ON user.LevelID = level.LevelID JOIN tbl_level level ON user.LevelID = level.LevelID
WHERE marine.Id=@id"; WHERE marine.Id = @id";
var tarballEntries = await _context.Database var tarball = await _context.Database
.SqlQueryRaw<TarballPdfDto>(query, new MySqlParameter("@id", id)) .SqlQueryRaw<TarballPdfDto>(query, new MySqlParameter("@id", id))
.ToListAsync(); .FirstOrDefaultAsync();
if (!tarballEntries.Any()) if (tarball == null)
return NotFound($"No records found for Id: {id}"); return NotFound("Record not found");
var pdfList = new List<IActionResult>(); // Prepare boolean values for PDF
bool tarBallYes = tarball.ClassifyID != "NO";
bool tarBallNo = tarball.ClassifyID == "NO";
bool isSand = tarball.ClassifyID == "SD";
bool isNonSandy = tarball.ClassifyID == "NS";
bool isCoquina = tarball.ClassifyID == "CO";
// Iterate over each unique TimeSample within the same DateSample // ===== 2. Get Images =====
foreach (var timeGroup in tarballEntries.GroupBy(t => t.TimeSample)) // For date (stored as DATE in DB → "2025-01-30" becomes "20250130")
var sampleDateString = tarball.DateSample.ToString("yyyyMMdd");
// For time (stored as TIME in DB → "16:49:02" becomes "164902")
var sampleTimeString = tarball.TimeSample.ToString("hhmmss");
var stationFolder = Path.Combine(PhotoBasePath, tarball.StationID);
var stationImages = new Dictionary<string, string>();
var foundAnyImages = false;
if (Directory.Exists(stationFolder))
{ {
var tarball = timeGroup.First(); // Use first entry per time sample var basePattern = $"{tarball.StationID}_{sampleDateString}_{sampleTimeString}_";
var allImages = Directory.GetFiles(stationFolder, $"{basePattern}*");
// Classification logic remains unchanged foreach (var imagePath in allImages)
bool tarBallYes = tarball.ClassifyID != "NO";
bool tarBallNo = tarball.ClassifyID == "NO";
bool isSand = tarball.ClassifyID == "SD";
bool isNonSandy = tarball.ClassifyID == "NS";
bool isCoquina = tarball.ClassifyID == "CO";
// Construct image paths ensuring correct naming format
var stationFolder = Path.Combine(PhotoBasePath, tarball.StationID);
var sampleDateString = tarball.DateSample.ToString("yyyyMMdd");
var sampleTimeString = tarball.TimeSample.ToString(@"hhmmss");
var stationImages = new Dictionary<string, string>();
if (Directory.Exists(stationFolder))
{ {
var imageTypes = new Dictionary<string, string> var fileName = Path.GetFileNameWithoutExtension(imagePath);
{ var type = fileName.Split('_').Last();
{ "LEFTSIDECOASTALVIEW", null }, stationImages[type] = imagePath;
{ "RIGHTSIDECOASTALVIEW", null }, foundAnyImages = true;
{ "DRAWINGVERTICALLINES", null },
{ "DRAWINGHORIZONTALLINES", null },
{ "OPTIONAL01", null },
{ "OPTIONAL02", null },
{ "OPTIONAL03", null },
{ "OPTIONAL04", null }
};
foreach (var imagePath in Directory.GetFiles(stationFolder))
{
var fileName = Path.GetFileNameWithoutExtension(imagePath);
foreach (var type in imageTypes.Keys.ToList())
{
if (fileName.StartsWith($"{tarball.StationID}_{sampleDateString}_{sampleTimeString}") &&
fileName.Contains(type, StringComparison.OrdinalIgnoreCase))
{
imageTypes[type] = imagePath;
break;
}
}
}
stationImages = imageTypes;
} }
// Validate mandatory images exist
var mandatoryImages = new List<string>
{
"LEFTSIDECOASTALVIEW",
"RIGHTSIDECOASTALVIEW",
"DRAWINGVERTICALLINES",
"DRAWINGHORIZONTALLINES"
};
foreach (var mandatoryType in mandatoryImages)
{
if (!stationImages.ContainsKey(mandatoryType) || stationImages[mandatoryType] == null)
{
return StatusCode(400, $"Missing mandatory image: {mandatoryType} for {tarball.StationID} on {tarball.DateSample}");
}
}
// Generate PDF using existing classification and sorting logic
var pdfDocument = new TarBallPDF(
tarball.StateName,
tarball.StationID,
tarball.LocationName,
tarball.Longitude,
tarball.Latitude,
tarball.DateSample,
tarball.TimeSample,
tarball.ClassifyID,
tarBallYes,
tarBallNo,
isSand,
isNonSandy,
isCoquina,
stationImages["LEFTSIDECOASTALVIEW"],
stationImages["RIGHTSIDECOASTALVIEW"],
stationImages["DRAWINGVERTICALLINES"],
stationImages["DRAWINGHORIZONTALLINES"],
stationImages.GetValueOrDefault("OPTIONAL01"),
stationImages.GetValueOrDefault("OPTIONAL02"),
stationImages.GetValueOrDefault("OPTIONAL03"),
stationImages.GetValueOrDefault("OPTIONAL04"),
tarball.OptionalName1,
tarball.OptionalName2,
tarball.OptionalName3,
tarball.OptionalName4,
tarball.FirstSampler,
tarball.FullName,
tarball.LevelName
).GeneratePdf();
string pdfFileName = $"{tarball.StationID}_{tarball.DateSample:yyyyMMdd}_{tarball.TimeSample:hhmmss}.pdf";
pdfList.Add(
forceDownload
? File(pdfDocument, "application/pdf", pdfFileName)
: File(pdfDocument, "application/pdf")
);
} }
return pdfList.Count == 1 ? pdfList.First() : Ok(pdfList); if (!foundAnyImages)
{
return StatusCode(404, "No images found for this record");
}
// Verify mandatory images exist
var mandatoryImages = new[] {
"LEFTSIDECOASTALVIEW",
"RIGHTSIDECOASTALVIEW",
"DRAWINGVERTICALLINES",
"DRAWINGHORIZONTALLINES"
};
foreach (var type in mandatoryImages)
{
if (!stationImages.ContainsKey(type))
{
return StatusCode(400, $"Missing mandatory image: {type}");
}
}
// ===== 3. Generate PDF =====
var pdf = new TarBallPDF(
tarball.StateName,
tarball.StationID,
tarball.LocationName,
tarball.Longitude,
tarball.Latitude,
tarball.DateSample,
tarball.TimeSample,
tarball.ClassifyID,
tarBallYes,
tarBallNo,
isSand,
isNonSandy,
isCoquina,
stationImages["LEFTSIDECOASTALVIEW"],
stationImages["RIGHTSIDECOASTALVIEW"],
stationImages["DRAWINGVERTICALLINES"],
stationImages["DRAWINGHORIZONTALLINES"],
stationImages.GetValueOrDefault("OPTIONAL01"),
stationImages.GetValueOrDefault("OPTIONAL02"),
stationImages.GetValueOrDefault("OPTIONAL03"),
stationImages.GetValueOrDefault("OPTIONAL04"),
tarball.OptionalName1,
tarball.OptionalName2,
tarball.OptionalName3,
tarball.OptionalName4,
tarball.FirstSampler,
tarball.FullName,
tarball.LevelName
).GeneratePdf();
// ===== 4. Return PDF =====
var downloadName = $"{tarball.StationID}_{sampleDateString}_{sampleTimeString}.pdf";
return forceDownload
? File(pdf, "application/pdf", downloadName)
: File(pdf, "application/pdf");
} }
catch (Exception ex) catch (Exception ex)
{ {
return StatusCode(500, $"Error: {ex.Message}"); return StatusCode(500, $"PDF generation failed: {ex.Message}");
} }
finally finally
{ {
_networkAccessService.DisconnectFromNetworkShare(); _networkAccessService.DisconnectFromNetworkShare();
} }
} }
private bool IsImageValid(string imagePath) private bool IsImageValid(string imagePath)
{ {
try try

View File

@ -11,8 +11,8 @@ namespace PSTW_CentralSystem.Areas.MMS.Models.PDFGenerator
string longitude, string latitude, DateTime dateSample, TimeSpan timeSample, string longitude, string latitude, DateTime dateSample, TimeSpan timeSample,
string classifyID, bool tarBallYes, bool tarBallNo, bool isSand, bool isNonSandy, string classifyID, bool tarBallYes, bool tarBallNo, bool isSand, bool isNonSandy,
bool isCoquina, string photoPath1, string photoPath2, string photoPath3, string photoPath4, bool isCoquina, string photoPath1, string photoPath2, string photoPath3, string photoPath4,
string photoPath5, string photoPath6, string photoPath7, string photoPath8, string? photoPath5, string? photoPath6, string? photoPath7, string? photoPath8,
string optionalName1, string optionalName2, string optionalName3, string optionalName4, string? optionalName1, string? optionalName2, string? optionalName3, string? optionalName4,
string firstSampler, string fullName, string levelName string firstSampler, string fullName, string levelName
) )
: IDocument : IDocument
@ -360,7 +360,7 @@ namespace PSTW_CentralSystem.Areas.MMS.Models.PDFGenerator
table.Cell().Element(CellStyle).Text("Signature").FontSize(12); table.Cell().Element(CellStyle).Text("Signature").FontSize(12);
table.Cell().Element(CellStyle).Text(""); table.Cell().Element(CellStyle).Text("");
table.Cell().Element(CellStyle).Text("Date").FontSize(12); table.Cell().Element(CellStyle).Text("Date").FontSize(12);
table.Cell().Element(CellStyle).Text(_dateSample.ToString("yyyy/MM/dd")).FontSize(12); table.Cell().Element(CellStyle).Text($"{_dateSample:yyyyMMdd}").FontSize(12);
table.Cell().Element(CellStyle).Text("Designation").FontSize(12); table.Cell().Element(CellStyle).Text("Designation").FontSize(12);
table.Cell().ColumnSpan(3).Element(CellStyle).Text(_levelName).FontSize(12); table.Cell().ColumnSpan(3).Element(CellStyle).Text(_levelName).FontSize(12);

View File

@ -102,21 +102,15 @@
return []; // Return an empty array if no data is available return []; // Return an empty array if no data is available
} }
// Extract all years from the data (handling both FormattedDate and date) // Extract all years from the data
const allYears = this.dataFromServer.map(data => { const allYears = this.dataFromServer.map(data => new Date(data.date).getFullYear());
const dateStr = data.Date || data.date; // Handle both cases
return new Date(dateStr).getFullYear();
});
// Find the minimum and maximum years // Find the minimum and maximum years
const minYear = Math.min(...allYears); const minYear = Math.min(...allYears);
const maxYear = Math.max(...allYears); const maxYear = Math.max(...allYears);
// Generate a range of years from minYear to maxYear // Generate a range of years from minYear to maxYear
return Array.from( return Array.from({ length: maxYear - minYear + 1 }, (_, i) => (minYear + i).toString());
{ length: maxYear - minYear + 1 },
(_, i) => (minYear + i).toString()
);
}, },
sortedFilteredData() { sortedFilteredData() {
// If no filters are applied, return all data sorted by descending date // If no filters are applied, return all data sorted by descending date
@ -160,11 +154,12 @@
"data": null, "data": null,
"render": (data, type, row, meta) => meta.row + 1 // Dynamically generate "No." "render": (data, type, row, meta) => meta.row + 1 // Dynamically generate "No."
}, },
{ "data": "date"}, { "data": "date", "render": (data) => new Date(data).toLocaleDateString('en-GB') },
{ "data": "station" }, { "data": "station" },
{ {
"data": null, "data": null,
"render": (data) => ` "render": () => `
<button class="btn btn-success">Approve</button> <button class="btn btn-success">Approve</button>
<button class="btn btn-danger">Reject</button> <button class="btn btn-danger">Reject</button>
` `
@ -172,10 +167,8 @@
{ {
"data": null, "data": null,
"render": (data) => ` "render": (data) => `
<a href="/MMS/Marine/ViewPDF?stationId=${data.Id}" <a href="/MMS/Marine/ViewPDF?id=${data.id}" class="btn btn-primary" target="_blank">View PDF</a>
class="btn btn-primary" target="_blank">View PDF</a> <a href="/MMS/Marine/GenerateReport?id=${data.id}" class="btn btn-primary">Download PDF</a>
<a href="/MMS/Marine/GenerateReport?stationId=${data.Id}"
class="btn btn-primary">Download PDF</a>
` `
} }
], ],