standardise table format

This commit is contained in:
misya 2025-05-07 14:38:28 +08:00
parent 7e80ca5ef9
commit 5ae31cd21b
5 changed files with 171 additions and 127 deletions

View File

@ -21,7 +21,7 @@ namespace PSTW_CentralSystem.Areas.MMS.Controllers
_networkAccessService = networkAccessService;
}
private bool TryAccessNetworkPath()
private bool TryAccessNetworkPath() //check if the path is accessible
{
try
{
@ -71,6 +71,7 @@ namespace PSTW_CentralSystem.Areas.MMS.Controllers
})
.ToList();
Console.WriteLine($"Marine Tarballs Count: {marineTarballs.Count}");
return View(marineTarballs);
}
@ -109,7 +110,12 @@ namespace PSTW_CentralSystem.Areas.MMS.Controllers
TarBallNo = marine.ClassifyID == "NO",
IsSand = marine.ClassifyID == "SD",
IsNonSandy = marine.ClassifyID == "NS",
IsCoquina = marine.ClassifyID == "CO"
IsCoquina = marine.ClassifyID == "CO",
marine.OptionalName1,
marine.OptionalName2,
marine.OptionalName3,
marine.OptionalName4,
marine.FirstSampler
}).FirstOrDefault();
if (tarballData == null)
@ -118,7 +124,7 @@ namespace PSTW_CentralSystem.Areas.MMS.Controllers
// 2. Get photos from station folder (with date matching)
var sampleDateString = tarballData.DateSample.ToString("yyyyMMdd");
var stationFolder = Path.Combine(PhotoBasePath, tarballData.StationID);
var stationImages = new List<string>();
var stationImages = new Dictionary<string, string>();
if (Directory.Exists(stationFolder))
{
@ -150,37 +156,33 @@ namespace PSTW_CentralSystem.Areas.MMS.Controllers
"OPTIONAL04"
};
// Sort logic
bool hasValidFormat = allImages.Any(f =>
imageTypesInOrder.Any(t => Path.GetFileNameWithoutExtension(f).ToUpper().Contains(t)));
if (hasValidFormat)
// Match images to their types
foreach (var imageType in imageTypesInOrder)
{
stationImages = allImages
.OrderBy(f =>
var matchedImage = allImages.FirstOrDefault(f =>
Path.GetFileNameWithoutExtension(f).ToUpper().Contains(imageType));
if (matchedImage != null)
{
var fileName = Path.GetFileNameWithoutExtension(f).ToUpper();
var typeIndex = imageTypesInOrder.FindIndex(t => fileName.Contains(t));
return typeIndex >= 0 ? typeIndex : int.MaxValue;
})
.ThenBy(f => f)
.Take(8)
.ToList();
stationImages[imageType] = matchedImage;
}
else
{
stationImages = allImages
.OrderBy(f => f)
.Take(8)
.ToList();
Console.WriteLine($"WARNING: No images matched keywords. Sorted alphabetically.");
}
}
// Validate minimum images
if (stationImages.Count < 4)
// Validate mandatory images
var mandatoryImages = new List<string>
{
return StatusCode(400, $"Minimum 4 images required for {tarballData.DateSample:yyyy/MM/dd}. Found: {stationImages.Count}");
"LEFTSIDECOASTALVIEW",
"RIGHTSIDECOASTALVIEW",
"DRAWINGVERTICALLINES",
"DRAWINGHORIZONTALLINES"
};
foreach (var mandatoryImage in mandatoryImages)
{
if (!stationImages.ContainsKey(mandatoryImage))
{
return StatusCode(400, $"Missing mandatory image: {mandatoryImage}");
}
}
// 3. Generate PDF
@ -198,14 +200,19 @@ namespace PSTW_CentralSystem.Areas.MMS.Controllers
tarballData.IsSand,
tarballData.IsNonSandy,
tarballData.IsCoquina,
stationImages.Count > 0 ? stationImages[0] : null,
stationImages.Count > 1 ? stationImages[1] : null,
stationImages.Count > 2 ? stationImages[2] : null,
stationImages.Count > 3 ? stationImages[3] : null,
stationImages.Count > 4 ? stationImages[4] : null,
stationImages.Count > 5 ? stationImages[5] : null,
stationImages.Count > 6 ? stationImages[6] : null,
stationImages.Count > 7 ? stationImages[7] : null
stationImages.ContainsKey("LEFTSIDECOASTALVIEW") ? stationImages["LEFTSIDECOASTALVIEW"] : null,
stationImages.ContainsKey("RIGHTSIDECOASTALVIEW") ? stationImages["RIGHTSIDECOASTALVIEW"] : null,
stationImages.ContainsKey("DRAWINGVERTICALLINES") ? stationImages["DRAWINGVERTICALLINES"] : null,
stationImages.ContainsKey("DRAWINGHORIZONTALLINES") ? stationImages["DRAWINGHORIZONTALLINES"] : null,
stationImages.ContainsKey("OPTIONAL01") ? stationImages["OPTIONAL01"] : null,
stationImages.ContainsKey("OPTIONAL02") ? stationImages["OPTIONAL02"] : null,
stationImages.ContainsKey("OPTIONAL03") ? stationImages["OPTIONAL03"] : null,
stationImages.ContainsKey("OPTIONAL04") ? stationImages["OPTIONAL04"] : null,
tarballData.OptionalName1,
tarballData.OptionalName2,
tarballData.OptionalName3,
tarballData.OptionalName4,
tarballData.FirstSampler
).GeneratePdf();
// 4. Return file

View File

@ -10,7 +10,9 @@ namespace PSTW_CentralSystem.Areas.MMS.Models.PDFGenerator
string longitude, string latitude, DateTime dateSample, TimeSpan timeSample,
string classifyID, bool tarBallYes, bool tarBallNo, bool isSand, bool isNonSandy,
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 firstSampler
)
: IDocument
{
@ -27,14 +29,19 @@ namespace PSTW_CentralSystem.Areas.MMS.Models.PDFGenerator
private readonly bool _isSand = isSand;
private readonly bool _isNonSandy = isNonSandy;
private readonly bool _isCoquina = isCoquina;
private readonly string? _photoPath1 = photoPath1; //having '?' makes it nullable, ELSE the string must always have a value
private readonly string? _photoPath2 = photoPath2; //"but why keep '?' for photopath 1 to 4?
private readonly string? _photoPath3 = photoPath3; //A: to make
private readonly string? _photoPath4 = photoPath4;
private readonly string _photoPath1 = photoPath1;
private readonly string _photoPath2 = photoPath2;
private readonly string _photoPath3 = photoPath3;
private readonly string _photoPath4 = photoPath4;
private readonly string? _photoPath5 = photoPath5;
private readonly string? _photoPath6 = photoPath6;
private readonly string? _photoPath7 = photoPath7;
private readonly string? _photoPath8 = photoPath8;
private readonly string? _optionalName1 = optionalName1;
private readonly string? _optionalName2 = optionalName2;
private readonly string? _optionalName3 = optionalName3;
private readonly string? _optionalName4 = optionalName4;
private readonly string _firstSampler = firstSampler;
private Image? LoadImage(string? imagePath)
{
@ -286,8 +293,10 @@ namespace PSTW_CentralSystem.Areas.MMS.Models.PDFGenerator
.Image(LoadImage(_photoPath6) ?? null) // Just pass null if no image
.FitArea();
table.Cell().Element(CellStyle).Text("Figure 5:").FontSize(12).AlignLeft();
table.Cell().Element(CellStyle).Text("Figure 6:").FontSize(12).AlignLeft();
table.Cell().Element(CellStyle).Text($"Figure 5: {_optionalName1}")
.FontSize(12).AlignLeft();
table.Cell().Element(CellStyle).Text($"Figure 6: {_optionalName2}")
.FontSize(12).AlignLeft();
table.Cell().Element(CellStyle).Height(150)
.Image(LoadImage(_photoPath7) ?? null) // Just pass null if no image
@ -296,8 +305,10 @@ namespace PSTW_CentralSystem.Areas.MMS.Models.PDFGenerator
.Image(LoadImage(_photoPath8) ?? null) // Just pass null if no image
.FitArea();
table.Cell().Element(CellStyle).Text("Figure 7:").FontSize(12).AlignLeft();
table.Cell().Element(CellStyle).Text("Figure 8:").FontSize(12).AlignLeft();
table.Cell().Element(CellStyle).Text($"Figure 7: {_optionalName3}")
.FontSize(12).AlignLeft();
table.Cell().Element(CellStyle).Text($"Figure 8: {_optionalName4}")
.FontSize(12).AlignLeft();
});
// Note Section
@ -322,7 +333,12 @@ namespace PSTW_CentralSystem.Areas.MMS.Models.PDFGenerator
columns.RelativeColumn(1);
});
table.Cell().RowSpan(2).Element(CellStyle).Text("REPORTED BY :").Bold().FontSize(12);
table.Cell().RowSpan(2).Element(CellStyle)
.Text(text =>
{
text.Span("REPORTED BY: ").Bold().FontSize(12);
text.Span(_firstSampler).FontSize(10);
});
table.Cell().Element(CellStyle).Text("Signature").FontSize(12);
table.Cell().Element(CellStyle).Text("");
table.Cell().Element(CellStyle).Text("Date").FontSize(12);
@ -331,7 +347,12 @@ namespace PSTW_CentralSystem.Areas.MMS.Models.PDFGenerator
table.Cell().Element(CellStyle).Text("Designation").FontSize(12);
table.Cell().ColumnSpan(3).Element(CellStyle).Text("");
table.Cell().RowSpan(2).Element(CellStyle).Text("CHECKED BY :").Bold().FontSize(12);
table.Cell().RowSpan(2).Element(CellStyle)
.Text(text =>
{
text.Span("CHECKED BY: ").Bold().FontSize(12);
text.Span("Rifaie Azhari").FontSize(10);
});
table.Cell().Element(CellStyle).Text("Signature").FontSize(12);
table.Cell().ColumnSpan(2).Element(CellStyle).Text("");
//table.Cell().Element(CellStyle).Text("Date").FontSize(12);

View File

@ -20,8 +20,8 @@
}
div {
padding-top: 10px;
padding-bottom: 10px;
padding-top: 5px;
padding-bottom: 5px;
}
h4 {
@ -38,39 +38,22 @@
border: 1px solid #ccc;
padding: 10px;
}
.tbhead {
text-align: center;
}
/* Default arrow style (grey) */
.sort-arrow {
color: #aaa; /* Light grey for default state */
font-size: 12px; /* Adjust size for better visibility */
line-height: 1; /* Ensure proper spacing */
display: inline-block; /* Stack arrows vertically */
margin: 0; /* Remove extra spacing */
}
/* Active arrow style (darker grey) */
.sort-arrow.active {
color: #333; /* Dark grey for active state */
font-weight: bold; /* Optional: Make it bold for emphasis */
}
</style>
<!-- DataTables CSS -->
<link href="~/lib/datatables/datatables.css" rel="stylesheet" />
</head>
<body>
<div id="app" class="container">
<div>
<h4>Month</h4>
<select v-model="selectedMonth" style="width: 100%; padding: 5px;">
<option value="default" selected disabled>Filter by Month</option>
<option value="" selected>Filter by Month</option>
<option v-for="month in months" :value="month">{{ month }}</option>
</select>
<h4>Year</h4>
<select v-model="selectedYear" style="width: 100%; padding: 5px;">
<option value="default" selected disabled>Filter by Year</option>
<option value="" selected>Filter by Year</option>
<option v-for="year in years" :value="year">{{ year }}</option>
</select>
@ -80,40 +63,17 @@
</div>
<div class="datatable">
<table>
<table id="tarballTable" class="table table-bordered table-hover table-striped" style="width:100%;">
<thead>
<tr>
<th>No.</th>
<th v-on:click="sortBy('date')">
Date
<span :class="['sort-arrow', { active: sortKey === 'date' && sortOrder === 'asc' }]">▲</span>
<span :class="['sort-arrow', { active: sortKey === 'date' && sortOrder === 'desc' }]">▼</span>
</th>
<th v-on:click="sortBy('station')">
Station
<span :class="['sort-arrow', { active: sortKey === 'station' && sortOrder === 'asc' }]">▲</span>
<span :class="['sort-arrow', { active: sortKey === 'station' && sortOrder === 'desc' }]">▼</span>
</th>
<th>Date</th>
<th>Station</th>
<th>Status</th>
<th>PDF</th>
</tr>
</thead>
<tbody>
<tr v-for="(data, index) in numberedData" :key="data.id">
<td>{{ index + 1 }}</td>
<td>{{ new Date(data.date).toLocaleDateString('en-GB') }}</td>
<td>{{ data.station }}</td>
<td>
<button class="btn btn-success">Approve</button>
<button class="btn btn-danger">Reject</button>
</td>
<td>
<a :href="`/MMS/Marine/ViewPDF?id=${data.id}`" class="btn btn-primary" target="_blank">View PDF</a>
<a :href="`/MMS/Marine/GenerateReport?id=${data.id}`" class="btn btn-primary">Download PDF</a>
</td>
</tr>
</tbody>
<tbody></tbody>
</table>
</div>
</div>
@ -121,14 +81,14 @@
</html>
@section Scripts {
<!-- DataTables JS -->
<script src="~/lib/datatables/datatables.js"></script>
<script>
new Vue({
el: '#app',
data: {
selectedMonth: '',
selectedYear: '',
sortKey: 'date',
sortOrder: 'desc',
months: [
'January', 'February', 'March', 'April', 'May',
'June', 'July', 'August', 'September',
@ -138,13 +98,28 @@
},
computed: {
years() {
if (!this.dataFromServer || this.dataFromServer.length === 0) {
return []; // Return an empty array if no data is available
}
// Extract all years from the data
const allYears = this.dataFromServer.map(data => new Date(data.date).getFullYear());
// Find the minimum and maximum years
const minYear = Math.min(...allYears);
const maxYear = Math.max(...allYears);
// Generate a range of years from minYear to maxYear
return Array.from({ length: maxYear - minYear + 1 }, (_, i) => (minYear + i).toString());
},
filteredData() {
return this.dataFromServer.filter(data => {
sortedFilteredData() {
// If no filters are applied, return all data sorted by descending date
if (!this.selectedMonth && !this.selectedYear) {
return this.dataFromServer.sort((a, b) => new Date(b.date) - new Date(a.date));
}
// Filter data by selected month and year
const filtered = this.dataFromServer.filter(data => {
const date = new Date(data.date);
const monthMatches = this.selectedMonth
? date.toLocaleString('default', { month: 'long' }) === this.selectedMonth
@ -154,33 +129,66 @@
: true;
return monthMatches && yearMatches;
});
},
numberedData() {
return this.filteredData
.slice()
.sort((a, b) => {
const dateA = new Date(a.date);
const dateB = new Date(b.date);
return this.sortOrder === 'asc' ? dateA - dateB : dateB - dateA;
})
.map((data, index) => ({
no: index + 1,
...data
}));
// Sort data by date in descending order
return filtered.sort((a, b) => new Date(b.date) - new Date(a.date));
}
},
methods: {
clearFilters() {
this.selectedMonth = '';
this.selectedYear = '';
},
sortBy(key) {
if (this.sortKey === key) {
this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc';
} else {
this.sortKey = key;
this.sortOrder = 'asc';
}
},
mounted() {
// Initialize DataTables after Vue has rendered the table
this.$nextTick(() => {
const table = $('#tarballTable').DataTable({
"pageLength": 10,
"lengthMenu": [5, 10, 15, 20],
"responsive": true,
"order": [[1, "desc"]], // Default sorting by Date column (descending)
"orderMulti": false, // Disable multi-column sorting
"columns": [
{
"data": null,
"render": (data, type, row, meta) => meta.row + 1 // Dynamically generate "No."
},
{ "data": "date", "render": (data) => new Date(data).toLocaleDateString('en-GB') },
{ "data": "station" },
{
"data": null,
"render": () => `
<button class="btn btn-success">Approve</button>
<button class="btn btn-danger">Reject</button>
`
},
{
"data": null,
"render": (data) => `
<a href="/MMS/Marine/ViewPDF?id=${data.id}" class="btn btn-primary" target="_blank">View PDF</a>
<a href="/MMS/Marine/GenerateReport?id=${data.id}" class="btn btn-primary">Download PDF</a>
`
}
],
"rowCallback": function(row, data, index) {
// Update the "No." column to start from 1 for the current page
const pageInfo = table.page.info();
$('td:first', row).html(pageInfo.start + index + 1);
}
});
// Populate the table with all data on initial load
table.rows.add(this.dataFromServer).draw();
});
},
watch: {
sortedFilteredData() {
// Automatically update DataTables whenever the filtered data changes
const table = $('#tarballTable').DataTable();
table.clear();
table.rows.add(this.sortedFilteredData);
table.draw();
}
}
});

View File

@ -38,6 +38,10 @@ namespace PSTW_CentralSystem.DBContext
entity.Property(e => e.Longitude).HasColumnName("longitude");
entity.Property(e => e.GetLatitude).HasColumnName("getLatitude");
entity.Property(e => e.GetLongitude).HasColumnName("getLongitude");
entity.Property(e => e.OptionalName1).HasColumnName("optionalName1");
entity.Property(e => e.OptionalName2).HasColumnName("optionalName2");
entity.Property(e => e.OptionalName3).HasColumnName("optionalName3");
entity.Property(e => e.OptionalName4).HasColumnName("optionalName4");
entity.Property(e => e.Timestamp).HasColumnName("timestamp");
//Configure relationship with TarballStation

View File

@ -17,15 +17,19 @@ namespace PSTW_CentralSystem.Models
public double GetLatitude { get; set; } // Maps to 'getLatitude'
public double GetLongitude { get; set; } // Maps to 'getLongitude'
public DateTime Timestamp { get; set; } // Maps to 'timestamp'
public string? OptionalName1 { get; set; }
public string? OptionalName2 { get; set; }
public string? OptionalName3 { get; set; }
public string? OptionalName4 { get; set; }
public required string PhotoPath1 { get; set; } // Left Side Coastal View
public required string PhotoPath2 { get; set; } // Right Side Coastal View
public required string PhotoPath3 { get; set; } // Vertical Lines
public required string PhotoPath4 { get; set; } // Horizontal Lines
public required string PhotoPath5 { get; set; } // optional
public required string PhotoPath6 { get; set; } // optional
public required string PhotoPath7 { get; set; } // optional
public required string PhotoPath8 { get; set; } // optional
public string? PhotoPath5 { get; set; } // optional
public string? PhotoPath6 { get; set; } // optional
public string? PhotoPath7 { get; set; } // optional
public string? PhotoPath8 { get; set; } // optional
[ForeignKey("StationID")]
public required MarineStation MarineStation { get; set; }