Backend pagination for Item, Pr Tracking, approved pr, Deleted PR
This commit is contained in:
parent
1b06999f15
commit
d770613286
@ -2,19 +2,13 @@
|
||||
using CPRNIMS.Domain.Contracts.PR;
|
||||
using CPRNIMS.Domain.Services;
|
||||
using CPRNIMS.Infrastructure.Dto.Items;
|
||||
using CPRNIMS.Infrastructure.Dto.PO;
|
||||
using CPRNIMS.Infrastructure.Dto.PR;
|
||||
using CPRNIMS.Infrastructure.Entities.PO;
|
||||
using CPRNIMS.Infrastructure.Entities.Purchasing;
|
||||
using CPRNIMS.Infrastructure.Helper;
|
||||
using CPRNIMS.Infrastructure.Models.Common;
|
||||
using CPRNIMS.Infrastructure.ViewModel.Common;
|
||||
using CPRNIMS.Infrastructure.ViewModel.PR;
|
||||
using CPRNIMS.WebApi.Controllers.Base;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Reflection.Metadata.Ecma335;
|
||||
using System.Text;
|
||||
|
||||
|
||||
@ -377,6 +371,15 @@ namespace CPRNIMS.WebApi.Controllers.PR
|
||||
nameof(GetAllPR), false
|
||||
);
|
||||
}
|
||||
[HttpPost("GetPRArchived")]
|
||||
public async Task<IActionResult> GetPRArchived(PRDto PRDto)
|
||||
{
|
||||
return await ExecuteWithErrorHandling(
|
||||
() => _pRequest.GetPRArchived(PRDto),
|
||||
nameof(GetPRArchived), false
|
||||
);
|
||||
}
|
||||
|
||||
[HttpPost("GetApprovedPR")]
|
||||
public async Task<IActionResult> GetApprovedPR(PRDto PRDto)
|
||||
{
|
||||
@ -385,12 +388,12 @@ namespace CPRNIMS.WebApi.Controllers.PR
|
||||
nameof(GetApprovedPR), false
|
||||
);
|
||||
}
|
||||
[HttpPost("GetRemovedPR")]
|
||||
public async Task<IActionResult> GetRemovedPR(PRDto PRDto)
|
||||
[HttpPost("GetDeletedPR")]
|
||||
public async Task<IActionResult> GetDeletedPR(PRDto PRDto)
|
||||
{
|
||||
return await ExecuteWithErrorHandling(
|
||||
() => _pRequest.GetRemovedPR(PRDto),
|
||||
nameof(GetRemovedPR), false
|
||||
() => _pRequest.GetDeletedPR(PRDto),
|
||||
nameof(GetDeletedPR), false
|
||||
);
|
||||
}
|
||||
[HttpPost("GetMyPR")]
|
||||
|
||||
@ -326,11 +326,25 @@ namespace CPRNIMS.WebApps.Controllers.Items
|
||||
throw;
|
||||
}
|
||||
}
|
||||
public async Task<IActionResult> GetItemList()
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetItemList(string searchTerm = "", int pageNumber = 1, int pageSize = 10)
|
||||
{
|
||||
var viewModels = new ItemVM();
|
||||
response = await _item.GetItemList(GetUser(), viewModels);
|
||||
return GetResponse(response);
|
||||
var viewModel = new ItemVM
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
||||
@ -21,20 +21,134 @@ namespace CPRNIMS.WebApps.Controllers.PR
|
||||
_pRequest = pRequest;
|
||||
}
|
||||
#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);
|
||||
return GetResponse(response);
|
||||
var dto = new PRVM
|
||||
{
|
||||
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);
|
||||
return GetResponse(response);
|
||||
var dto = new PRVM
|
||||
{
|
||||
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);
|
||||
return GetResponse(response);
|
||||
var dto = new PRVM
|
||||
{
|
||||
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]
|
||||
public async Task<IActionResult> GetPRAttachment(string fileName)
|
||||
@ -127,11 +241,6 @@ namespace CPRNIMS.WebApps.Controllers.PR
|
||||
response = await _pRequest.GetItemDetailForReceiving(GetUser(), viewModel);
|
||||
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)
|
||||
{
|
||||
response = await _pRequest.GetSupplierAlternativeOffer(GetUser(), viewModel);
|
||||
|
||||
@ -10,7 +10,7 @@ namespace CPRNIMS.WebApps.ViewComponents.PR
|
||||
{
|
||||
1 => "~/Views/Components/PRMgmt/PRTabbedTable/AllPR.cshtml",
|
||||
2 => "~/Views/Components/PRMgmt/PRTabbedTable/ApprovedPR.cshtml",
|
||||
_ => "~/Views/Components/PRMgmt/PRTabbedTable/RemovedPR.cshtml"
|
||||
_ => "~/Views/Components/PRMgmt/PRTabbedTable/DeletedPR.cshtml"
|
||||
};
|
||||
return View(viewName);
|
||||
}
|
||||
|
||||
@ -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%">
|
||||
<thead >
|
||||
<tr>
|
||||
@ -21,91 +69,82 @@
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
loader = $('#overlay, #loader');
|
||||
|
||||
UserRights = document.getElementById("roleRights").value;
|
||||
|
||||
const reportTitle = `All Purchase Request - as of ${getFormattedDateTime()}`;
|
||||
|
||||
prTable = $('#PRTable').DataTable({
|
||||
serverSide: true,
|
||||
processing: true,
|
||||
searching: false,
|
||||
ajax: {
|
||||
url: '/PRMgmt/GetAllPR',
|
||||
url: endpoint.GetAllPR,
|
||||
type: 'GET',
|
||||
beforeSend: function () {
|
||||
loader.show();
|
||||
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
|
||||
};
|
||||
},
|
||||
complete: function () {
|
||||
loader.hide();
|
||||
},
|
||||
error: function (xhr, error, thrown) {
|
||||
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') {
|
||||
if (typeof toastr !== 'undefined')
|
||||
toastr.error('Failed to load data. Please try again.');
|
||||
}
|
||||
}
|
||||
},
|
||||
dom: 'lBfrtip',
|
||||
buttons: [
|
||||
{
|
||||
extend: 'csv',
|
||||
title: reportTitle
|
||||
text: '<i class="fas fa-file-csv"></i> CSV',
|
||||
className: 'btn btn-sm btn-secondary',
|
||||
action: function () { exportAllData('csv', endpoint.GetAllPR, loader, reportTitle, colOnPRTable); }
|
||||
},
|
||||
{
|
||||
extend: 'excel',
|
||||
title: reportTitle
|
||||
text: '<i class="fas fa-file-excel"></i> Excel',
|
||||
className: 'btn btn-sm btn-success',
|
||||
action: function () { exportAllData('excel', endpoint.GetAllPR, loader, reportTitle, colOnPRTable); }
|
||||
},
|
||||
{
|
||||
extend: 'pdf',
|
||||
title: reportTitle
|
||||
text: '<i class="fas fa-file-pdf"></i> PDF',
|
||||
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,
|
||||
order: [[3, 'asc']],
|
||||
order: [[0, 'asc']],
|
||||
rowCallback: rowStatusColorCallback,
|
||||
responsive: true,
|
||||
language: {
|
||||
emptyTable: "No record available",
|
||||
loadingRecords: "Loading data...",
|
||||
processing: "Processing..."
|
||||
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(function () {
|
||||
prTable.ajax.reload();
|
||||
}, 400));
|
||||
|
||||
$('.pr-search-clear').on('click', function () {
|
||||
const target = $(this).data('target');
|
||||
$(target).val('');
|
||||
prTable.ajax.reload();
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
@ -1,4 +1,74 @@
|
||||
<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%">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -24,140 +94,84 @@
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
|
||||
const reportTitle = `Approved Purchase Request (No PO) - as of ${getFormattedDateTime()}`;
|
||||
|
||||
var approvedPRTable = $('#ApprovedPRTable').DataTable({
|
||||
serverSide: true,
|
||||
processing: true,
|
||||
searching: false,
|
||||
ajax: {
|
||||
url: '/PRMgmt/GetApprovedPR',
|
||||
url: endpoint.GetApprovedPR,
|
||||
type: 'GET',
|
||||
beforeSend: function () {
|
||||
loader.show();
|
||||
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
|
||||
};
|
||||
},
|
||||
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.');
|
||||
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: [
|
||||
{
|
||||
extend: 'csv',
|
||||
title: reportTitle
|
||||
text: '<i class="fas fa-file-csv"></i> CSV',
|
||||
className: 'btn btn-sm btn-secondary',
|
||||
action: function () { exportAllData('csv', endpoint.GetApprovedPR, loader, reportTitle, colOnApprovedPR); }
|
||||
},
|
||||
{
|
||||
extend: 'excel',
|
||||
title: reportTitle
|
||||
text: '<i class="fas fa-file-excel"></i> Excel',
|
||||
className: 'btn btn-sm btn-success',
|
||||
action: function () { exportAllData('excel', endpoint.GetApprovedPR, loader, reportTitle, colOnApprovedPR); }
|
||||
},
|
||||
{
|
||||
extend: 'pdf',
|
||||
title: reportTitle
|
||||
text: '<i class="fas fa-file-pdf"></i> PDF',
|
||||
className: 'btn btn-sm btn-danger',
|
||||
action: function () { exportAllData('pdf', endpoint.GetApprovedPR, loader, reportTitle, colOnApprovedPR); }
|
||||
}
|
||||
],
|
||||
initComplete: function () {
|
||||
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' }
|
||||
],
|
||||
columns: colOnApprovedPR,
|
||||
order: [[11, 'asc']],
|
||||
responsive: true,
|
||||
language: {
|
||||
emptyTable: "No approved records available"
|
||||
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(() => 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) {
|
||||
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -10,6 +10,29 @@
|
||||
Add new
|
||||
</button>
|
||||
<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%">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -18,8 +41,6 @@
|
||||
<th>ItemSpecs</th>
|
||||
<th>CategoryName</th>
|
||||
<th>Action</th>
|
||||
<th hidden></th>
|
||||
<th hidden></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -237,7 +258,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="~/JsFunctions/Items/ItemManagementV7.js"></script>
|
||||
<script src="~/JsFunctions/Items/ItemManagementV8.js"></script>
|
||||
@await Html.PartialAsync("PagesView/Item/_Scripts")
|
||||
</div>
|
||||
|
||||
|
||||
@ -17,8 +17,6 @@
|
||||
<th>ItemCategory</th>
|
||||
<th>SupplierName</th>
|
||||
<th>Action</th>
|
||||
<th hidden></th>
|
||||
<th hidden></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@ -33,6 +33,30 @@
|
||||
<h2 class="modal-title" id="addItemLabel">Item List</h2>
|
||||
</div>
|
||||
<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">
|
||||
<span class="fw-semibold">Selected Items:</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/PRTabs.css" rel="stylesheet" />
|
||||
@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>
|
||||
@await Html.PartialAsync("PagesView/PR/_PRScripts")
|
||||
</body>
|
||||
@ -5,7 +5,55 @@
|
||||
<h2 style="display: flex; flex-direction: column; align-items: center;">PR Archived</h2>
|
||||
</div>
|
||||
<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%">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -17,8 +65,6 @@
|
||||
<th style="width:8%">DateNeeded</th>
|
||||
<th style="width:7%">ChargeTo</th>
|
||||
<th style="width:7%">Action</th>
|
||||
<th hidden></th>
|
||||
<th hidden></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -27,7 +73,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<link href="~/css/pr/TrackingV3.css" rel="stylesheet" />
|
||||
<link href="~/css/item/item.css" rel="stylesheet" />
|
||||
@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")
|
||||
</body>
|
||||
@ -5,6 +5,76 @@
|
||||
</div>
|
||||
<div class="table-container shadow-lg p-3 mb-3 bg-white rounded" style="overflow-x: auto;height:110%">
|
||||
<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%">
|
||||
<thead>
|
||||
<tr>
|
||||
@ -45,6 +115,6 @@
|
||||
</div>
|
||||
</div>
|
||||
<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")
|
||||
</body>
|
||||
@ -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="loader" class="loader"></div>
|
||||
</div>
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
<script src="~/jsfunctions/po/POButtonV9.js"></script>
|
||||
<script src="~/jsfunctions/po/PrintingV2.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/POVarV6.js"></script>
|
||||
<script src="~/jsfunctions/po/POViewV4.js"></script>
|
||||
|
||||
@ -1,20 +1,22 @@
|
||||
<div id="overlay" class="overlay" style="display: none;">
|
||||
<div id="loader" class="loader"></div>
|
||||
</div>
|
||||
<link href="~/css/item/item.css" rel="stylesheet" />
|
||||
<link href="~/css/common/rowhighlighter.css" rel="stylesheet" />
|
||||
|
||||
<script src="~/jsfunctions/pr/PRColumnV8.js"></script>
|
||||
<script src="~/jsfunctions/pr/PRViewV8.js"></script>
|
||||
<script src="~/jsfunctions/pr/PRColumnV9.js"></script>
|
||||
<script src="~/jsfunctions/pr/PRViewV9.js"></script>
|
||||
<script src="~/jsfunctions/pr/PRPostPut.js"></script>
|
||||
<script src="~/jsfunctions/pr/PRButtonv3.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/prRowCallbackV3.js"></script>
|
||||
<script src="~/jsfunctions/utilities/columnstyle.js"></script>
|
||||
<script src="~/jsfunctions/tracking/tracking.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/StylesV3.js"></script>
|
||||
<script src="~/jsfunctions/utilities/searchengine.js"></script>
|
||||
|
||||
@ -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"
|
||||
tabindex="-1" aria-labelledby="ModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-xl">
|
||||
|
||||
@ -41,18 +41,32 @@ function isFullFilled() {
|
||||
|
||||
$(document).ready(function () {
|
||||
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();
|
||||
$('#cartCount').text(cartItemCount);
|
||||
|
||||
itemTable = $('#ItemTable').DataTable({
|
||||
serverSide: false,
|
||||
serverSide: true,
|
||||
processing: true,
|
||||
deferLoading: 0,
|
||||
data: [],
|
||||
searching: false, // disable built-in search box (we drive it manually)
|
||||
|
||||
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: [
|
||||
{ data: 'itemNo' },
|
||||
{ data: 'itemName' },
|
||||
@ -63,69 +77,34 @@ $(document).ready(function () {
|
||||
render: function (data, type, row) {
|
||||
return renderItembtns(data, row);
|
||||
}
|
||||
},
|
||||
{ data: 'cartItemCount', visible: false },
|
||||
{ data: 'createdDate', visible: false },
|
||||
{ data: 'requestTypeId', visible: false }
|
||||
}
|
||||
],
|
||||
|
||||
order: [[0, 'desc']],
|
||||
responsive: true,
|
||||
|
||||
language: {
|
||||
emptyTable: "Type in the search box to fetch data."
|
||||
}
|
||||
});
|
||||
|
||||
// Simple debounce helper
|
||||
function debounce(func, delay) {
|
||||
let timer;
|
||||
// Debounce helper
|
||||
function debounce(fn, delay) {
|
||||
let t;
|
||||
return function (...args) {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => func.apply(this, args), delay);
|
||||
clearTimeout(t);
|
||||
t = setTimeout(() => fn.apply(this, args), delay);
|
||||
};
|
||||
}
|
||||
|
||||
// Search handler
|
||||
$('#ItemTable_filter input').unbind().on('keyup', debounce(function () {
|
||||
let searchValue = $(this).val().trim();
|
||||
|
||||
if (searchValue.length === 0) {
|
||||
itemTable.clear().draw();
|
||||
// Custom search box
|
||||
$('#customSearch').on('keyup', debounce(function () {
|
||||
if ($(this).val().trim().length === 0) {
|
||||
itemTable.clear().draw(); // show nothing until user types
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasFetched && !fetchInProgress) {
|
||||
// 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
|
||||
itemTable.ajax.reload(); // triggers new server call with searchTerm
|
||||
}, 400));
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const triggers = Array.from(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
||||
@ -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);
|
||||
});
|
||||
80
CPRNIMS.WebApps/wwwroot/JsFunctions/PR/ArchivedV3.js
Normal file
80
CPRNIMS.WebApps/wwwroot/JsFunctions/PR/ArchivedV3.js
Normal 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);
|
||||
});
|
||||
@ -3,10 +3,14 @@
|
||||
GetItemColor: '/ItemMgmt/GetItemColor',
|
||||
GetItemLocalization: '/ItemMgmt/GetItemLocalization',
|
||||
GetItemUOM: '/ItemMgmt/GetItemUOM',
|
||||
GetAllPR: '/PRMgmt/GetAllPR',
|
||||
GetApprovedPR: '/PRMgmt/GetApprovedPR',
|
||||
GetDeletedPR: '/PRMgmt/GetDeletedPR',
|
||||
|
||||
GetProjectCodes: '/PRMgmt/GetProjectCodes',
|
||||
GetSupplierAlternativeOffer: '/PRMgmt/GetSupplierAlternativeOffer',
|
||||
GetSupplierAlterOfferDetails: '/PRMgmt/GetSupplierAlterOfferDetails',
|
||||
GetDetailedPRTracking: '/PRMgmt/GetDetailedPRTracking',
|
||||
|
||||
PutSupplierAlterOffer: '/PRMgmt/PutSupplierAlterOffer',
|
||||
PostPutProjectCode: '/PRMgmt/PostPutProjectCode',
|
||||
@ -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}`;
|
||||
}
|
||||
@ -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}`;
|
||||
}
|
||||
@ -45,8 +45,6 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Optional: Add helper function to refresh current tab
|
||||
function refreshCurrentTab() {
|
||||
if (typeof PRTabs !== 'undefined') {
|
||||
PRTabs.reload();
|
||||
@ -55,7 +53,6 @@ function refreshCurrentTab() {
|
||||
}
|
||||
}
|
||||
|
||||
// Optional: Add helper to switch tabs programmatically
|
||||
function switchToPRTab(tabName) {
|
||||
if (typeof PRTabs !== 'undefined') {
|
||||
PRTabs.switchToTab(tabName);
|
||||
@ -117,22 +114,20 @@ const btnUploadContainer = document.getElementById('btnUploadContainer');
|
||||
const btnUploadText = document.getElementById('btnUploadText');
|
||||
|
||||
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) {
|
||||
const file = e.target.files[0];
|
||||
|
||||
if (file) {
|
||||
// Validate file size (5MB max)
|
||||
const maxSize = 5 * 1024 * 1024; // 5MB in bytes
|
||||
const maxSize = 5 * 1024 * 1024;
|
||||
if (file.size > maxSize) {
|
||||
showToast('warning', 'File size exceeds 5MB. Please choose a smaller file.', 'File Upload', 4000);
|
||||
e.target.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate file type
|
||||
const allowedExtensions = ['csv', 'xlsx', 'xls', 'pdf'];
|
||||
const fileExtension = file.name.split('.').pop().toLowerCase();
|
||||
|
||||
@ -142,23 +137,19 @@ fileInput.addEventListener('change', function (e) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the selected file
|
||||
selectedFile = file;
|
||||
|
||||
// Show file info and upload button
|
||||
showFileInfo(file);
|
||||
showUploadButton();
|
||||
}
|
||||
});
|
||||
|
||||
// Show file information
|
||||
function showFileInfo(file) {
|
||||
fileNameDisplay.textContent = truncateFileName(file.name, 30);
|
||||
fileSizeDisplay.textContent = formatFileSize(file.size);
|
||||
fileInfoBadge.classList.remove('d-none');
|
||||
}
|
||||
|
||||
// Show upload button
|
||||
function showUploadButton() {
|
||||
// Change button text based on whether there's an existing attachment
|
||||
if (existingAttachment) {
|
||||
@ -170,7 +161,6 @@ function showUploadButton() {
|
||||
btnUploadContainer.classList.remove('d-none');
|
||||
}
|
||||
|
||||
// Clear file attachment
|
||||
function clearFileAttachment() {
|
||||
fileInput.value = '';
|
||||
selectedFile = null;
|
||||
@ -178,7 +168,6 @@ function clearFileAttachment() {
|
||||
btnUploadContainer.classList.add('d-none');
|
||||
}
|
||||
|
||||
// Upload attachment function
|
||||
function uploadAttachment() {
|
||||
if (!selectedFile) {
|
||||
showToast('warning', 'Please select a file first.', 'File Upload', 3000);
|
||||
@ -205,7 +194,7 @@ function uploadAttachment() {
|
||||
existingAttachment = true;
|
||||
|
||||
$('#fileName').val(data.newFileName)
|
||||
// Update the download button if needed
|
||||
|
||||
document.getElementById('btnDownloadAttachment').classList.remove('d-none');
|
||||
|
||||
clearFileAttachment();
|
||||
@ -217,13 +206,11 @@ function uploadAttachment() {
|
||||
showToast('error', 'Upload error:', error, 'File Upload', 4000);
|
||||
})
|
||||
.finally(() => {
|
||||
// Reset button state
|
||||
document.querySelector('#btnUploadContainer button').disabled = false;
|
||||
showUploadButton(); // Restore button text
|
||||
showUploadButton();
|
||||
});
|
||||
}
|
||||
|
||||
// Truncate long file names
|
||||
function truncateFileName(fileName, maxLength) {
|
||||
if (fileName.length <= maxLength) return fileName;
|
||||
|
||||
@ -234,7 +221,6 @@ function truncateFileName(fileName, maxLength) {
|
||||
return truncatedName + '.' + extension;
|
||||
}
|
||||
|
||||
// Format file size
|
||||
function formatFileSize(bytes) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
@ -243,10 +229,7 @@ function formatFileSize(bytes) {
|
||||
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() {
|
||||
// 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;
|
||||
|
||||
if (existingFileName) {
|
||||
@ -14,25 +14,25 @@
|
||||
}
|
||||
];
|
||||
var colOnPRtracking = [
|
||||
{ data: 'statusName' },
|
||||
{ data: 'prNo' },
|
||||
{ data: 'newPRNo' },
|
||||
{ data: 'itemNo' },
|
||||
{ data: 'uomName' },
|
||||
{ data: 'statusName', title: 'Status' },
|
||||
{ data: 'prNo', title: 'PRNo' },
|
||||
{ data: 'newPRNo', title: 'NewPRNo' },
|
||||
{ data: 'itemNo', title: 'ItemNo' },
|
||||
{ data: 'uomName', title: 'UOM' },
|
||||
{
|
||||
data: 'qty',
|
||||
data: 'qty', title: 'Qty' ,
|
||||
render: function (data, type, row, meta) {
|
||||
return numberWithCommas(data);
|
||||
}
|
||||
},
|
||||
{ data: 'itemName' },
|
||||
{ data: 'itemDescription' },
|
||||
{ data: 'specification' },
|
||||
{ data: 'itemCategoryName' },
|
||||
{ data: 'prTypeName' },
|
||||
{ data: 'prBy' },
|
||||
{ data: 'itemName', title: 'ItemName' },
|
||||
{ data: 'itemDescription', title: 'Description' },
|
||||
{ data: 'specification', title: 'Specification' },
|
||||
{ data: 'itemCategoryName', title: 'Category' },
|
||||
{ data: 'prTypeName', title: 'PRType' },
|
||||
{ data: 'prBy', title: 'PRBy' },
|
||||
{
|
||||
data: 'prDate',
|
||||
data: 'prDate', title: 'PRDate',
|
||||
render: function (data, type, row) {
|
||||
if (type === 'display' && data) {
|
||||
return formatDate(data);
|
||||
@ -40,39 +40,39 @@ var colOnPRtracking = [
|
||||
return data;
|
||||
}
|
||||
},
|
||||
{ data: 'department' },
|
||||
{ data: 'remarks' },
|
||||
{ data: 'attestedBy' },
|
||||
{ data: 'approvedBy' },
|
||||
{ data: 'canvassNo' },
|
||||
{ data: 'canvassBy' },
|
||||
{ data: 'canvassDate' },
|
||||
{ data: 'poNo' },
|
||||
{ data: 'poBy' },
|
||||
{ data: 'poDate' },
|
||||
{ data: 'rrNo' },
|
||||
{ data: 'receivedBy' },
|
||||
{ data: 'rrDate' },
|
||||
{ data: 'department', title: 'Department' },
|
||||
{ data: 'remarks', title: 'Remarks' },
|
||||
{ data: 'attestedBy', title: 'AttestedBy' },
|
||||
{ data: 'approvedBy', title: 'ApprovedBy' },
|
||||
{ data: 'canvassNo', title: 'CanvassNo' },
|
||||
{ data: 'canvassBy', title: 'CanvassBy' },
|
||||
{ data: 'canvassDate', title: 'CanvassDate' },
|
||||
{ data: 'poNo', title: 'PONo' },
|
||||
{ data: 'poBy', title: 'POBy' },
|
||||
{ data: 'poDate', title: 'PODate' },
|
||||
{ data: 'rrNo', title: 'RRNo' },
|
||||
{ data: 'receivedBy', title: 'ReceivedBy' },
|
||||
{ data: 'rrDate', title: 'RRDate' },
|
||||
{
|
||||
data: 'quantityReceived',
|
||||
data: 'quantityReceived', title: 'QtyReceived',
|
||||
render: function (data, type, row, meta) {
|
||||
return numberWithCommas(data);
|
||||
}
|
||||
},
|
||||
{ data: 'acknowledgeBy' },
|
||||
{ data: 'acknowledgeDate' },
|
||||
{ data: 'acknowledgeBy', title: 'AcknowledgeBy' },
|
||||
{ data: 'acknowledgeDate', title: 'AcknowledgeDate' },
|
||||
];
|
||||
var colOnPRTable = [
|
||||
{ data: 'prNo' },
|
||||
{ data: 'newPRNo' },
|
||||
{ data: 'prNo', title: 'PRNo' },
|
||||
{ data: 'newPRNo', title: 'NewPRNo' },
|
||||
{
|
||||
data: 'aggreItemName',
|
||||
data: 'aggreItemName', title: 'ItemName',
|
||||
render: function (data, type, row) {
|
||||
return renderExpandableItems(data, 3);
|
||||
}
|
||||
},
|
||||
{
|
||||
data: 'createdDate',
|
||||
data: 'createdDate', title: 'PRDate',
|
||||
render: function (data, type, row) {
|
||||
if (type === 'display' && data) {
|
||||
return formatDate(data);
|
||||
@ -80,9 +80,9 @@ var colOnPRTable = [
|
||||
return data;
|
||||
}
|
||||
},
|
||||
{ data: 'createdBy' },
|
||||
{ data: 'createdBy', title: 'PRBy', },
|
||||
{
|
||||
data: 'dateNeeded',
|
||||
data: 'dateNeeded', title: 'DateNeeded',
|
||||
render: function (data, type, row) {
|
||||
if (type === 'display' && data) {
|
||||
return formatDate(data);
|
||||
@ -90,7 +90,7 @@ var colOnPRTable = [
|
||||
return data;
|
||||
}
|
||||
},
|
||||
{ data: 'department' },
|
||||
{ data: 'department', title: 'Department', },
|
||||
{
|
||||
data: null,
|
||||
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 = [
|
||||
{ data: 'prNo' },
|
||||
{ data: 'itemNo' },
|
||||
@ -1,7 +1,6 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// Tab configuration with table IDs for view components
|
||||
const tabConfig = {
|
||||
'pr-list': {
|
||||
title: 'All Purchase Requests',
|
||||
@ -29,7 +28,6 @@
|
||||
tab.addEventListener('click', handleTabClick);
|
||||
});
|
||||
|
||||
// Load initial tab content
|
||||
loadTabContent('pr-list');
|
||||
}
|
||||
|
||||
@ -52,16 +50,13 @@
|
||||
|
||||
isSwitching = true;
|
||||
|
||||
// Update active states
|
||||
document.querySelectorAll('.pr-tab-btn').forEach(b => {
|
||||
b.classList.remove('active');
|
||||
});
|
||||
btn.classList.add('active');
|
||||
|
||||
// Update title
|
||||
updateTitle(tabConfig[tabId].title);
|
||||
|
||||
// Load view component
|
||||
loadTabContent(tabId, btn);
|
||||
|
||||
currentTab = tabId;
|
||||
@ -71,16 +66,13 @@
|
||||
const titleEl = document.getElementById('pageTitle');
|
||||
if (!titleEl) return;
|
||||
|
||||
// Fade out with slide
|
||||
titleEl.classList.add('updating');
|
||||
|
||||
setTimeout(() => {
|
||||
titleEl.textContent = title;
|
||||
|
||||
// Trigger reflow
|
||||
void titleEl.offsetWidth;
|
||||
|
||||
// Fade in
|
||||
titleEl.classList.remove('updating');
|
||||
}, 250);
|
||||
}
|
||||
@ -93,15 +85,12 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
if (btn) {
|
||||
btn.classList.add('loading');
|
||||
}
|
||||
showContainerLoading(true);
|
||||
|
||||
// Call your existing function
|
||||
prTabbedComponent(config.tableId, config.endpoint, function (success) {
|
||||
// Hide loading state
|
||||
if (btn) {
|
||||
btn.classList.remove('loading');
|
||||
}
|
||||
@ -127,14 +116,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize on DOM ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
|
||||
// Public API
|
||||
window.PRTabs = {
|
||||
switchTo: function (tabId) {
|
||||
const btn = document.querySelector(`.pr-tab-btn[data-tab="${tabId}"]`);
|
||||
@ -149,19 +136,17 @@
|
||||
return currentTab;
|
||||
},
|
||||
addTab: function (tabId, config) {
|
||||
// Dynamically add new tab configuration
|
||||
tabConfig[tabId] = config;
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
// Updated prTabbedComponent to work with callback
|
||||
function prTabbedComponent(id, endpoint, callback) {
|
||||
$.ajax({
|
||||
url: endpoint,
|
||||
type: 'GET',
|
||||
data: { TableId: id }, // Note: capital T to match your controller parameter
|
||||
data: { TableId: id },
|
||||
success: function (response) {
|
||||
$('#TabbedContainer').html(response);
|
||||
|
||||
|
||||
@ -2,25 +2,45 @@
|
||||
var loader = $('#overlay, #loader').css('z-index', 1100);
|
||||
$('#viewItemList').modal('show');
|
||||
$('#viewItemList').css('z-index', 1090);
|
||||
|
||||
tableElement = $('#ItemTable');
|
||||
tableName = '#ItemTable';
|
||||
|
||||
tableDestroy(tableElement);
|
||||
totalSelectedLabel = $('#totalSelectedItem');
|
||||
|
||||
clearTableSelection(tableName, selectedProductsMap, () => {
|
||||
totalSelectedLabel.text(0);
|
||||
}, '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({
|
||||
ajax: $.extend({
|
||||
serverSide: true,
|
||||
processing: true,
|
||||
searching: false,
|
||||
ajax: {
|
||||
url: '/ItemMgmt/GetItemList',
|
||||
type: 'POST',
|
||||
}, beforeComplete(loader)),
|
||||
type: 'GET',
|
||||
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,
|
||||
language: {
|
||||
emptyTable: "No record available"
|
||||
emptyTable: "No record available",
|
||||
processing: "Searching…"
|
||||
},
|
||||
initComplete: function () {
|
||||
initializeTableSelection({
|
||||
@ -51,35 +71,60 @@
|
||||
{ data: 'itemName' },
|
||||
{ data: 'itemDescription' },
|
||||
{ data: 'itemCategoryName' },
|
||||
{ data: 'qty' },
|
||||
{ data: 'qty' }
|
||||
],
|
||||
"columnDefs": [
|
||||
columnDefs: [
|
||||
{
|
||||
"targets": [5],
|
||||
"render": function (data, type, row) {
|
||||
return '<input type="number" class="editable-qty" style="width:60px;" value="' + data + '" />';
|
||||
targets: [5],
|
||||
render: function (data, type, row) {
|
||||
return `<input type="number" class="editable-qty"
|
||||
style="width:60px;" value="${data}" />`;
|
||||
}
|
||||
}
|
||||
],
|
||||
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
|
||||
});
|
||||
// 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 () {
|
||||
const $row = $(this).closest('tr');
|
||||
const rowData = itemListTable.row($row).data();
|
||||
if (!rowData) return;
|
||||
|
||||
const newQty = parseFloat($(this).val()) || 0;
|
||||
const itemId = `${rowData.itemNo}|${rowData.qty}`; // original key
|
||||
|
||||
// ✅ If this item is already selected, update its qty in the map
|
||||
const itemId = `${rowData.itemNo}|${rowData.qty}`;
|
||||
if (selectedProductsMap[itemId]) {
|
||||
selectedProductsMap[itemId].qty = newQty;
|
||||
}
|
||||
|
||||
// Update the DataTable's internal data too
|
||||
rowData.qty = newQty;
|
||||
});
|
||||
}
|
||||
@ -155,7 +200,6 @@ function getApproverName(prDetailsId, prNo) {
|
||||
const attestedBy = document.getElementById('label-pr-attestedBy');
|
||||
const approveBy = document.getElementById('label-pr-approvedBy');
|
||||
|
||||
// Security: Use textContent to prevent XSS
|
||||
if (attestedBy) attestedBy.textContent = item.attestedBy || '';
|
||||
if (approveBy) approveBy.textContent = item.approvedBy || '';
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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"> </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');
|
||||
}
|
||||
});
|
||||
}
|
||||
74
CPRNIMS.WebApps/wwwroot/css/Item/Item.css
Normal file
74
CPRNIMS.WebApps/wwwroot/css/Item/Item.css
Normal 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;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user