Backend pagination for Item, Pr Tracking, approved pr, Deleted PR

This commit is contained in:
rowell_m_soriano 2026-03-12 16:03:02 +08:00
parent 1b06999f15
commit d770613286
31 changed files with 1325 additions and 968 deletions

View File

@ -2,19 +2,13 @@
using CPRNIMS.Domain.Contracts.PR; using CPRNIMS.Domain.Contracts.PR;
using CPRNIMS.Domain.Services; using CPRNIMS.Domain.Services;
using CPRNIMS.Infrastructure.Dto.Items; using CPRNIMS.Infrastructure.Dto.Items;
using CPRNIMS.Infrastructure.Dto.PO;
using CPRNIMS.Infrastructure.Dto.PR; using CPRNIMS.Infrastructure.Dto.PR;
using CPRNIMS.Infrastructure.Entities.PO;
using CPRNIMS.Infrastructure.Entities.Purchasing;
using CPRNIMS.Infrastructure.Helper; using CPRNIMS.Infrastructure.Helper;
using CPRNIMS.Infrastructure.Models.Common; using CPRNIMS.Infrastructure.Models.Common;
using CPRNIMS.Infrastructure.ViewModel.Common; using CPRNIMS.Infrastructure.ViewModel.Common;
using CPRNIMS.Infrastructure.ViewModel.PR; using CPRNIMS.Infrastructure.ViewModel.PR;
using CPRNIMS.WebApi.Controllers.Base; using CPRNIMS.WebApi.Controllers.Base;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System.Reflection.Metadata.Ecma335;
using System.Text; using System.Text;
@ -377,6 +371,15 @@ namespace CPRNIMS.WebApi.Controllers.PR
nameof(GetAllPR), false nameof(GetAllPR), false
); );
} }
[HttpPost("GetPRArchived")]
public async Task<IActionResult> GetPRArchived(PRDto PRDto)
{
return await ExecuteWithErrorHandling(
() => _pRequest.GetPRArchived(PRDto),
nameof(GetPRArchived), false
);
}
[HttpPost("GetApprovedPR")] [HttpPost("GetApprovedPR")]
public async Task<IActionResult> GetApprovedPR(PRDto PRDto) public async Task<IActionResult> GetApprovedPR(PRDto PRDto)
{ {
@ -385,12 +388,12 @@ namespace CPRNIMS.WebApi.Controllers.PR
nameof(GetApprovedPR), false nameof(GetApprovedPR), false
); );
} }
[HttpPost("GetRemovedPR")] [HttpPost("GetDeletedPR")]
public async Task<IActionResult> GetRemovedPR(PRDto PRDto) public async Task<IActionResult> GetDeletedPR(PRDto PRDto)
{ {
return await ExecuteWithErrorHandling( return await ExecuteWithErrorHandling(
() => _pRequest.GetRemovedPR(PRDto), () => _pRequest.GetDeletedPR(PRDto),
nameof(GetRemovedPR), false nameof(GetDeletedPR), false
); );
} }
[HttpPost("GetMyPR")] [HttpPost("GetMyPR")]

View File

@ -326,11 +326,25 @@ namespace CPRNIMS.WebApps.Controllers.Items
throw; throw;
} }
} }
public async Task<IActionResult> GetItemList() [HttpGet]
public async Task<IActionResult> GetItemList(string searchTerm = "", int pageNumber = 1, int pageSize = 10)
{ {
var viewModels = new ItemVM(); var viewModel = new ItemVM
response = await _item.GetItemList(GetUser(), viewModels); {
return GetResponse(response); SearchTerm = searchTerm,
PageNumber = pageNumber,
PageSize = pageSize
};
var result = await _item.GetItemList(GetUser(), viewModel);
return Json(new
{
draw = Request.Query["draw"].ToString(),
recordsTotal = result.TotalCount,
recordsFiltered = result.TotalCount,
data = result.Data
});
} }
public async Task<IActionResult> GetItemCateg(ItemVM viewModels) public async Task<IActionResult> GetItemCateg(ItemVM viewModels)
{ {

View File

@ -21,20 +21,134 @@ namespace CPRNIMS.WebApps.Controllers.PR
_pRequest = pRequest; _pRequest = pRequest;
} }
#region Get #region Get
public async Task<IActionResult> GetAllPR(PRVM viewModels) [HttpGet]
public async Task<IActionResult> GetAllPR(
string searchPRNo = "", string searchItemName = "",
string searchDept = "", int pageNumber = 1, int pageSize = 10)
{ {
response = await _pRequest.GetAllPR(GetUser(), viewModels); var dto = new PRVM
return GetResponse(response); {
SearchPRNo = searchPRNo,
SearchItemName = searchItemName,
SearchDept = searchDept,
PageNumber = pageNumber,
PageSize = pageSize
};
var result = await _pRequest.GetAllPR(GetUser(),dto);
int draw = int.TryParse(Request.Query["draw"], out int d) ? d : 1;
return Json(new
{
draw = draw,
recordsTotal = result.TotalCount,
recordsFiltered = result.TotalCount,
data = result.Data
});
} }
public async Task<IActionResult> GetApprovedPR(PRVM viewModels) [HttpGet]
public async Task<IActionResult> GetPRArchived(
string searchPRNo = "", string searchItemName = "",
string searchDept = "", int pageNumber = 1, int pageSize = 10)
{ {
response = await _pRequest.GetApprovedPR(GetUser(), viewModels); var dto = new PRVM
return GetResponse(response); {
SearchPRNo = searchPRNo,
SearchItemName = searchItemName,
SearchDept = searchDept,
PageNumber = pageNumber,
PageSize = pageSize
};
var result = await _pRequest.GetPRArchived(GetUser(), dto);
int draw = int.TryParse(Request.Query["draw"], out int d) ? d : 1;
return Json(new
{
draw = draw,
recordsTotal = result.TotalCount,
recordsFiltered = result.TotalCount,
data = result.Data
});
} }
public async Task<IActionResult> GetRemovedPR(PRVM viewModels) public async Task<IActionResult> GetDetailedPRTracking(
string searchPRNo = "", string searchItemName = "", string searchDept = "", string searchStatusName = "",
int pageNumber = 1, int pageSize = 10)
{ {
response = await _pRequest.GetRemovedPR(GetUser(), viewModels); var dto = new PRVM
return GetResponse(response); {
SearchPRNo = searchPRNo,
SearchItemName = searchItemName,
SearchDept = searchDept,
SearchStatusName = searchStatusName,
PageNumber = pageNumber,
PageSize = pageSize
};
var result = await _pRequest.GetDetailedPRTracking(GetUser(), dto);
int draw = int.TryParse(Request.Query["draw"], out int d) ? d : 1;
return Json(new
{
draw = draw,
recordsTotal = result.TotalCount,
recordsFiltered = result.TotalCount,
data = result.Data,
statusList = result.StatusList
});
}
[HttpGet]
public async Task<IActionResult> GetApprovedPR(
string searchPRNo = "", string searchItemName = "", string searchDept = "", string searchStatusName = "",
int pageNumber = 1, int pageSize = 10)
{
var dto = new PRVM
{
SearchPRNo = searchPRNo,
SearchItemName = searchItemName,
SearchDept = searchDept,
SearchStatusName = searchStatusName,
PageNumber = pageNumber,
PageSize = pageSize
};
var result = await _pRequest.GetApprovedPR(GetUser(),dto);
int draw = int.TryParse(Request.Query["draw"], out int d) ? d : 1;
return Json(new
{
draw = draw,
recordsTotal = result.TotalCount,
recordsFiltered = result.TotalCount,
data = result.Data,
statusList = result.StatusList
});
}
[HttpGet]
public async Task<IActionResult> GetDeletedPR(
bool isArchived = false, string searchPRNo = "", string searchItemName = "",
string searchDept = "", int pageNumber = 1, int pageSize = 10)
{
var dto = new PRVM
{
IsArchived = isArchived,
SearchPRNo = searchPRNo,
SearchItemName = searchItemName,
SearchDept = searchDept,
PageNumber = pageNumber,
PageSize = pageSize
};
var result = await _pRequest.GetDeletedPR(GetUser(), dto);
int draw = int.TryParse(Request.Query["draw"], out int d) ? d : 1;
return Json(new
{
draw = draw,
recordsTotal = result.TotalCount,
recordsFiltered = result.TotalCount,
data = result.Data
});
} }
[HttpGet] [HttpGet]
public async Task<IActionResult> GetPRAttachment(string fileName) public async Task<IActionResult> GetPRAttachment(string fileName)
@ -127,11 +241,6 @@ namespace CPRNIMS.WebApps.Controllers.PR
response = await _pRequest.GetItemDetailForReceiving(GetUser(), viewModel); response = await _pRequest.GetItemDetailForReceiving(GetUser(), viewModel);
return GetResponse(response); return GetResponse(response);
} }
public async Task<IActionResult> GetDetailedPRTracking(PRVM viewModel)
{
response = await _pRequest.GetDetailedPRTracking(GetUser(), viewModel);
return GetResponse(response);
}
public async Task<IActionResult> GetSupplierAlternativeOffer(PRVM viewModel) public async Task<IActionResult> GetSupplierAlternativeOffer(PRVM viewModel)
{ {
response = await _pRequest.GetSupplierAlternativeOffer(GetUser(), viewModel); response = await _pRequest.GetSupplierAlternativeOffer(GetUser(), viewModel);

View File

@ -10,7 +10,7 @@ namespace CPRNIMS.WebApps.ViewComponents.PR
{ {
1 => "~/Views/Components/PRMgmt/PRTabbedTable/AllPR.cshtml", 1 => "~/Views/Components/PRMgmt/PRTabbedTable/AllPR.cshtml",
2 => "~/Views/Components/PRMgmt/PRTabbedTable/ApprovedPR.cshtml", 2 => "~/Views/Components/PRMgmt/PRTabbedTable/ApprovedPR.cshtml",
_ => "~/Views/Components/PRMgmt/PRTabbedTable/RemovedPR.cshtml" _ => "~/Views/Components/PRMgmt/PRTabbedTable/DeletedPR.cshtml"
}; };
return View(viewName); return View(viewName);
} }

View File

@ -1,5 +1,53 @@
<!-- AllPR.cshtml - Example View Component --> <div class="tab-content-wrapper">
<div class="tab-content-wrapper"> <div class="d-flex flex-wrap gap-2 mb-2">
<div class="search-wrapper" style="min-width:180px; flex:1;">
<div class="search-inner">
<span class="search-icon">
<svg width="14" height="14" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2.5">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.35-4.35" />
</svg>
</span>
<input id="srchPRNo" class="search-input"
placeholder="PR Number…" autocomplete="off" />
<span class="search-clear pr-search-clear"
data-target="#srchPRNo" title="Clear">✕</span>
</div>
</div>
<div class="search-wrapper" style="min-width:220px; flex:2;">
<div class="search-inner">
<span class="search-icon">
<svg width="14" height="14" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2.5">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.35-4.35" />
</svg>
</span>
<input id="srchItem" class="search-input"
placeholder="Item Name…" autocomplete="off" />
<span class="search-clear pr-search-clear"
data-target="#srchItem" title="Clear">✕</span>
</div>
</div>
<div class="search-wrapper" style="min-width:180px; flex:1;">
<div class="search-inner">
<span class="search-icon">
<svg width="14" height="14" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2.5">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.35-4.35" />
</svg>
</span>
<input id="srchDept" class="search-input"
placeholder="Department…" autocomplete="off" />
<span class="search-clear pr-search-clear"
data-target="#srchDept" title="Clear">✕</span>
</div>
</div>
</div>
<table id="PRTable" class="row-border" cellspacing="0" width="100%"> <table id="PRTable" class="row-border" cellspacing="0" width="100%">
<thead > <thead >
<tr> <tr>
@ -21,91 +69,82 @@
<script> <script>
$(document).ready(function () { $(document).ready(function () {
loader = $('#overlay, #loader'); loader = $('#overlay, #loader');
UserRights = document.getElementById("roleRights").value; UserRights = document.getElementById("roleRights").value;
const reportTitle = `All Purchase Request - as of ${getFormattedDateTime()}`; const reportTitle = `All Purchase Request - as of ${getFormattedDateTime()}`;
prTable = $('#PRTable').DataTable({ prTable = $('#PRTable').DataTable({
serverSide: true,
processing: true,
searching: false,
ajax: { ajax: {
url: '/PRMgmt/GetAllPR', url: endpoint.GetAllPR,
type: 'GET', type: 'GET',
beforeSend: function () { data: function (d) {
loader.show(); return {
draw: d.draw,
searchPRNo: ($('#srchPRNo').val() || '').trim(),
searchItemName:($('#srchItem').val()|| '').trim(),
searchDept: ($('#srchDept').val() || '').trim(),
pageNumber: Math.floor(d.start / d.length) + 1,
pageSize: d.length
};
}, },
complete: function () { dataSrc: function (json) { return json.data; },
loader.hide(); beforeSend: function () { loader.show(); },
}, complete: function () { loader.hide(); },
error: function (xhr, error, thrown) { error: function (xhr, error) {
loader.hide(); loader.hide();
console.error('Error loading data:', error); console.error('Error loading data:', error);
if (typeof toastr !== 'undefined') { if (typeof toastr !== 'undefined')
toastr.error('Failed to load data. Please try again.'); toastr.error('Failed to load data. Please try again.');
} }
}
}, },
dom: 'lBfrtip', dom: 'lBfrtip',
buttons: [ buttons: [
{ {
extend: 'csv', text: '<i class="fas fa-file-csv"></i> CSV',
title: reportTitle className: 'btn btn-sm btn-secondary',
action: function () { exportAllData('csv', endpoint.GetAllPR, loader, reportTitle, colOnPRTable); }
}, },
{ {
extend: 'excel', text: '<i class="fas fa-file-excel"></i> Excel',
title: reportTitle className: 'btn btn-sm btn-success',
action: function () { exportAllData('excel', endpoint.GetAllPR, loader, reportTitle, colOnPRTable); }
}, },
{ {
extend: 'pdf', text: '<i class="fas fa-file-pdf"></i> PDF',
title: reportTitle className: 'btn btn-sm btn-danger',
action: function () { exportAllData('pdf', endpoint.GetAllPR, loader ,reportTitle, colOnPRTable); }
} }
], ],
initComplete: function () {
initializeColumnSearch({
tableId: '#PRTable',
dataTable: prTable,
searchableColumns: [
{
columnIndex: 0,
columnName: 'PR No',
placeholder: 'Enter PR Number...',
searchType: 'text',
searchMode: 'exact',
width: '150px'
},
{
columnIndex: 2,
columnName: 'Item Name',
placeholder: 'Enter Item Name...',
searchType: 'text',
searchMode: 'contains',
width: '200px'
},
{
columnIndex: 6,
columnName: 'Department',
placeholder: 'Select Department...',
searchType: 'select',
searchMode: 'exact',
width: '150px'
}
]
});
const uniqueSearchClass = 'column-search-input-PRTable';
populateSelectOptions(prTable, 6, '.' + uniqueSearchClass + '[data-column="6"]');
restoreSearchFromURL(uniqueSearchClass, '#PRTable');
},
columns: colOnPRTable, columns: colOnPRTable,
order: [[3, 'asc']], order: [[0, 'asc']],
rowCallback: rowStatusColorCallback, rowCallback: rowStatusColorCallback,
responsive: true,
language: { language: {
emptyTable: "No record available", emptyTable: "No record available",
loadingRecords: "Loading data...", processing: "Loading…"
processing: "Processing..."
}, },
error: errorHandler error: errorHandler
}); });
function debounce(fn, delay) {
let t;
return function (...args) {
clearTimeout(t);
t = setTimeout(() => fn.apply(this, args), delay);
};
}
$('#srchPRNo, #srchItem, #srchDept')
.on('keyup', debounce(function () {
prTable.ajax.reload();
}, 400));
$('.pr-search-clear').on('click', function () {
const target = $(this).data('target');
$(target).val('');
prTable.ajax.reload();
}); });
});
</script> </script>

View File

@ -1,4 +1,74 @@
<div class="tab-content-wrapper"> <div class="tab-content-wrapper">
<div class="d-flex flex-wrap gap-2 mb-2">
<!-- Status dropdown -->
<div class="search-wrapper" style="min-width:170px; flex:1;">
<div class="search-inner" style="padding-right:4px;">
<span class="search-icon">
<svg width="14" height="14" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2.5">
<path d="M3 6h18M7 12h10M11 18h2" />
</svg>
</span>
<select id="srchStatus" class="search-input" style="cursor:pointer;">
<option value="">All Statuses</option>
<!-- populated dynamically from first API response -->
</select>
</div>
</div>
<!-- PR No -->
<div class="search-wrapper" style="min-width:150px; flex:1;">
<div class="search-inner">
<span class="search-icon">
<svg width="14" height="14" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2.5">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.35-4.35" />
</svg>
</span>
<input id="srchPRNo" class="search-input"
placeholder="PR Number…" autocomplete="off" />
<span class="search-clear aprv-search-clear"
data-target="#srchPRNo" title="Clear">✕</span>
</div>
</div>
<!-- Item Name -->
<div class="search-wrapper" style="min-width:200px; flex:2;">
<div class="search-inner">
<span class="search-icon">
<svg width="14" height="14" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2.5">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.35-4.35" />
</svg>
</span>
<input id="srchItem" class="search-input"
placeholder="Item Name…" autocomplete="off" />
<span class="search-clear aprv-search-clear"
data-target="#srchItem" title="Clear">✕</span>
</div>
</div>
<!-- Department -->
<div class="search-wrapper" style="min-width:170px; flex:1;">
<div class="search-inner">
<span class="search-icon">
<svg width="14" height="14" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2.5">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.35-4.35" />
</svg>
</span>
<input id="srchDept" class="search-input"
placeholder="Department…" autocomplete="off" />
<span class="search-clear aprv-search-clear"
data-target="#srchDept" title="Clear">✕</span>
</div>
</div>
</div>
<table id="ApprovedPRTable" class="row-border" cellspacing="0" width="100%"> <table id="ApprovedPRTable" class="row-border" cellspacing="0" width="100%">
<thead> <thead>
<tr> <tr>
@ -23,144 +93,88 @@
</div> </div>
<script> <script>
$(document).ready(function() { $(document).ready(function () {
const reportTitle = `Approved Purchase Request (No PO) - as of ${getFormattedDateTime()}`; const reportTitle = `Approved Purchase Request (No PO) - as of ${getFormattedDateTime()}`;
var approvedPRTable = $('#ApprovedPRTable').DataTable({ var approvedPRTable = $('#ApprovedPRTable').DataTable({
serverSide: true,
processing: true,
searching: false,
ajax: { ajax: {
url: '/PRMgmt/GetApprovedPR', url: endpoint.GetApprovedPR,
type: 'GET', type: 'GET',
beforeSend: function () { data: function (d) {
loader.show(); return {
draw: d.draw,
searchPRNo: ($('#srchPRNo').val() || '').trim(),
searchItemName: ($('#srchItem').val() || '').trim(),
searchDept: ($('#srchDept').val() || '').trim(),
searchStatusName: ($('#srchStatus').val() || '').trim(),
pageNumber: Math.floor(d.start / d.length) + 1,
pageSize: d.length
};
}, },
complete: function () { dataSrc: function (json) {
loader.hide(); const $sel = $('#srchStatus');
}, if ($sel.find('option').length <= 1 && json.statusList?.length) {
error: function (xhr, error, thrown) { json.statusList.forEach(function (s) {
loader.hide(); $sel.append(`<option value="${s}">${s}</option>`);
console.error('Error loading data:', error); });
if (typeof toastr !== 'undefined') {
toastr.error('Failed to load data. Please try again.');
} }
return json.data;
},
beforeSend: function () { loader.show(); },
complete: function () { loader.hide(); },
error: function (xhr, error) {
loader.hide();
if (typeof toastr !== 'undefined')
toastr.error('Failed to load data. Please try again.');
} }
}, },
dom: 'lBfrtip', dom: 'lBfrtip',
buttons: [ buttons: [
{ {
extend: 'csv', text: '<i class="fas fa-file-csv"></i> CSV',
title: reportTitle className: 'btn btn-sm btn-secondary',
action: function () { exportAllData('csv', endpoint.GetApprovedPR, loader, reportTitle, colOnApprovedPR); }
}, },
{ {
extend: 'excel', text: '<i class="fas fa-file-excel"></i> Excel',
title: reportTitle className: 'btn btn-sm btn-success',
action: function () { exportAllData('excel', endpoint.GetApprovedPR, loader, reportTitle, colOnApprovedPR); }
}, },
{ {
extend: 'pdf', text: '<i class="fas fa-file-pdf"></i> PDF',
title: reportTitle className: 'btn btn-sm btn-danger',
action: function () { exportAllData('pdf', endpoint.GetApprovedPR, loader, reportTitle, colOnApprovedPR); }
} }
], ],
initComplete: function () { columns: colOnApprovedPR,
initializeColumnSearch({
tableId: '#ApprovedPRTable',
dataTable: approvedPRTable,
searchableColumns: [
{
columnIndex: 0,
columnName: 'Status',
placeholder: 'Select Status...',
searchType: 'select',
searchMode: 'exact',
width: '150px'
},
{
columnIndex: 2,
columnName: 'PR No',
placeholder: 'Enter PR Number...',
searchType: 'text',
searchMode: 'exact',
width: '150px'
},
{
columnIndex: 3,
columnName: 'Item No',
placeholder: 'Enter Item Number...',
searchType: 'text',
searchMode: 'exact',
width: '150px'
},
{
columnIndex: 4,
columnName: 'Item Name',
placeholder: 'Enter Item Name...',
searchType: 'text',
searchMode: 'contains',
width: '200px'
},
{
columnIndex: 12,
columnName: 'Department',
placeholder: 'Select Department...',
searchType: 'select',
searchMode: 'exact',
width: '150px'
}
]
});
const uniqueSearchClass = 'column-search-input-ApprovedPRTable';
populateSelectOptions(approvedPRTable, 0, '.' + uniqueSearchClass + '[data-column="0"]');
populateSelectOptions(approvedPRTable, 12, '.' + uniqueSearchClass + '[data-column="12"]');
restoreSearchFromURL(uniqueSearchClass, '#ApprovedPRTable');
},
columns: [
{ data: 'statusName' },
{ data: 'remainingDays', searchable: false },
{ data: 'prNo' },
{ data: 'itemNo' },
{ data: 'itemName' },
{
searchable: false,
data: 'qty',
render: function (data) {
return numberWithCommas(data);
}
},
{ data: 'createdBy' },
{
searchable: false,
data: 'createdDate',
render: function (data) {
return formatDateTime(data);
}
},
{ data: 'attestedBy' },
{
searchable: false,
data: 'attestedDate',
render: function (data) {
return formatStrDateTime(data);
}
},
{ data: 'approvedBy' },
{
searchable: false,
data: 'approvedDate',
render: function (data) {
return formatStrDateTime(data);
}
},
{ data: 'department' }
],
order: [[11, 'asc']], order: [[11, 'asc']],
responsive: true, responsive: true,
language: { language: { emptyTable: "No approved records available" }
emptyTable: "No approved records available" });
function debounce(fn, delay) {
let t;
return function (...args) {
clearTimeout(t);
t = setTimeout(() => fn.apply(this, args), delay);
};
} }
$('#srchPRNo, #srchItem, #srchDept')
.on('keyup', debounce(() => approvedPRTable.ajax.reload(), 400));
$('#srchStatus').on('change', function () {
approvedPRTable.ajax.reload();
});
$('.aprv-search-clear').on('click', function () {
$($(this).data('target')).val('').trigger('change');
}); });
}); });
function formatStrDateTime(dateString) { function formatStrDateTime(dateString) {
if (!dateString || dateString === "None") return "None"; if (!dateString || dateString === "None") return "None";
let date = new Date(dateString); let date = new Date(dateString);

View File

@ -0,0 +1,163 @@
<div class="tab-content-wrapper">
<div class="d-flex flex-wrap gap-2 mb-2">
<div class="search-wrapper" style="min-width:180px; flex:1;">
<div class="search-inner">
<span class="search-icon">
<svg width="14" height="14" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2.5">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.35-4.35" />
</svg>
</span>
<input id="srchPRNo" class="search-input"
placeholder="PR Number…" autocomplete="off" />
<span class="search-clear pr-search-clear"
data-target="#srchPRNo" title="Clear">✕</span>
</div>
</div>
<div class="search-wrapper" style="min-width:220px; flex:2;">
<div class="search-inner">
<span class="search-icon">
<svg width="14" height="14" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2.5">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.35-4.35" />
</svg>
</span>
<input id="srchItem" class="search-input"
placeholder="Item Name…" autocomplete="off" />
<span class="search-clear pr-search-clear"
data-target="#srchItem" title="Clear">✕</span>
</div>
</div>
<div class="search-wrapper" style="min-width:180px; flex:1;">
<div class="search-inner">
<span class="search-icon">
<svg width="14" height="14" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2.5">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.35-4.35" />
</svg>
</span>
<input id="srchDept" class="search-input"
placeholder="Department…" autocomplete="off" />
<span class="search-clear pr-search-clear"
data-target="#srchDept" title="Clear">✕</span>
</div>
</div>
</div>
<table id="DeletedPRTable" class="row-border" cellspacing="0" width="100%">
<thead>
<tr>
<th>PRNo</th>
<th>ItemNo</th>
<th>ItemName</th>
<th>Qty</th>
<th>PR By</th>
<th>PR Date</th>
<th>Attested By</th>
<th>Attested Date</th>
<th>Approved By</th>
<th>Approved Date</th>
<th>Charge To</th>
<th>Remarks</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<script>
$(document).ready(function () {
const reportTitle = `Deleted Purchase Request - as of ${getFormattedDateTime()}`;
var deletedPRTable = $('#DeletedPRTable').DataTable({
serverSide: true,
processing: true,
searching: false,
ajax: {
url: endpoint.GetDeletedPR,
type: 'GET',
data: function (d) {
return {
draw: d.draw,
searchPRNo: ($('#srchPRNo').val() || '').trim(),
searchItemName:($('#srchItem').val()|| '').trim(),
searchDept: ($('#srchDept').val() || '').trim(),
pageNumber: Math.floor(d.start / d.length) + 1,
pageSize: d.length
};
},
dataSrc: function (json) { return json.data; },
beforeSend: function () { loader.show(); },
complete: function () { loader.hide(); },
error: function (xhr, error) {
loader.hide();
console.error('Error loading data:', error);
if (typeof toastr !== 'undefined')
toastr.error('Failed to load data. Please try again.');
}
},
dom: 'lBfrtip',
buttons: [
{
text: '<i class="fas fa-file-csv"></i> CSV',
className: 'btn btn-sm btn-secondary',
action: function () { exportAllData('csv', endpoint.GetDeletedPR, loader, reportTitle, colOnDeletedPR); }
},
{
text: '<i class="fas fa-file-excel"></i> Excel',
className: 'btn btn-sm btn-success',
action: function () { exportAllData('excel', endpoint.GetDeletedPR, loader, reportTitle, colOnDeletedPR); }
},
{
text: '<i class="fas fa-file-pdf"></i> PDF',
className: 'btn btn-sm btn-danger',
action: function () { exportAllData('pdf', endpoint.GetDeletedPR, loader, reportTitle, colOnDeletedPR); }
}
],
columns: colOnDeletedPR,
rowCallback: rowStatusColorCallback,
responsive: true,
language: {
emptyTable: "No record available",
processing: "Loading…"
},
error: errorHandler
});
function debounce(fn, delay) {
let t;
return function (...args) {
clearTimeout(t);
t = setTimeout(() => fn.apply(this, args), delay);
};
}
$('#srchPRNo, #srchItem, #srchDept')
.on('keyup', debounce(() => deletedPRTable.ajax.reload(), 400));
$('.pr-search-clear').on('click', function () {
const target = $(this).data('target');
$(target).val('');
deletedPRTable.ajax.reload();
});
});
function formatStrDateTime(dateString) {
if (!dateString || dateString === "None") return "None";
let date = new Date(dateString);
let month = ('0' + (date.getMonth() + 1)).slice(-2);
let day = ('0' + date.getDate()).slice(-2);
let year = date.getFullYear();
let hours = ('0' + date.getHours()).slice(-2);
let minutes = ('0' + date.getMinutes()).slice(-2);
let seconds = ('0' + date.getSeconds()).slice(-2);
return `${month}/${day}/${year} ${hours}:${minutes}:${seconds}`;
}
</script>

View File

@ -1,164 +0,0 @@
<div class="tab-content-wrapper">
<table id="RemovedPRTable" class="row-border" cellspacing="0" width="100%">
<thead>
<tr>
<th>PRNo</th>
<th>ItemNo</th>
<th>ItemName</th>
<th>Qty</th>
<th>PR By</th>
<th>PR Date</th>
<th>Attested By</th>
<th>Attested Date</th>
<th>Approved By</th>
<th>Approved Date</th>
<th>Charge To</th>
<th>Remarks</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<script>
$(document).ready(function() {
const reportTitle = `Deleted Purchase Request - as of ${getFormattedDateTime()}`;
var removedPRTable = $('#RemovedPRTable').DataTable({
ajax: {
url: '/PRMgmt/GetApprovedPR',
type: 'GET',
beforeSend: function () {
loader.show();
},
complete: function () {
loader.hide();
},
error: function (xhr, error, thrown) {
loader.hide();
console.error('Error loading data:', error);
if (typeof toastr !== 'undefined') {
toastr.error('Failed to load data. Please try again.');
}
}
},
dom: 'lBfrtip',
buttons: [
{
extend: 'csv',
title: reportTitle
},
{
extend: 'excel',
title: reportTitle
},
{
extend: 'pdf',
title: reportTitle
}
],
initComplete: function () {
initializeColumnSearch({
tableId: '#RemovedPRTable',
dataTable: removedPRTable,
searchableColumns: [
{
columnIndex: 0,
columnName: 'PR No',
placeholder: 'Enter PR Number...',
searchType: 'text',
searchMode: 'exact',
width: '150px'
},
{
columnIndex: 1,
columnName: 'Item No',
placeholder: 'Enter Item Number...',
searchType: 'text',
searchMode: 'exact',
width: '150px'
},
{
columnIndex: 2,
columnName: 'Item Name',
placeholder: 'Enter Item Name...',
searchType: 'text',
searchMode: 'contains',
width: '200px'
},
{
columnIndex: 10,
columnName: 'Department',
placeholder: 'Select Department...',
searchType: 'select',
searchMode: 'exact',
width: '150px'
}
]
});
const uniqueSearchClass = 'column-search-input-RemovedPRTable';
populateSelectOptions(removedPRTable, 10, '.' + uniqueSearchClass + '[data-column="10"]');
restoreSearchFromURL(uniqueSearchClass, '#RemovedPRTable');
},
columns: [
{ data: 'prNo' },
{ data: 'itemNo' },
{ data: 'itemName' },
{
searchable: false,
data: 'qty',
render: function (data) {
return numberWithCommas(data);
}
},
{ data: 'createdBy' },
{
searchable: false,
data: 'createdDate',
render: function (data) {
return formatDateTime(data);
}
},
{ data: 'attestedBy' },
{
searchable: false,
data: 'attestedDate',
render: function (data) {
return formatStrDateTime(data);
}
},
{ data: 'approvedBy' },
{
searchable: false,
data: 'approvedDate',
render: function (data) {
return formatStrDateTime(data);
}
},
{ data: 'department' },
{ data: 'remarks' }
],
order: [[9, 'asc']],
responsive: true,
language: {
emptyTable: "No approved records available"
}
});
});
function formatStrDateTime(dateString) {
if (!dateString || dateString === "None") return "None";
let date = new Date(dateString);
let month = ('0' + (date.getMonth() + 1)).slice(-2);
let day = ('0' + date.getDate()).slice(-2);
let year = date.getFullYear();
let hours = ('0' + date.getHours()).slice(-2);
let minutes = ('0' + date.getMinutes()).slice(-2);
let seconds = ('0' + date.getSeconds()).slice(-2);
return `${month}/${day}/${year} ${hours}:${minutes}:${seconds}`;
}
</script>

View File

@ -10,6 +10,29 @@
Add new Add new
</button> </button>
<br /> <br />
<!-- Replace your existing customSearch input with this -->
<div class="search-wrapper">
<div class="search-inner">
<span class="search-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"
viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.35-4.35" />
</svg>
</span>
<input id="customSearch" type="text"
class="search-input"
placeholder="Search by item name, description or category…"
autocomplete="off" />
<span class="search-clear" id="searchClear" title="Clear">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="none"
viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path d="M18 6 6 18M6 6l12 12" />
</svg>
</span>
</div>
<div class="search-hint" id="searchHint">Start typing to search items…</div>
</div>
<table id="ItemTable" class="row-border" cellspacing="0" width="100%"> <table id="ItemTable" class="row-border" cellspacing="0" width="100%">
<thead> <thead>
<tr> <tr>
@ -18,8 +41,6 @@
<th>ItemSpecs</th> <th>ItemSpecs</th>
<th>CategoryName</th> <th>CategoryName</th>
<th>Action</th> <th>Action</th>
<th hidden></th>
<th hidden></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -237,7 +258,7 @@
</div> </div>
</div> </div>
</div> </div>
<script src="~/JsFunctions/Items/ItemManagementV7.js"></script> <script src="~/JsFunctions/Items/ItemManagementV8.js"></script>
@await Html.PartialAsync("PagesView/Item/_Scripts") @await Html.PartialAsync("PagesView/Item/_Scripts")
</div> </div>

View File

@ -17,8 +17,6 @@
<th>ItemCategory</th> <th>ItemCategory</th>
<th>SupplierName</th> <th>SupplierName</th>
<th>Action</th> <th>Action</th>
<th hidden></th>
<th hidden></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View File

@ -33,6 +33,30 @@
<h2 class="modal-title" id="addItemLabel">Item List</h2> <h2 class="modal-title" id="addItemLabel">Item List</h2>
</div> </div>
<div class="modal-body p-4"> <div class="modal-body p-4">
<!-- Inside #viewItemList modal, above the table -->
<div class="search-wrapper mb-2">
<div class="search-inner">
<span class="search-icon">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none"
viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.35-4.35" />
</svg>
</span>
<input id="modalSearchInput" type="text"
class="search-input"
placeholder="Search by item name, description or category…"
autocomplete="off" />
<span class="search-clear" id="modalSearchClear" title="Clear">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="none"
viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path d="M18 6 6 18M6 6l12 12" />
</svg>
</span>
</div>
<div class="search-hint" id="modalSearchHint">Start typing to search items…</div>
</div>
<div style="margin-bottom:5px"> <div style="margin-bottom:5px">
<span class="fw-semibold">Selected Items:</span> <span class="fw-semibold">Selected Items:</span>
<span id="totalSelectedItem" class="badge bg-danger ms-2">0</span> <span id="totalSelectedItem" class="badge bg-danger ms-2">0</span>
@ -116,7 +140,7 @@
<link href="~/css/pr/ButtonStyleV2.css" rel="stylesheet" /> <link href="~/css/pr/ButtonStyleV2.css" rel="stylesheet" />
<link href="~/css/pr/PRTabs.css" rel="stylesheet" /> <link href="~/css/pr/PRTabs.css" rel="stylesheet" />
@await Html.PartialAsync("PagesView/PR/_PRTracking") @await Html.PartialAsync("PagesView/PR/_PRTracking")
<script src="~/JsFunctions/PR/PRV9.js"></script> <script src="~/JsFunctions/PR/PR.js"></script>
<script src="~/JsFunctions/PR/PRTabs.js"></script> <script src="~/JsFunctions/PR/PRTabs.js"></script>
@await Html.PartialAsync("PagesView/PR/_PRScripts") @await Html.PartialAsync("PagesView/PR/_PRScripts")
</body> </body>

View File

@ -5,7 +5,55 @@
<h2 style="display: flex; flex-direction: column; align-items: center;">PR Archived</h2> <h2 style="display: flex; flex-direction: column; align-items: center;">PR Archived</h2>
</div> </div>
<br /> <br />
<br /> <div class="d-flex flex-wrap gap-2 mb-2">
<div class="search-wrapper" style="min-width:180px; flex:1;">
<div class="search-inner">
<span class="search-icon">
<svg width="14" height="14" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2.5">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.35-4.35" />
</svg>
</span>
<input id="srchPRNo" class="search-input"
placeholder="PR Number…" autocomplete="off" />
<span class="search-clear pr-search-clear"
data-target="#srchPRNo" title="Clear">✕</span>
</div>
</div>
<div class="search-wrapper" style="min-width:220px; flex:2;">
<div class="search-inner">
<span class="search-icon">
<svg width="14" height="14" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2.5">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.35-4.35" />
</svg>
</span>
<input id="srchItemName" class="search-input"
placeholder="Item Name…" autocomplete="off" />
<span class="search-clear pr-search-clear"
data-target="#srchItemName" title="Clear">✕</span>
</div>
</div>
<div class="search-wrapper" style="min-width:180px; flex:1;">
<div class="search-inner">
<span class="search-icon">
<svg width="14" height="14" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2.5">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.35-4.35" />
</svg>
</span>
<input id="srchDept" class="search-input"
placeholder="Department…" autocomplete="off" />
<span class="search-clear pr-search-clear"
data-target="#srchDept" title="Clear">✕</span>
</div>
</div>
</div>
<table id="PRTable" class="row-border" cellspacing="0" width="100%"> <table id="PRTable" class="row-border" cellspacing="0" width="100%">
<thead> <thead>
<tr> <tr>
@ -17,8 +65,6 @@
<th style="width:8%">DateNeeded</th> <th style="width:8%">DateNeeded</th>
<th style="width:7%">ChargeTo</th> <th style="width:7%">ChargeTo</th>
<th style="width:7%">Action</th> <th style="width:7%">Action</th>
<th hidden></th>
<th hidden></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -27,7 +73,8 @@
</div> </div>
</div> </div>
<link href="~/css/pr/TrackingV3.css" rel="stylesheet" /> <link href="~/css/pr/TrackingV3.css" rel="stylesheet" />
<link href="~/css/item/item.css" rel="stylesheet" />
@await Html.PartialAsync("PagesView/PR/_PRTracking") @await Html.PartialAsync("PagesView/PR/_PRTracking")
<script src="~/JsFunctions/PR/ArchivedV2.js"></script> <script src="~/JsFunctions/PR/ArchivedV3.js"></script>
@await Html.PartialAsync("PagesView/PR/_PRScripts") @await Html.PartialAsync("PagesView/PR/_PRScripts")
</body> </body>

View File

@ -5,6 +5,76 @@
</div> </div>
<div class="table-container shadow-lg p-3 mb-3 bg-white rounded" style="overflow-x: auto;height:110%"> <div class="table-container shadow-lg p-3 mb-3 bg-white rounded" style="overflow-x: auto;height:110%">
<br /> <br />
<div class="d-flex flex-wrap gap-2 mb-2">
<!-- Status dropdown -->
<div class="search-wrapper" style="min-width:170px; flex:1;">
<div class="search-inner" style="padding-right:4px;">
<span class="search-icon">
<svg width="14" height="14" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2.5">
<path d="M3 6h18M7 12h10M11 18h2" />
</svg>
</span>
<select id="srchStatus" class="search-input" style="cursor:pointer;">
<option value="">All Statuses</option>
<!-- populated dynamically from first API response -->
</select>
</div>
</div>
<!-- PR No -->
<div class="search-wrapper" style="min-width:150px; flex:1;">
<div class="search-inner">
<span class="search-icon">
<svg width="14" height="14" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2.5">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.35-4.35" />
</svg>
</span>
<input id="srchPRNo" class="search-input"
placeholder="PR Number…" autocomplete="off" />
<span class="search-clear aprv-search-clear"
data-target="#srchPRNo" title="Clear">✕</span>
</div>
</div>
<!-- Item Name -->
<div class="search-wrapper" style="min-width:200px; flex:2;">
<div class="search-inner">
<span class="search-icon">
<svg width="14" height="14" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2.5">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.35-4.35" />
</svg>
</span>
<input id="srchItem" class="search-input"
placeholder="Item Name…" autocomplete="off" />
<span class="search-clear aprv-search-clear"
data-target="#srchItem" title="Clear">✕</span>
</div>
</div>
<!-- Department -->
<div class="search-wrapper" style="min-width:170px; flex:1;">
<div class="search-inner">
<span class="search-icon">
<svg width="14" height="14" fill="none" viewBox="0 0 24 24"
stroke="currentColor" stroke-width="2.5">
<circle cx="11" cy="11" r="8" />
<path d="m21 21-4.35-4.35" />
</svg>
</span>
<input id="srchDept" class="search-input"
placeholder="Department…" autocomplete="off" />
<span class="search-clear aprv-search-clear"
data-target="#srchDept" title="Clear">✕</span>
</div>
</div>
</div>
<table id="PRTable" class="row-border" width="100%"> <table id="PRTable" class="row-border" width="100%">
<thead> <thead>
<tr> <tr>
@ -45,6 +115,6 @@
</div> </div>
</div> </div>
<link href="~/css/pr/trackingdetail.css" rel="stylesheet" /> <link href="~/css/pr/trackingdetail.css" rel="stylesheet" />
<script src="~/jsfunctions/pr/DetailedPRTrackingV7.js"></script> <script src="~/jsfunctions/pr/DetailedPRTrackingV8.js"></script>
@await Html.PartialAsync("PagesView/PR/_PRScripts") @await Html.PartialAsync("PagesView/PR/_PRScripts")
</body> </body>

View File

@ -1,4 +1,5 @@
<input hidden id="roleRights" value="@ViewBag.UserRoles" /> <link href="~/css/item/item.css" rel="stylesheet" />
<input hidden id="roleRights" value="@ViewBag.UserRoles" />
<div id="overlay" class="overlay" style="display: none;"> <div id="overlay" class="overlay" style="display: none;">
<div id="loader" class="loader"></div> <div id="loader" class="loader"></div>
</div> </div>

View File

@ -18,7 +18,7 @@
<script src="~/jsfunctions/po/POButtonV9.js"></script> <script src="~/jsfunctions/po/POButtonV9.js"></script>
<script src="~/jsfunctions/po/PrintingV2.js"></script> <script src="~/jsfunctions/po/PrintingV2.js"></script>
<script src="~/JsFunctions/PO/POColumn.js"></script> <script src="~/JsFunctions/PO/POColumn.js"></script>
<script src="~/jsfunctions/po/ApiV4.js"></script> <script src="~/jsfunctions/po/ApiV5.js"></script>
<script src="~/jsfunctions/po/populatetable.js"></script> <script src="~/jsfunctions/po/populatetable.js"></script>
<script src="~/jsfunctions/po/POVarV6.js"></script> <script src="~/jsfunctions/po/POVarV6.js"></script>
<script src="~/jsfunctions/po/POViewV4.js"></script> <script src="~/jsfunctions/po/POViewV4.js"></script>

View File

@ -1,20 +1,22 @@
<div id="overlay" class="overlay" style="display: none;"> <div id="overlay" class="overlay" style="display: none;">
<div id="loader" class="loader"></div> <div id="loader" class="loader"></div>
</div> </div>
<link href="~/css/item/item.css" rel="stylesheet" />
<link href="~/css/common/rowhighlighter.css" rel="stylesheet" /> <link href="~/css/common/rowhighlighter.css" rel="stylesheet" />
<script src="~/jsfunctions/pr/PRColumnV8.js"></script> <script src="~/jsfunctions/pr/PRColumnV9.js"></script>
<script src="~/jsfunctions/pr/PRViewV8.js"></script> <script src="~/jsfunctions/pr/PRViewV9.js"></script>
<script src="~/jsfunctions/pr/PRPostPut.js"></script> <script src="~/jsfunctions/pr/PRPostPut.js"></script>
<script src="~/jsfunctions/pr/PRButtonv3.js"></script> <script src="~/jsfunctions/pr/PRButtonv3.js"></script>
<script src="~/jsfunctions/pr/PRVarV3.js"></script> <script src="~/jsfunctions/pr/PRVarV3.js"></script>
<script src="~/jsfunctions/pr/Configv6.js"></script> <script src="~/jsfunctions/pr/ConfigV7.js"></script>
<script src="~/jsfunctions/pr/populatedropdown.js"></script> <script src="~/jsfunctions/pr/populatedropdown.js"></script>
<script src="~/jsfunctions/pr/prRowCallbackV3.js"></script> <script src="~/jsfunctions/pr/prRowCallbackV3.js"></script>
<script src="~/jsfunctions/utilities/columnstyle.js"></script> <script src="~/jsfunctions/utilities/columnstyle.js"></script>
<script src="~/jsfunctions/tracking/tracking.js"></script> <script src="~/jsfunctions/tracking/tracking.js"></script>
<script src="~/jsfunctions/utilities/NewStyle.js"></script> <script src="~/jsfunctions/utilities/NewStyle.js"></script>
<script src="~/jsfunctions/utilities/exportcsvexcel.js"></script>
<script src="~/jsfunctions/utilities/utilsV3.js"></script> <script src="~/jsfunctions/utilities/utilsV3.js"></script>
<script src="~/jsfunctions/utilities/StylesV3.js"></script> <script src="~/jsfunctions/utilities/StylesV3.js"></script>
<script src="~/jsfunctions/utilities/searchengine.js"></script> <script src="~/jsfunctions/utilities/searchengine.js"></script>

View File

@ -1,5 +1,4 @@
<link href="~/css/pr/receiving.css" rel="stylesheet" />  <!-- Modal viewPRItemDetails -->
<!-- Modal viewPRItemDetails -->
<div class="modal fade custom-modal-backdrop" id="viewPRItemDetails" <div class="modal fade custom-modal-backdrop" id="viewPRItemDetails"
tabindex="-1" aria-labelledby="ModalLabel" aria-hidden="true"> tabindex="-1" aria-labelledby="ModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl"> <div class="modal-dialog modal-xl">

View File

@ -41,18 +41,32 @@ function isFullFilled() {
$(document).ready(function () { $(document).ready(function () {
loader = $('#overlay, #loader'); loader = $('#overlay, #loader');
let fetchedData = [];
let hasFetched = false; // ensures only one fetch per page load
let fetchInProgress = false; // prevents multiple calls before first finishes
let cartItemCount = $('#cartItemCount').val(); let cartItemCount = $('#cartItemCount').val();
$('#cartCount').text(cartItemCount); $('#cartCount').text(cartItemCount);
itemTable = $('#ItemTable').DataTable({ itemTable = $('#ItemTable').DataTable({
serverSide: false, serverSide: true,
processing: true, processing: true,
deferLoading: 0, searching: false, // disable built-in search box (we drive it manually)
data: [],
ajax: {
url: '/ItemMgmt/GetItemList',
type: 'GET',
data: function (d) {
var searchVal = $('#customSearch').length ? $('#customSearch').val() : '';
return {
draw: d.draw,
searchTerm: (searchVal || '').trim(),
pageNumber: Math.floor(d.start / d.length) + 1,
pageSize: d.length
};
},
dataSrc: function (json) {
return json.data; // ← extract data array here
}
},
columns: [ columns: [
{ data: 'itemNo' }, { data: 'itemNo' },
{ data: 'itemName' }, { data: 'itemName' },
@ -63,69 +77,34 @@ $(document).ready(function () {
render: function (data, type, row) { render: function (data, type, row) {
return renderItembtns(data, row); return renderItembtns(data, row);
} }
}, }
{ data: 'cartItemCount', visible: false },
{ data: 'createdDate', visible: false },
{ data: 'requestTypeId', visible: false }
], ],
order: [[0, 'desc']], order: [[0, 'desc']],
responsive: true, responsive: true,
language: { language: {
emptyTable: "Type in the search box to fetch data." emptyTable: "Type in the search box to fetch data."
} }
}); });
// Simple debounce helper // Debounce helper
function debounce(func, delay) { function debounce(fn, delay) {
let timer; let t;
return function (...args) { return function (...args) {
clearTimeout(timer); clearTimeout(t);
timer = setTimeout(() => func.apply(this, args), delay); t = setTimeout(() => fn.apply(this, args), delay);
}; };
} }
// Search handler // Custom search box
$('#ItemTable_filter input').unbind().on('keyup', debounce(function () { $('#customSearch').on('keyup', debounce(function () {
let searchValue = $(this).val().trim(); if ($(this).val().trim().length === 0) {
itemTable.clear().draw(); // show nothing until user types
if (searchValue.length === 0) {
itemTable.clear().draw();
return; return;
} }
itemTable.ajax.reload(); // triggers new server call with searchTerm
if (!hasFetched && !fetchInProgress) { }, 400));
// Fetch only once on first user input
fetchInProgress = true;
loader.show();
$.ajax({
url: '/ItemMgmt/GetItemList',
type: 'GET',
success: function (response) {
fetchedData = response.data || [];
itemTable.clear().rows.add(fetchedData).draw();
hasFetched = true; // mark as fetched
},
complete: function () {
loader.hide();
fetchInProgress = false;
},
error: function (xhr, error) {
console.log('Error fetching data:', error);
}
});
} else if (hasFetched) {
// Local filtering only
let filteredData = fetchedData.filter(function (item) {
return (
item.itemCodeId.toString().includes(searchValue) ||
item.itemName.toLowerCase().includes(searchValue.toLowerCase()) ||
item.itemDescription.toLowerCase().includes(searchValue.toLowerCase()) ||
item.itemCategoryName.toLowerCase().includes(searchValue.toLowerCase())
);
});
itemTable.clear().rows.add(filteredData).draw();
}
}, 300)); // debounce delay in ms
}); });
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
const triggers = Array.from(document.querySelectorAll('[data-bs-toggle="tooltip"]')); const triggers = Array.from(document.querySelectorAll('[data-bs-toggle="tooltip"]'));

View File

@ -1,42 +0,0 @@
$(document).ready(function () {
loader = $('#overlay, #loader');
UserRights = document.getElementById("roleRights").value;
let IsArchived = true;
prTable = $('#PRTable').DataTable({
ajax: $.extend({
url: '/PRMgmt/GetAllPR',
type: 'GET',
data: { IsArchived }
}, beforeComplete(loader)),
initComplete: initCompleteCallback,
columns: colOnPRTable,
order: [10, 'asc'],
rowCallback: rowStatusColorCallback,
responsive: true,
language: {
emptyTable: "No record available"
},
error: errorHandler
});
})
$(document).on('click', '.toggle-btn', function () {
var $button = $(this);
var $span = $button.prev('.item-display');
var isExpanded = $button.data('expanded');
if (!isExpanded) {
// Show the full content
var fullItems = decodeURIComponent($button.attr('data-full-items'));
$span.html(fullItems);
$button.text('...see less');
} else {
// Show the short content
var shortItems = decodeURIComponent($button.attr('data-short-items'));
$span.html(shortItems);
$button.text('...see more');
}
// Toggle the expanded state
$button.data('expanded', !isExpanded);
});

View File

@ -0,0 +1,80 @@

$(document).ready(function () {
loader = $('#overlay, #loader');
prTable = $('#PRTable').DataTable({
serverSide: true,
processing: true,
searching: false,
ajax: {
url: '/PRMgmt/GetPRArchived',
type: 'GET',
data: function (d) {
return {
draw: d.draw,
searchPRNo: ($('#srchPRNo').val() || '').trim(),
searchItemName: ($('#srchItemName').val() || '').trim(),
searchDept: ($('#srchDept').val() || '').trim(),
pageNumber: Math.floor(d.start / d.length) + 1,
pageSize: d.length
};
},
dataSrc: function (json) { return json.data; },
beforeSend: function () { loader.show(); },
complete: function () { loader.hide(); },
error: function (xhr, error) {
loader.hide();
console.error('Error loading data:', error);
if (typeof toastr !== 'undefined')
toastr.error('Failed to load data. Please try again.');
}
},
columns: colOnPRTable,
order: [[0, 'asc']],
language: {
emptyTable: "No record available",
processing: "Loading…"
},
error: errorHandler
});
function debounce(fn, delay) {
let t;
return function (...args) {
clearTimeout(t);
t = setTimeout(() => fn.apply(this, args), delay);
};
}
$('#srchPRNo, #srchItemName, #srchDept')
.on('keyup', debounce(function () {
prTable.ajax.reload();
}, 400));
$('.pr-search-clear').on('click', function () {
const target = $(this).data('target');
$(target).val('');
prTable.ajax.reload();
});
});
$(document).on('click', '.toggle-btn', function () {
var $button = $(this);
var $span = $button.prev('.item-display');
var isExpanded = $button.data('expanded');
if (!isExpanded) {
// Show the full content
var fullItems = decodeURIComponent($button.attr('data-full-items'));
$span.html(fullItems);
$button.text('...see less');
} else {
// Show the short content
var shortItems = decodeURIComponent($button.attr('data-short-items'));
$span.html(shortItems);
$button.text('...see more');
}
// Toggle the expanded state
$button.data('expanded', !isExpanded);
});

View File

@ -3,10 +3,14 @@
GetItemColor: '/ItemMgmt/GetItemColor', GetItemColor: '/ItemMgmt/GetItemColor',
GetItemLocalization: '/ItemMgmt/GetItemLocalization', GetItemLocalization: '/ItemMgmt/GetItemLocalization',
GetItemUOM: '/ItemMgmt/GetItemUOM', GetItemUOM: '/ItemMgmt/GetItemUOM',
GetAllPR: '/PRMgmt/GetAllPR',
GetApprovedPR: '/PRMgmt/GetApprovedPR',
GetDeletedPR: '/PRMgmt/GetDeletedPR',
GetProjectCodes: '/PRMgmt/GetProjectCodes', GetProjectCodes: '/PRMgmt/GetProjectCodes',
GetSupplierAlternativeOffer: '/PRMgmt/GetSupplierAlternativeOffer', GetSupplierAlternativeOffer: '/PRMgmt/GetSupplierAlternativeOffer',
GetSupplierAlterOfferDetails: '/PRMgmt/GetSupplierAlterOfferDetails', GetSupplierAlterOfferDetails: '/PRMgmt/GetSupplierAlterOfferDetails',
GetDetailedPRTracking: '/PRMgmt/GetDetailedPRTracking',
PutSupplierAlterOffer: '/PRMgmt/PutSupplierAlterOffer', PutSupplierAlterOffer: '/PRMgmt/PutSupplierAlterOffer',
PostPutProjectCode: '/PRMgmt/PostPutProjectCode', PostPutProjectCode: '/PRMgmt/PostPutProjectCode',

View File

@ -1,106 +0,0 @@
$(document).ready(function () {
loader = $('#overlay, #loader');
const reportTitle = `PR Tracking - as of ${getFormattedDateTime()}`;
prTable = $('#PRTable').DataTable({
ajax: $.extend({
url: '/PRMgmt/GetDetailedPRTracking',
type: 'GET',
}, beforeComplete(loader)),
language: {
emptyTable: "No record available"
},
columns: colOnPRtracking,
pageLength: 5,
lengthMenu: [[5, 10, 25, 50, 100, -1], [5, 10, 25, 50, 100, "All"]],
initComplete: function () {
initializeColumnSearch({
tableId: '#PRTable',
dataTable: prTable,
searchableColumns: [
{
columnIndex: 0,
columnName: 'Status',
placeholder: 'Select Status...',
searchType: 'select',
searchMode: 'exact',
width: '150px'
},
{
columnIndex: 1,
columnName: 'PR No',
placeholder: 'Enter PR Number...',
searchType: 'text',
searchMode: 'exact',
width: '150px'
},
{
columnIndex: 3,
columnName: 'Item No',
placeholder: 'Enter Item Number...',
searchType: 'text',
searchMode: 'exact',
width: '150px'
},
{
columnIndex: 6,
columnName: 'Item Name',
placeholder: 'Enter Item Name...',
searchType: 'text',
searchMode: 'contains',
width: '200px'
},
{
columnIndex: 13,
columnName: 'Department',
placeholder: 'Select Department...',
searchType: 'select',
searchMode: 'exact',
width: '150px'
}
]
});
const uniqueSearchClass = 'column-search-input-PRTable';
populateSelectOptions(prTable, 0, '.' + uniqueSearchClass + '[data-column="0"]');
populateSelectOptions(prTable, 13, '.' + uniqueSearchClass + '[data-column="13"]');
restoreSearchFromURL(uniqueSearchClass, '#PRTable');
},
dom: 'lBfrtip',
buttons: [
{
extend: 'csv',
title: reportTitle
},
{
extend: 'excel',
title: reportTitle
},
{
extend: 'pdf',
title: reportTitle
}
],
rowCallback: rowStatusColorCallback,
error: errorHandler,
columnDefs: [
{
targets: '_all',
render: function (data, type, row) {
if (type === 'display' && data && data.length > 50) {
return '<div style="white-space: normal; word-wrap: break-word;">' + data + '</div>';
}
return data;
}
}
]
});
});
function getFormattedDateTime() {
const now = new Date();
const yyyy = now.getFullYear();
const mm = String(now.getMonth() + 1).padStart(2, '0');
const dd = String(now.getDate()).padStart(2, '0');
const hh = String(now.getHours()).padStart(2, '0');
const min = String(now.getMinutes()).padStart(2, '0');
const ss = String(now.getSeconds()).padStart(2, '0');
return `${yyyy}-${mm}-${dd}_${hh}${min}${ss}`;
}

View File

@ -0,0 +1,92 @@
$(document).ready(function () {
const reportTitle = `PR Tracking - as of ${getFormattedDateTime()}`;
loader = $('#overlay, #loader');
var prTable = $('#PRTable').DataTable({
serverSide: true,
processing: true,
searching: false,
ajax: {
url: endpoint.GetDetailedPRTracking,
type: 'GET',
data: function (d) {
return {
draw: d.draw,
searchPRNo: ($('#srchPRNo').val() || '').trim(),
searchItemName: ($('#srchItem').val() || '').trim(),
searchDept: ($('#srchDept').val() || '').trim(),
searchStatusName: ($('#srchStatus').val() || '').trim(),
pageNumber: Math.floor(d.start / d.length) + 1,
pageSize: d.length
};
},
dataSrc: function (json) {
const $sel = $('#srchStatus');
if ($sel.find('option').length <= 1 && json.statusList?.length) {
json.statusList.forEach(function (s) {
$sel.append(`<option value="${s}">${s}</option>`);
});
}
return json.data;
},
beforeSend: function () { loader.show(); },
complete: function () { loader.hide(); },
error: function (xhr, error) {
loader.hide();
if (typeof toastr !== 'undefined')
toastr.error('Failed to load data. Please try again.');
}
},
dom: 'lBfrtip',
buttons: [
{
text: '<i class="fas fa-file-csv"></i> CSV',
className: 'btn btn-sm btn-secondary',
action: function () { exportAllData('csv', endpoint.GetDetailedPRTracking, loader, reportTitle, colOnPRtracking); }
},
{
text: '<i class="fas fa-file-excel"></i> Excel',
className: 'btn btn-sm btn-success',
action: function () { exportAllData('excel', endpoint.GetDetailedPRTracking, loader, reportTitle, colOnPRtracking); }
},
{
text: '<i class="fas fa-file-pdf"></i> PDF',
className: 'btn btn-sm btn-danger',
action: function () { exportAllData('pdf', endpoint.GetDetailedPRTracking, loader, reportTitle, colOnPRtracking); }
}
],
columns: colOnPRtracking,
rowCallback: rowStatusColorCallback,
language: { emptyTable: "No approved records available" }
});
function debounce(fn, delay) {
let t;
return function (...args) {
clearTimeout(t);
t = setTimeout(() => fn.apply(this, args), delay);
};
}
$('#srchPRNo, #srchItem, #srchDept')
.on('keyup', debounce(() => prTable.ajax.reload(), 400));
$('#srchStatus').on('change', function () {
prTable.ajax.reload();
});
$('.aprv-search-clear').on('click', function () {
$($(this).data('target')).val('').trigger('change');
});
});
function getFormattedDateTime() {
const now = new Date();
const yyyy = now.getFullYear();
const mm = String(now.getMonth() + 1).padStart(2, '0');
const dd = String(now.getDate()).padStart(2, '0');
const hh = String(now.getHours()).padStart(2, '0');
const min = String(now.getMinutes()).padStart(2, '0');
const ss = String(now.getSeconds()).padStart(2, '0');
return `${yyyy}-${mm}-${dd}_${hh}${min}${ss}`;
}

View File

@ -45,8 +45,6 @@
} }
}); });
} }
// Optional: Add helper function to refresh current tab
function refreshCurrentTab() { function refreshCurrentTab() {
if (typeof PRTabs !== 'undefined') { if (typeof PRTabs !== 'undefined') {
PRTabs.reload(); PRTabs.reload();
@ -55,7 +53,6 @@ function refreshCurrentTab() {
} }
} }
// Optional: Add helper to switch tabs programmatically
function switchToPRTab(tabName) { function switchToPRTab(tabName) {
if (typeof PRTabs !== 'undefined') { if (typeof PRTabs !== 'undefined') {
PRTabs.switchToTab(tabName); PRTabs.switchToTab(tabName);
@ -117,22 +114,20 @@ const btnUploadContainer = document.getElementById('btnUploadContainer');
const btnUploadText = document.getElementById('btnUploadText'); const btnUploadText = document.getElementById('btnUploadText');
let selectedFile = null; let selectedFile = null;
let existingAttachment = false; // Set this to true if there's already an attachment let existingAttachment = false;
// File input change event
fileInput.addEventListener('change', function (e) { fileInput.addEventListener('change', function (e) {
const file = e.target.files[0]; const file = e.target.files[0];
if (file) { if (file) {
// Validate file size (5MB max) // Validate file size (5MB max)
const maxSize = 5 * 1024 * 1024; // 5MB in bytes const maxSize = 5 * 1024 * 1024;
if (file.size > maxSize) { if (file.size > maxSize) {
showToast('warning', 'File size exceeds 5MB. Please choose a smaller file.', 'File Upload', 4000); showToast('warning', 'File size exceeds 5MB. Please choose a smaller file.', 'File Upload', 4000);
e.target.value = ''; e.target.value = '';
return; return;
} }
// Validate file type
const allowedExtensions = ['csv', 'xlsx', 'xls', 'pdf']; const allowedExtensions = ['csv', 'xlsx', 'xls', 'pdf'];
const fileExtension = file.name.split('.').pop().toLowerCase(); const fileExtension = file.name.split('.').pop().toLowerCase();
@ -142,23 +137,19 @@ fileInput.addEventListener('change', function (e) {
return; return;
} }
// Store the selected file
selectedFile = file; selectedFile = file;
// Show file info and upload button
showFileInfo(file); showFileInfo(file);
showUploadButton(); showUploadButton();
} }
}); });
// Show file information
function showFileInfo(file) { function showFileInfo(file) {
fileNameDisplay.textContent = truncateFileName(file.name, 30); fileNameDisplay.textContent = truncateFileName(file.name, 30);
fileSizeDisplay.textContent = formatFileSize(file.size); fileSizeDisplay.textContent = formatFileSize(file.size);
fileInfoBadge.classList.remove('d-none'); fileInfoBadge.classList.remove('d-none');
} }
// Show upload button
function showUploadButton() { function showUploadButton() {
// Change button text based on whether there's an existing attachment // Change button text based on whether there's an existing attachment
if (existingAttachment) { if (existingAttachment) {
@ -170,7 +161,6 @@ function showUploadButton() {
btnUploadContainer.classList.remove('d-none'); btnUploadContainer.classList.remove('d-none');
} }
// Clear file attachment
function clearFileAttachment() { function clearFileAttachment() {
fileInput.value = ''; fileInput.value = '';
selectedFile = null; selectedFile = null;
@ -178,7 +168,6 @@ function clearFileAttachment() {
btnUploadContainer.classList.add('d-none'); btnUploadContainer.classList.add('d-none');
} }
// Upload attachment function
function uploadAttachment() { function uploadAttachment() {
if (!selectedFile) { if (!selectedFile) {
showToast('warning', 'Please select a file first.', 'File Upload', 3000); showToast('warning', 'Please select a file first.', 'File Upload', 3000);
@ -205,7 +194,7 @@ function uploadAttachment() {
existingAttachment = true; existingAttachment = true;
$('#fileName').val(data.newFileName) $('#fileName').val(data.newFileName)
// Update the download button if needed
document.getElementById('btnDownloadAttachment').classList.remove('d-none'); document.getElementById('btnDownloadAttachment').classList.remove('d-none');
clearFileAttachment(); clearFileAttachment();
@ -217,13 +206,11 @@ function uploadAttachment() {
showToast('error', 'Upload error:', error, 'File Upload', 4000); showToast('error', 'Upload error:', error, 'File Upload', 4000);
}) })
.finally(() => { .finally(() => {
// Reset button state
document.querySelector('#btnUploadContainer button').disabled = false; document.querySelector('#btnUploadContainer button').disabled = false;
showUploadButton(); // Restore button text showUploadButton();
}); });
} }
// Truncate long file names
function truncateFileName(fileName, maxLength) { function truncateFileName(fileName, maxLength) {
if (fileName.length <= maxLength) return fileName; if (fileName.length <= maxLength) return fileName;
@ -234,7 +221,6 @@ function truncateFileName(fileName, maxLength) {
return truncatedName + '.' + extension; return truncatedName + '.' + extension;
} }
// Format file size
function formatFileSize(bytes) { function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes'; if (bytes === 0) return '0 Bytes';
const k = 1024; const k = 1024;
@ -243,10 +229,7 @@ function formatFileSize(bytes) {
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]; return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
} }
// Initialize: Check if there's an existing attachment on page load
function checkExistingAttachment() { function checkExistingAttachment() {
// Replace with your logic to check if attachment exists
// For example, check if there's a filename in your hidden input
const existingFileName = document.getElementById('fileName').value; const existingFileName = document.getElementById('fileName').value;
if (existingFileName) { if (existingFileName) {

View File

@ -14,25 +14,25 @@
} }
]; ];
var colOnPRtracking = [ var colOnPRtracking = [
{ data: 'statusName' }, { data: 'statusName', title: 'Status' },
{ data: 'prNo' }, { data: 'prNo', title: 'PRNo' },
{ data: 'newPRNo' }, { data: 'newPRNo', title: 'NewPRNo' },
{ data: 'itemNo' }, { data: 'itemNo', title: 'ItemNo' },
{ data: 'uomName' }, { data: 'uomName', title: 'UOM' },
{ {
data: 'qty', data: 'qty', title: 'Qty' ,
render: function (data, type, row, meta) { render: function (data, type, row, meta) {
return numberWithCommas(data); return numberWithCommas(data);
} }
}, },
{ data: 'itemName' }, { data: 'itemName', title: 'ItemName' },
{ data: 'itemDescription' }, { data: 'itemDescription', title: 'Description' },
{ data: 'specification' }, { data: 'specification', title: 'Specification' },
{ data: 'itemCategoryName' }, { data: 'itemCategoryName', title: 'Category' },
{ data: 'prTypeName' }, { data: 'prTypeName', title: 'PRType' },
{ data: 'prBy' }, { data: 'prBy', title: 'PRBy' },
{ {
data: 'prDate', data: 'prDate', title: 'PRDate',
render: function (data, type, row) { render: function (data, type, row) {
if (type === 'display' && data) { if (type === 'display' && data) {
return formatDate(data); return formatDate(data);
@ -40,39 +40,39 @@ var colOnPRtracking = [
return data; return data;
} }
}, },
{ data: 'department' }, { data: 'department', title: 'Department' },
{ data: 'remarks' }, { data: 'remarks', title: 'Remarks' },
{ data: 'attestedBy' }, { data: 'attestedBy', title: 'AttestedBy' },
{ data: 'approvedBy' }, { data: 'approvedBy', title: 'ApprovedBy' },
{ data: 'canvassNo' }, { data: 'canvassNo', title: 'CanvassNo' },
{ data: 'canvassBy' }, { data: 'canvassBy', title: 'CanvassBy' },
{ data: 'canvassDate' }, { data: 'canvassDate', title: 'CanvassDate' },
{ data: 'poNo' }, { data: 'poNo', title: 'PONo' },
{ data: 'poBy' }, { data: 'poBy', title: 'POBy' },
{ data: 'poDate' }, { data: 'poDate', title: 'PODate' },
{ data: 'rrNo' }, { data: 'rrNo', title: 'RRNo' },
{ data: 'receivedBy' }, { data: 'receivedBy', title: 'ReceivedBy' },
{ data: 'rrDate' }, { data: 'rrDate', title: 'RRDate' },
{ {
data: 'quantityReceived', data: 'quantityReceived', title: 'QtyReceived',
render: function (data, type, row, meta) { render: function (data, type, row, meta) {
return numberWithCommas(data); return numberWithCommas(data);
} }
}, },
{ data: 'acknowledgeBy' }, { data: 'acknowledgeBy', title: 'AcknowledgeBy' },
{ data: 'acknowledgeDate' }, { data: 'acknowledgeDate', title: 'AcknowledgeDate' },
]; ];
var colOnPRTable = [ var colOnPRTable = [
{ data: 'prNo' }, { data: 'prNo', title: 'PRNo' },
{ data: 'newPRNo' }, { data: 'newPRNo', title: 'NewPRNo' },
{ {
data: 'aggreItemName', data: 'aggreItemName', title: 'ItemName',
render: function (data, type, row) { render: function (data, type, row) {
return renderExpandableItems(data, 3); return renderExpandableItems(data, 3);
} }
}, },
{ {
data: 'createdDate', data: 'createdDate', title: 'PRDate',
render: function (data, type, row) { render: function (data, type, row) {
if (type === 'display' && data) { if (type === 'display' && data) {
return formatDate(data); return formatDate(data);
@ -80,9 +80,9 @@ var colOnPRTable = [
return data; return data;
} }
}, },
{ data: 'createdBy' }, { data: 'createdBy', title: 'PRBy', },
{ {
data: 'dateNeeded', data: 'dateNeeded', title: 'DateNeeded',
render: function (data, type, row) { render: function (data, type, row) {
if (type === 'display' && data) { if (type === 'display' && data) {
return formatDate(data); return formatDate(data);
@ -90,7 +90,7 @@ var colOnPRTable = [
return data; return data;
} }
}, },
{ data: 'department' }, { data: 'department', title: 'Department', },
{ {
data: null, data: null,
render: function (data, type, row) { render: function (data, type, row) {
@ -98,6 +98,71 @@ var colOnPRTable = [
} }
} }
]; ];
var colOnApprovedPR = [
{ data: 'statusName' },
{ data: 'remainingDays', searchable: false },
{ data: 'prNo' },
{ data: 'itemNo' },
{ data: 'itemName' },
{
data: 'qty', searchable: false,
render: data => numberWithCommas(data)
},
{ data: 'createdBy' },
{
data: 'createdDate', searchable: false,
render: data => formatDateTime(data)
},
{ data: 'attestedBy' },
{
data: 'attestedDate', searchable: false,
render: data => formatStrDateTime(data)
},
{ data: 'approvedBy' },
{
data: 'approvedDate', searchable: false,
render: data => formatStrDateTime(data)
},
{ data: 'department' }
];
var colOnDeletedPR = [
{ data: 'prNo' },
{ data: 'itemNo' },
{ data: 'itemName' },
{
searchable: false,
data: 'qty',
render: function (data) {
return numberWithCommas(data);
}
},
{ data: 'createdBy' },
{
searchable: false,
data: 'createdDate',
render: function (data) {
return formatDateTime(data);
}
},
{ data: 'attestedBy' },
{
searchable: false,
data: 'attestedDate',
render: function (data) {
return formatStrDateTime(data);
}
},
{ data: 'approvedBy' },
{
searchable: false,
data: 'approvedDate',
render: function (data) {
return formatStrDateTime(data);
}
},
{ data: 'department' },
{ data: 'remarks' }
];
var colOnAlterOfferTable = [ var colOnAlterOfferTable = [
{ data: 'prNo' }, { data: 'prNo' },
{ data: 'itemNo' }, { data: 'itemNo' },

View File

@ -1,7 +1,6 @@
(function () { (function () {
'use strict'; 'use strict';
// Tab configuration with table IDs for view components
const tabConfig = { const tabConfig = {
'pr-list': { 'pr-list': {
title: 'All Purchase Requests', title: 'All Purchase Requests',
@ -29,7 +28,6 @@
tab.addEventListener('click', handleTabClick); tab.addEventListener('click', handleTabClick);
}); });
// Load initial tab content
loadTabContent('pr-list'); loadTabContent('pr-list');
} }
@ -52,16 +50,13 @@
isSwitching = true; isSwitching = true;
// Update active states
document.querySelectorAll('.pr-tab-btn').forEach(b => { document.querySelectorAll('.pr-tab-btn').forEach(b => {
b.classList.remove('active'); b.classList.remove('active');
}); });
btn.classList.add('active'); btn.classList.add('active');
// Update title
updateTitle(tabConfig[tabId].title); updateTitle(tabConfig[tabId].title);
// Load view component
loadTabContent(tabId, btn); loadTabContent(tabId, btn);
currentTab = tabId; currentTab = tabId;
@ -71,16 +66,13 @@
const titleEl = document.getElementById('pageTitle'); const titleEl = document.getElementById('pageTitle');
if (!titleEl) return; if (!titleEl) return;
// Fade out with slide
titleEl.classList.add('updating'); titleEl.classList.add('updating');
setTimeout(() => { setTimeout(() => {
titleEl.textContent = title; titleEl.textContent = title;
// Trigger reflow
void titleEl.offsetWidth; void titleEl.offsetWidth;
// Fade in
titleEl.classList.remove('updating'); titleEl.classList.remove('updating');
}, 250); }, 250);
} }
@ -93,15 +85,12 @@
return; return;
} }
// Show loading state
if (btn) { if (btn) {
btn.classList.add('loading'); btn.classList.add('loading');
} }
showContainerLoading(true); showContainerLoading(true);
// Call your existing function
prTabbedComponent(config.tableId, config.endpoint, function (success) { prTabbedComponent(config.tableId, config.endpoint, function (success) {
// Hide loading state
if (btn) { if (btn) {
btn.classList.remove('loading'); btn.classList.remove('loading');
} }
@ -127,14 +116,12 @@
} }
} }
// Initialize on DOM ready
if (document.readyState === 'loading') { if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init); document.addEventListener('DOMContentLoaded', init);
} else { } else {
init(); init();
} }
// Public API
window.PRTabs = { window.PRTabs = {
switchTo: function (tabId) { switchTo: function (tabId) {
const btn = document.querySelector(`.pr-tab-btn[data-tab="${tabId}"]`); const btn = document.querySelector(`.pr-tab-btn[data-tab="${tabId}"]`);
@ -149,19 +136,17 @@
return currentTab; return currentTab;
}, },
addTab: function (tabId, config) { addTab: function (tabId, config) {
// Dynamically add new tab configuration
tabConfig[tabId] = config; tabConfig[tabId] = config;
} }
}; };
})(); })();
// Updated prTabbedComponent to work with callback
function prTabbedComponent(id, endpoint, callback) { function prTabbedComponent(id, endpoint, callback) {
$.ajax({ $.ajax({
url: endpoint, url: endpoint,
type: 'GET', type: 'GET',
data: { TableId: id }, // Note: capital T to match your controller parameter data: { TableId: id },
success: function (response) { success: function (response) {
$('#TabbedContainer').html(response); $('#TabbedContainer').html(response);

View File

@ -2,25 +2,45 @@
var loader = $('#overlay, #loader').css('z-index', 1100); var loader = $('#overlay, #loader').css('z-index', 1100);
$('#viewItemList').modal('show'); $('#viewItemList').modal('show');
$('#viewItemList').css('z-index', 1090); $('#viewItemList').css('z-index', 1090);
tableElement = $('#ItemTable'); tableElement = $('#ItemTable');
tableName = '#ItemTable'; tableName = '#ItemTable';
tableDestroy(tableElement); tableDestroy(tableElement);
totalSelectedLabel = $('#totalSelectedItem'); totalSelectedLabel = $('#totalSelectedItem');
clearTableSelection(tableName, selectedProductsMap, () => { clearTableSelection(tableName, selectedProductsMap, () => {
totalSelectedLabel.text(0); totalSelectedLabel.text(0);
}, 'selected-row', '.select-all-item-checkbox'); }, 'selected-row', '.select-all-item-checkbox');
// Reset search input on modal open
$('#modalSearchInput').val('');
$('#modalSearchClear').css('display', 'none');
$('#modalSearchHint').text('Start typing to search items…');
var itemListTable = tableElement.DataTable({ var itemListTable = tableElement.DataTable({
ajax: $.extend({ serverSide: true,
processing: true,
searching: false,
ajax: {
url: '/ItemMgmt/GetItemList', url: '/ItemMgmt/GetItemList',
type: 'POST', type: 'GET',
}, beforeComplete(loader)), data: function (d) {
var searchVal = $('#modalSearchInput').length
? $('#modalSearchInput').val()
: '';
return {
draw: d.draw,
searchTerm: (searchVal || '').trim(),
pageNumber: Math.floor(d.start / d.length) + 1,
pageSize: d.length
};
},
dataSrc: function (json) {
return json.data;
}
},
responsive: true, responsive: true,
language: { language: {
emptyTable: "No record available" emptyTable: "No record available",
processing: "Searching…"
}, },
initComplete: function () { initComplete: function () {
initializeTableSelection({ initializeTableSelection({
@ -51,35 +71,60 @@
{ data: 'itemName' }, { data: 'itemName' },
{ data: 'itemDescription' }, { data: 'itemDescription' },
{ data: 'itemCategoryName' }, { data: 'itemCategoryName' },
{ data: 'qty' }, { data: 'qty' }
], ],
"columnDefs": [ columnDefs: [
{ {
"targets": [5], targets: [5],
"render": function (data, type, row) { render: function (data, type, row) {
return '<input type="number" class="editable-qty" style="width:60px;" value="' + data + '" />'; return `<input type="number" class="editable-qty"
style="width:60px;" value="${data}" />`;
} }
} }
], ],
pageLength: 5, pageLength: 5,
lengthMenu: [[5, 10, 25, 50, 100, -1], [5, 10, 25, 50, 100, "All"]], lengthMenu: [[5, 10, 25, 50, 100], [5, 10, 25, 50, 100]],
error: errorHandler error: errorHandler
}); });
// Add this inside viewItemList(), after the DataTable is initialized
// Debounce helper (local scope)
function debounce(fn, delay) {
let t;
return function (...args) {
clearTimeout(t);
t = setTimeout(() => fn.apply(this, args), delay);
};
}
// Search input handler
$('#modalSearchInput').off('keyup').on('keyup', debounce(function () {
const val = $(this).val().trim();
$('#modalSearchHint').text(val.length ? `Searching for "${val}"…` : 'Start typing to search items…');
itemListTable.ajax.reload();
}, 400));
// Show/hide clear button
$('#modalSearchInput').off('input').on('input', function () {
$('#modalSearchClear').css('display', $(this).val().length ? 'flex' : 'none');
});
// Clear button handler
$('#modalSearchClear').off('click').on('click', function () {
$('#modalSearchInput').val('').trigger('input');
$('#modalSearchHint').text('Start typing to search items…');
itemListTable.ajax.reload();
});
// Editable qty handler
$('#ItemTable').on('change', '.editable-qty', function () { $('#ItemTable').on('change', '.editable-qty', function () {
const $row = $(this).closest('tr'); const $row = $(this).closest('tr');
const rowData = itemListTable.row($row).data(); const rowData = itemListTable.row($row).data();
if (!rowData) return; if (!rowData) return;
const newQty = parseFloat($(this).val()) || 0; const newQty = parseFloat($(this).val()) || 0;
const itemId = `${rowData.itemNo}|${rowData.qty}`; // original key const itemId = `${rowData.itemNo}|${rowData.qty}`;
// ✅ If this item is already selected, update its qty in the map
if (selectedProductsMap[itemId]) { if (selectedProductsMap[itemId]) {
selectedProductsMap[itemId].qty = newQty; selectedProductsMap[itemId].qty = newQty;
} }
// Update the DataTable's internal data too
rowData.qty = newQty; rowData.qty = newQty;
}); });
} }
@ -155,7 +200,6 @@ function getApproverName(prDetailsId, prNo) {
const attestedBy = document.getElementById('label-pr-attestedBy'); const attestedBy = document.getElementById('label-pr-attestedBy');
const approveBy = document.getElementById('label-pr-approvedBy'); const approveBy = document.getElementById('label-pr-approvedBy');
// Security: Use textContent to prevent XSS
if (attestedBy) attestedBy.textContent = item.attestedBy || ''; if (attestedBy) attestedBy.textContent = item.attestedBy || '';
if (approveBy) approveBy.textContent = item.approvedBy || ''; if (approveBy) approveBy.textContent = item.approvedBy || '';
} }

View File

@ -0,0 +1,91 @@
function exportAllData(format, api, loader, reportTitle, column) {
const params = new URLSearchParams({
searchPRNo: ($('#srchPRNo').val() || '').trim(),
searchItemName: ($('#srchItem').val() || '').trim(),
searchDept: ($('#srchDept').val() || '').trim(),
searchStatusName: ($('#srchStatus').val() || '').trim(),
pageNumber: 1,
pageSize: 999999
});
loader.show();
$.ajax({
url: `${api}?${params}`,
type: 'GET',
success: function (json) {
loader.hide();
if (!json.data || json.data.length === 0) {
toastr.warning('No data to export.');
return;
}
triggerClientExport(json.data, format, reportTitle, column);
},
error: function () {
loader.hide();
toastr.error('Failed to fetch data for export.');
}
});
}
function triggerClientExport(data, format, reportTitle, column) {
const tempTableId = 'tempExportTable';
// Filter out action/button columns (data: null) and strip render functions
const exportColumns = column
.filter(c => c.data !== null && c.data !== undefined)
.map(c => ({
data: c.data,
title: c.title || c.data,
// Keep render only for non-display logic (dates etc),
// but strip HTML from output
render: c.render
? function (data, type, row) {
if (type === 'export' || type === 'display') {
const result = c.render(data, 'display', row);
// Strip HTML tags for export
return $('<div>').html(result).text();
}
return data;
}
: undefined
}));
$('body').append(`
<div id="tempExportWrapper" style="position:fixed;left:-9999px;top:-9999px;">
<table id="${tempTableId}"></table>
</div>
`);
const tempTable = $(`#${tempTableId}`).DataTable({
data: data,
columns: exportColumns,
dom: 'Brt',
buttons: [
{
extend: format,
title: reportTitle,
exportOptions: {
columns: ':visible',
format: {
body: function (data) {
// Strip any remaining HTML tags
return $('<div>').html(data).text();
}
}
}
}
],
paging: false,
searching: false,
info: false
});
setTimeout(function () {
tempTable.button(0).trigger();
}, 500);
setTimeout(function () {
tempTable.destroy();
$('#tempExportWrapper').remove();
}, 2000);
}

View File

@ -1,232 +0,0 @@
// ===========================================
// REUSABLE DATATABLE COLUMN SEARCH UTILITY
// ===========================================
/**
* Initialize column-specific search functionality for DataTable
* @param {Object} config - Configuration object
* @param {string} config.tableId - DataTable selector (e.g., '#PRItemTable')
* @param {Object} config.dataTable - DataTable instance
* @param {Array} config.searchableColumns - Array of column configurations
* @param {string} config.containerId - Container ID for search inputs (optional)
* @param {string} config.searchInputClass - CSS class for search inputs (optional)
*/
function initializeColumnSearch(config) {
const {
tableId,
dataTable,
searchableColumns,
containerId = null,
searchInputClass = 'column-search-input'
} = config;
// Create search container if not provided
let searchContainer;
if (containerId) {
searchContainer = $('#' + containerId);
} else {
// Create default search container above the table
searchContainer = $('<div class="column-search-container mb-3"></div>');
$(tableId).parent().prepend(searchContainer);
}
// Clear existing search inputs
searchContainer.empty();
// Create search inputs for each specified column
searchableColumns.forEach(function (columnConfig) {
const columnIndex = columnConfig.columnIndex;
const columnName = columnConfig.columnName;
const placeholder = columnConfig.placeholder;
const searchType = columnConfig.searchType || 'text';
const searchMode = columnConfig.searchMode || 'contains';
const selectOptions = columnConfig.selectOptions || null;
const width = columnConfig.width || 'auto';
// Create input wrapper
const inputWrapper = $('<div class="d-inline-block me-2 mb-2" style="width: ' + width + ';"><label class="form-label small">' + columnName + ':</label></div>');
let searchInput;
// Create different input types based on searchType
switch (searchType) {
case 'select':
searchInput = $('<select class="' + searchInputClass + ' form-select form-select-sm" data-column="' + columnIndex + '" data-column-name="' + columnName + '" data-search-mode="' + searchMode + '"><option value="">All ' + columnName + '</option></select>');
// Add options if provided
if (selectOptions && Array.isArray(selectOptions)) {
selectOptions.forEach(function (option) {
searchInput.append('<option value="' + option.value + '">' + option.text + '</option>');
});
}
break;
case 'date':
searchInput = $('<input type="date" class="' + searchInputClass + ' form-control form-control-sm" data-column="' + columnIndex + '" data-column-name="' + columnName + '" data-search-mode="' + searchMode + '" placeholder="' + (placeholder || 'Search ' + columnName + '...') + '">');
break;
case 'number':
searchInput = $('<input type="number" class="' + searchInputClass + ' form-control form-control-sm" data-column="' + columnIndex + '" data-column-name="' + columnName + '" data-search-mode="' + searchMode + '" placeholder="' + (placeholder || 'Search ' + columnName + '...') + '">');
break;
default: // text
searchInput = $('<input type="text" class="' + searchInputClass + ' form-control form-control-sm" data-column="' + columnIndex + '" data-column-name="' + columnName + '" data-search-mode="' + searchMode + '" placeholder="' + (placeholder || 'Search ' + columnName + '...') + '">');
}
inputWrapper.append(searchInput);
searchContainer.append(inputWrapper);
});
// Add clear all button
const clearButton = $('<div class="d-inline-block me-2 mb-2"><label class="form-label small">&nbsp;</label><button type="button" class="btn btn-outline-secondary btn-sm d-block clear-column-search">Clear All</button></div>');
searchContainer.append(clearButton);
// Bind search events
bindColumnSearchEvents(dataTable, searchInputClass);
}
/**
* Bind search events to column search inputs
* @param {Object} dataTable - DataTable instance
* @param {string} searchInputClass - CSS class for search inputs
*/
function bindColumnSearchEvents(dataTable, searchInputClass) {
// Individual column search
$('.' + searchInputClass).off('keyup change').on('keyup change', function () {
const columnIndex = $(this).data('column');
const searchValue = $(this).val();
const searchMode = $(this).data('search-mode') || 'contains';
// Apply column-specific search with regex based on search mode
let searchRegex = '';
if (searchValue) {
switch (searchMode) {
case 'exact':
// Exact match - search for the exact value
searchRegex = '^' + escapeRegex(searchValue) + '$';
break;
case 'starts':
// Starts with - search for values that start with the input
searchRegex = '^' + escapeRegex(searchValue);
break;
case 'ends':
// Ends with - search for values that end with the input
searchRegex = escapeRegex(searchValue) + '$';
break;
default: // 'contains'
// Contains - default DataTable behavior (no regex needed)
searchRegex = searchValue;
}
}
// Apply search with regex if needed
if (searchMode !== 'contains' && searchValue) {
dataTable.column(columnIndex).search(searchRegex, true, false).draw();
} else {
dataTable.column(columnIndex).search(searchValue).draw();
}
// Update URL parameters (optional)
updateSearchParams($(this).data('column-name'), searchValue);
});
// Clear all searches
$('.clear-column-search').off('click').on('click', function () {
$('.' + searchInputClass).val('');
// Clear all column searches
dataTable.columns().search('').draw();
// Clear URL parameters (optional)
clearAllSearchParams();
});
}
/**
* Escape special regex characters
* @param {string} string - String to escape
* @returns {string} Escaped string
*/
function escapeRegex(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
/**
* Populate select options dynamically from table data
* @param {Object} dataTable - DataTable instance
* @param {number} columnIndex - Column index to extract unique values
* @param {string} selectSelector - jQuery selector for the select element
*/
function populateSelectOptions(dataTable, columnIndex, selectSelector) {
// Get unique values from the column
const uniqueValues = dataTable.column(columnIndex).data().unique().sort();
const selectElement = $(selectSelector);
// Clear existing options (except the first "All" option)
selectElement.find('option:not(:first)').remove();
// Add unique values as options
uniqueValues.each(function (value) {
if (value && value.toString().trim() !== '') {
selectElement.append('<option value="' + value + '">' + value + '</option>');
}
});
}
/**
* Update URL search parameters (optional feature)
* @param {string} paramName - Parameter name
* @param {string} paramValue - Parameter value
*/
function updateSearchParams(paramName, paramValue) {
if (typeof URLSearchParams === 'undefined') return;
const url = new URL(window.location);
if (paramValue && paramValue.trim() !== '') {
url.searchParams.set('search_' + paramName, paramValue);
} else {
url.searchParams.delete('search_' + paramName);
}
window.history.replaceState({}, '', url);
}
/**
* Clear all search parameters from URL
*/
function clearAllSearchParams() {
if (typeof URLSearchParams === 'undefined') return;
const url = new URL(window.location);
const keysToDelete = [];
for (const key of url.searchParams.keys()) {
if (key.indexOf('search_') === 0) {
keysToDelete.push(key);
}
}
keysToDelete.forEach(function (key) {
url.searchParams.delete(key);
});
window.history.replaceState({}, '', url);
}
/**
* Restore search values from URL parameters
* @param {string} searchInputClass - CSS class for search inputs
*/
function restoreSearchFromURL(searchInputClass) {
if (typeof URLSearchParams === 'undefined') return;
const url = new URL(window.location);
$('.' + searchInputClass).each(function () {
const columnName = $(this).data('column-name');
const searchValue = url.searchParams.get('search_' + columnName);
if (searchValue) {
$(this).val(searchValue).trigger('change');
}
});
}

View File

@ -0,0 +1,74 @@
.search-wrapper {
margin-bottom: 1rem;
}
.search-inner {
position: relative;
display: flex;
align-items: center;
background: #fff;
border: 1.5px solid #d0e8e5;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(26, 122, 110, 0.08);
transition: border-color 0.2s ease, box-shadow 0.2s ease;
overflow: hidden;
}
.search-inner:focus-within {
border-color: #1a7a6e;
box-shadow: 0 0 0 3px rgba(26, 122, 110, 0.15), 0 2px 8px rgba(26, 122, 110, 0.1);
}
.search-icon {
display: flex;
align-items: center;
padding: 0 12px 0 14px;
color: #1a7a6e;
opacity: 0.6;
transition: opacity 0.2s;
flex-shrink: 0;
}
.search-inner:focus-within .search-icon {
opacity: 1;
}
.search-input {
flex: 1;
border: none;
outline: none;
padding: 11px 4px;
font-size: 0.9rem;
color: #2d2d2d;
background: transparent;
letter-spacing: 0.01em;
}
.search-input::placeholder {
color: #aab8b6;
font-style: italic;
}
.search-clear {
display: none;
align-items: center;
justify-content: center;
padding: 0 12px;
cursor: pointer;
color: #888;
transition: color 0.15s;
flex-shrink: 0;
}
.search-clear:hover {
color: #1a7a6e;
}
.search-hint {
font-size: 0.76rem;
color: #9ab5b1;
margin-top: 5px;
padding-left: 2px;
min-height: 1.1em;
transition: color 0.2s;
}