Update Inv Movement - ALL ITEM & QR

This commit is contained in:
Naz 2026-03-11 14:48:25 +08:00
parent 8276201e83
commit 8890cba87e
4 changed files with 848 additions and 456 deletions

View File

@ -184,6 +184,33 @@
style="width:100%;border-style: solid; border-width: 1px"></table> style="width:100%;border-style: solid; border-width: 1px"></table>
</div> </div>
</div> </div>
@* All Item Table *@
<div class="row card mt-3">
<div class="card-header">
<h2>All Item</h2>
</div>
<div class="card-body">
<div class="row mb-4 align-items-center">
<label class="col-sm-2 col-form-label" style="font-weight: bold;">Sort Location :</label>
<div class="col-sm-4">
<select class="form-select form-control" v-model="selectedLocation" v-on:change="updateAllItemsTable">
<option value="all">All Locations</option>
<option value="user">My Items</option>
<option v-for="station in PicStations" :key="station.stationId" :value="'station_' + station.stationId">
{{ station.stationName }}
</option>
</select>
</div>
</div>
<div class="table-responsive">
<table class="table table-bordered table-hover table-striped no-wrap" id="allItemsListDatatable" style="width:100%; border-style: solid; border-width: 1px"></table>
</div>
</div>
</div>
</div> </div>
<div v-if="sortBy === 'logs'"> <div v-if="sortBy === 'logs'">
@ -702,6 +729,24 @@
Next Next
</button> </button>
</div> </div>
@* QR Model *@
<div class="modal fade" id="zoomQrModal" tabindex="-1" aria-labelledby="zoomQrModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-sm">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="zoomQrModalLabel">QR Code</h5>
<button type="button" class="btn-close closeModal" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body d-flex justify-content-center">
<div id="zoomedQrContainer"></div>
</div>
<div class="modal-footer justify-content-center">
<h4 id="zoomedQrText" style="margin: 0; font-family: 'OCR A', monospace;"></h4>
</div>
</div>
</div>
</div>
</div> </div>
@section Scripts { @section Scripts {
@ -763,6 +808,8 @@
currentPageStation : 1, currentPageStation : 1,
itemsPerPageStation: 10, itemsPerPageStation: 10,
dropdownOpen: false, dropdownOpen: false,
selectedLocation: "all",
allItemsListDatatable: null,
} }
}, },
mounted() { mounted() {
@ -770,6 +817,11 @@
this.fetchStation(); this.fetchStation();
}, },
computed: { computed: {
PicStations() {
if (!this.currentUser) return [];
// Use == instead of === to safely compare string and int
return this.stations.filter(s => s.stationPicID == this.currentUser.id);
},
paginatedItems() { paginatedItems() {
const start = (this.currentPage - 1) * this.itemsPerPage; const start = (this.currentPage - 1) * this.itemsPerPage;
const end = start + this.itemsPerPage; const end = start + this.itemsPerPage;
@ -815,7 +867,6 @@
}, },
groupedByStation() { groupedByStation() {
let groupedByItem = this.items.reduce((acc, movement) => { let groupedByItem = this.items.reduce((acc, movement) => {
if (!acc[movement.uniqueID]) { if (!acc[movement.uniqueID]) {
acc[movement.uniqueID] = { acc[movement.uniqueID] = {
@ -843,6 +894,7 @@
} }
if (movements.length > 0) { if (movements.length > 0) {
let latestMovement = movements[0]; let latestMovement = movements[0];
let station = latestMovement.toStationName || latestMovement.lastStationName || "Not Assigned"; let station = latestMovement.toStationName || latestMovement.lastStationName || "Not Assigned";
@ -863,22 +915,23 @@
sortedKeys.forEach(key => { sortedKeys.forEach(key => {
sortedGrouped[key] = groupedByStation[key]; sortedGrouped[key] = groupedByStation[key];
}); });
return sortedGrouped; return sortedGrouped;
}, },
filteredItems() { filteredItems() {
if (!this.searchQuery.trim()) { if (!this.searchQuery.trim()) {
return this.groupedItems; return this.groupedItems;
} }
const searchLower = this.searchQuery.toLowerCase(); const searchLower = this.searchQuery.toLowerCase();
return Object.fromEntries( return Object.fromEntries(
Object.entries(this.groupedItems).filter(([_, group]) => Object.entries(this.groupedItems).filter(([_, group]) =>
group.uniqueID.toLowerCase().includes(searchLower) group.uniqueID.toLowerCase().includes(searchLower)
) )
); );
}, },
filteredStation() { filteredStation() {
if (!this.searchQueryStation) { if (!this.searchQueryStation) {
return this.groupedByStation; return this.groupedByStation;
@ -895,7 +948,6 @@
}); });
return filtered; return filtered;
}, },
}, },
methods: { methods: {
formatDate(dateString) { formatDate(dateString) {
@ -938,7 +990,9 @@
let modal = new bootstrap.Modal(document.getElementById("remarkModal")); let modal = new bootstrap.Modal(document.getElementById("remarkModal"));
modal.show(); modal.show();
}, },
updateAllItemsTable() {
this.initAllTables();
},
consignmentNote(consignmentNote) { consignmentNote(consignmentNote) {
if (!consignmentNote) { if (!consignmentNote) {
this.consignmentNoteUrl = "No consignment note available."; this.consignmentNoteUrl = "No consignment note available.";
@ -1058,7 +1112,7 @@
} else { } else {
const myStationIds = this.stations const myStationIds = this.stations
.filter(s => s.stationPicID === this.currentUser.id) .filter(s => s.stationPicID == this.currentUser.id)
.map(s => s.stationId); .map(s => s.stationId);
this.items = data.filter(item => this.items = data.filter(item =>
@ -1128,7 +1182,12 @@
} }
const data = await response.json(); const data = await response.json();
if (this.currentRole !== "Super Admin" && this.currentUser) {
this.stations = data.filter(s => s.stationPicID === this.currentUser.id);
} else {
this.stations = data; this.stations = data;
}
} catch (error) { } catch (error) {
console.error('Error fetching Station:', error); console.error('Error fetching Station:', error);
@ -1156,6 +1215,7 @@
this.itemMovementNotCompleteDatatable = $("#itemMovementNotCompleteDatatable").DataTable({ this.itemMovementNotCompleteDatatable = $("#itemMovementNotCompleteDatatable").DataTable({
data: this.items.filter((m) => m.movementComplete == 0), data: this.items.filter((m) => m.movementComplete == 0),
columns: [ columns: [
{ title: "Unique Id", data: "id" }, { title: "Unique Id", data: "id" },
{ title: "Product Name", data: "productName", render: (data, type, full) => { return `${data} <br> ${renderFile(full.productImage)}`; } }, { title: "Product Name", data: "productName", render: (data, type, full) => { return `${data} <br> ${renderFile(full.productImage)}`; } },
@ -1180,6 +1240,7 @@
this.itemMovementCompleteDatatable = $("#itemMovementCompleteDatatable").DataTable({ this.itemMovementCompleteDatatable = $("#itemMovementCompleteDatatable").DataTable({
data: this.items.filter((m) => m.movementComplete == 1 && m.action !== "Assign"), data: this.items.filter((m) => m.movementComplete == 1 && m.action !== "Assign"),
columns: [ columns: [
{ title: "Unique Id", data: "id" }, { title: "Unique Id", data: "id" },
{ title: "Product Name", data: "productName", render: (data, type, full) => { return `${data} <br> ${renderFile(full.productImage)}`; } }, { title: "Product Name", data: "productName", render: (data, type, full) => { return `${data} <br> ${renderFile(full.productImage)}`; } },
@ -1209,6 +1270,7 @@
m.action === "Change" || m.action === "Change" ||
m.toStation !== null m.toStation !== null
), ),
columns: [ columns: [
{ title: "Unique Id", data: "id" }, { title: "Unique Id", data: "id" },
{ title: "Product Name", data: "productName", render: (data, type, full) => { return `${data} <br> ${renderFile(full.productImage)}`; } }, { title: "Product Name", data: "productName", render: (data, type, full) => { return `${data} <br> ${renderFile(full.productImage)}`; } },
@ -1225,6 +1287,145 @@
responsive: true, responsive: true,
}); });
if (this.allItemsListDatatable) {
this.allItemsListDatatable.destroy();
}
let myStationIds = this.PicStations ? this.PicStations.map(s => s.stationId) : [];
let currentUserId = this.currentUser ? this.currentUser.id : null;
let currentUserStoreId = this.currentUser ? this.currentUser.store : null;
let latestMovementsMap = {};
this.items.forEach(movement => {
let id = movement.itemId;
if (!latestMovementsMap[id] || latestMovementsMap[id].id < movement.id) {
latestMovementsMap[id] = movement;
}
});
let latestMovements = Object.values(latestMovementsMap);
let allItemsData = [];
latestMovements.forEach(movement => {
let isDelivered = movement.latestStatus === 'Delivered';
let isReturned = movement.toOther === 'Return' &&
(movement.latestStatus === null || movement.latestStatus === 'Ready To Deploy');
if (isDelivered && !isReturned && movement.movementComplete == 1) {
// Check ownership (Is it with the user or their station or store)
let isWithUser = movement.toUser === currentUserId;
let isDeployedToUserStation = myStationIds.includes(movement.toStation);
let isAtUserStore = (currentUserStoreId !== null && movement.toStore === currentUserStoreId);
if (isWithUser || isDeployedToUserStation || isAtUserStore) {
if (this.selectedLocation === "all" || this.selectedLocation === "" || !this.selectedLocation) {
allItemsData.push(movement);
}
else if (this.selectedLocation === "user") {
// Under "My Items" - show items assigned to the user OR their store
if (isWithUser || isAtUserStore) {
allItemsData.push(movement);
}
}
else if (this.selectedLocation.startsWith("station_")) {
let stationId = parseInt(this.selectedLocation.split("_")[1]);
if (movement.toStation === stationId) {
allItemsData.push(movement);
}
}
}
}
});
this.allItemsListDatatable = $("#allItemsListDatatable").DataTable({
data: allItemsData,
columns: [
{
title: "Product Code",
data: "uniqueID",
render: function (data, type, row) {
const displayData = data ? data : "N/A";
return `<strong>${displayData}</strong><div id="qr_movement_${row.id}" class="mt-2"></div>`;
}
},
{
title: "Product Name",
data: "productName",
render: (data, type, full) => {
return `${data || 'Unknown'} <br> ${renderFile(full.productImage)}`;
}
},
{ title: "Current Location", data: "toStationName" },
{ title: "Receive Date", data: "receiveDate", render: this.formatDate.bind(this) },
{ title: "Send Date", data: "sendDate", render: this.formatDate.bind(this) },
{ title: "Action", data: "action" },
{ title: "From User", data: "lastUserName" },
{ title: "From Station", data: "lastStationName" },
{ title: "From Store", data: "lastStoreName" },
{ title: "Last User", data: "toUserName" },
{ title: "Last Station", data: "toStationName" },
{ title: "Last Store", data: "toStoreName" },
{ title: "Qty", data: "quantity" },
],
order: [[0, "desc"]],
responsive: true,
drawCallback: function (settings) {
setTimeout(() => {
const api = this.api();
api.rows().every(function () {
const data = this.data();
// Use data.id to ensure the element ID is unique per row
const containerId = `qr_movement_${data.id}`;
const container = document.getElementById(containerId);
if (!container) return;
container.innerHTML = "";
// Construct the full URL for the QR code dynamically
if (!data.uniqueID) return;
const qrText = `${window.location.origin}/I/${data.uniqueID}`;
// Generate Small QR
new QRCode(container, {
text: qrText,
width: 100,
height: 100,
colorDark: "#000000",
colorLight: "#ffffff",
correctLevel: QRCode.CorrectLevel.M
});
container.style.cursor = "pointer";
container.title = "Click to enlarge";
// Open Modal with Large QR
container.onclick = function () {
const zoomContainer = document.getElementById("zoomedQrContainer");
zoomContainer.innerHTML = "";
new QRCode(zoomContainer, {
text: qrText,
width: 250,
height: 250,
colorDark: "#000000",
colorLight: "#ffffff",
correctLevel: QRCode.CorrectLevel.M
});
document.getElementById("zoomedQrText").innerText = data.uniqueID;
$('#zoomQrModal').modal('show');
};
});
}, 100);
}
});
function renderFile(data, type, full, meta) { function renderFile(data, type, full, meta) {
if (!data) { if (!data) {
return "No Document"; return "No Document";
@ -1236,27 +1437,27 @@
if (isImage) { if (isImage) {
return `<a href="${data}" target="_blank" data-lightbox="image-1"> return `<a href="${data}" target="_blank" data-lightbox="image-1">
<img src="${data}" alt="Image" class="img-thumbnail" style="width: 100px; height: 100px;" /> <img src="${data}" alt="Image" class="img-thumbnail" style="width: 100px; height: 100px;" /></a>`;
</a>`;
} }
else if (isPdf) { else if (isPdf) {
return `<a href="${data}" target="_blank"> return `<a href="${data}" target="_blank">
<img src="https://upload.wikimedia.org/wikipedia/commons/8/87/PDF_file_icon.svg" <img src="https://upload.wikimedia.org/wikipedia/commons/8/87/PDF_file_icon.svg"
alt="PDF Document" class="img-thumbnail" alt="PDF Document" class="img-thumbnail"
style="width: 50px; height: 50px;" /> style="width: 50px; height: 50px;" />
<br>View PDF <br>View PDF</a>`;
</a>`;
} else { } else {
return `<a href="${data}" target="_blank">Download File</a>`; return `<a href="${data}" target="_blank">Download File</a>`;
} }
} }
}, },
toggleCategory(itemId) { toggleCategory(itemId) {
this.categoryVisible[itemId] = !this.categoryVisible[itemId]; this.categoryVisible[itemId] = !this.categoryVisible[itemId];
this.detailsVisible = {}; this.detailsVisible = {};
this.historyVisible = {}; this.historyVisible = {};
}, },
toggleHistory(itemId) { toggleHistory(itemId) {
// Jika item yang ditekan sudah terbuka, tutup // Jika item yang ditekan sudah terbuka, tutup
if (this.historyVisible[itemId]) { if (this.historyVisible[itemId]) {
@ -1269,10 +1470,10 @@
this.historyVisible[itemId] = true; this.historyVisible[itemId] = true;
} }
}, },
toggleDetails(movementId) { toggleDetails(movementId) {
this.detailsVisible[movementId] = !this.detailsVisible[movementId]; this.detailsVisible[movementId] = !this.detailsVisible[movementId];
}, },
}, },
directives: { directives: {
clickOutside: { clickOutside: {

View File

@ -924,7 +924,6 @@
isStationPIC() { isStationPIC() {
if (!this.thisItem || !this.stationlist || !this.currentUser) return false; if (!this.thisItem || !this.stationlist || !this.currentUser) return false;
// Now this.thisItem.toStation will have the ID from the backend
const targetStationId = this.thisItem.toStation; const targetStationId = this.thisItem.toStation;
return this.stationlist.some(station => return this.stationlist.some(station =>
@ -1244,7 +1243,6 @@
let receiveToStation = null; let receiveToStation = null;
if (this.thisItem.toStation) { if (this.thisItem.toStation) {
// Only keep Station. Clear User and Store as requested.
receiveToUser = null; receiveToUser = null;
receiveToStore = null; receiveToStore = null;
receiveToStation = this.thisItem.toStation; receiveToStation = this.thisItem.toStation;
@ -1258,19 +1256,16 @@
receiveToStore = null; receiveToStore = null;
} }
// 3. BINA PAKEJ DATA (Lengkap dengan Last & To)
const formData = { const formData = {
Id: this.thisItem.movementId, Id: this.thisItem.movementId,
ReceiveDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(), ReceiveDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(),
Remark: this.thisItem.remark, Remark: this.thisItem.remark,
LatestStatus: statusToSave, LatestStatus: statusToSave,
// Data Penerima (Update kepada siapa yang klik receive sekarang)
ToUser: receiveToUser, ToUser: receiveToUser,
ToStore: receiveToStore, ToStore: receiveToStore,
ToStation: receiveToStation, ToStation: receiveToStation,
// Data Penghantar (Kekalkan maklumat asal sebagai rujukan sejarah)
LastUser: this.thisItem.lastUser, LastUser: this.thisItem.lastUser,
LastStore: this.thisItem.lastStore, LastStore: this.thisItem.lastStore,
LastStation: this.thisItem.lastStation LastStation: this.thisItem.lastStation
@ -1570,13 +1565,10 @@
const formData = { const formData = {
ItemId: this.thisItem.itemID, ItemId: this.thisItem.itemID,
// ORIGIN: Where is it right now?
LastUser: this.thisItem.toUser, LastUser: this.thisItem.toUser,
LastStore: this.thisItem.toStore, LastStore: this.thisItem.toStore,
LastStation: this.thisItem.toStation, LastStation: this.thisItem.toStation,
// DESTINATION: Set to null here. The C# backend will query the DB
// and fill these in automatically based on history!
ToUser: null, ToUser: null,
ToStore: null, ToStore: null,
ToStation: null, ToStation: null,
@ -1615,7 +1607,7 @@
alert('Error: ' + error.message); alert('Error: ' + error.message);
} }
}, },
// Show the Station Modal
StationMessage() { StationMessage() {
this.selectedStation = ""; this.selectedStation = "";
$("#stationMessageModal").modal("show"); $("#stationMessageModal").modal("show");
@ -1630,7 +1622,6 @@
try { try {
const now = new Date(); const now = new Date();
// 1. LOGIC: Dynamically Identify the Origin (Where is the item right now?)
let originUser = null; let originUser = null;
let originStore = null; let originStore = null;
let originStation = null; let originStation = null;
@ -1642,18 +1633,16 @@
} else if (this.thisItem.toUser) { } else if (this.thisItem.toUser) {
originUser = this.thisItem.toUser; originUser = this.thisItem.toUser;
} else { } else {
originUser = this.currentUserId; // Fallback originUser = this.currentUserId;
} }
const formData = { const formData = {
ItemId: this.thisItem.itemID, ItemId: this.thisItem.itemID,
// ORIGINS: Shift the current location to the "Last" fields
LastStation: originStation, LastStation: originStation,
LastStore: originStore, LastStore: originStore,
LastUser: originUser, LastUser: originUser,
// DESTINATIONS: To the newly selected station (User and Store must be null)
ToStation: this.selectedStation, ToStation: this.selectedStation,
ToUser: null, ToUser: null,
ToStore: null, ToStore: null,
@ -1662,7 +1651,7 @@
Action: "Assign", Action: "Assign",
Quantity: this.thisItem.movementQuantity || 1, Quantity: this.thisItem.movementQuantity || 1,
Remark: this.remark ? (this.remark + ' / Deployed to station') : 'Deployed to station', Remark: this.remark ? (this.remark + ' / Deployed to station') : 'Deployed to station',
ConsignmentNote: this.document, // Ensure this matches how your file is stored in Vue data ConsignmentNote: this.document,
SendDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(), SendDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(),
Date: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(), Date: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(),
ReceiveDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(), ReceiveDate: new Date(now.getTime() + 8 * 60 * 60 * 1000).toISOString(),

View File

@ -152,6 +152,33 @@
style="width:100%;border-style: solid; border-width: 1px"></table> style="width:100%;border-style: solid; border-width: 1px"></table>
</div> </div>
</div> </div>
@* All Item User *@
<div class="row card mt-3">
<div class="card-header">
<h2>All Item</h2>
</div>
<div class="card-body">
<div class="row mb-4 align-items-center">
<label class="col-sm-2 col-form-label" style="font-weight: bold;">Sort Location :</label>
<div class="col-sm-4">
<select class="form-select form-control" v-model="selectedLocation" v-on:change="updateAllItemsTable">
<option value="all">All Locations</option>
<option value="user">My Items</option>
<option v-for="station in stations" :key="station.stationId" :value="'station_' + station.stationId">
{{ station.stationName }}
</option>
</select>
</div>
</div>
<div class="table-responsive">
<table class="table table-bordered table-hover table-striped no-wrap" id="allItemsListDatatable" style="width:100%; border-style: solid; border-width: 1px"></table>
</div>
</div>
</div>
</div> </div>
<!--------------------------------------------ITEM CATEGORY----------------------------------------------------------------------> <!--------------------------------------------ITEM CATEGORY---------------------------------------------------------------------->
@ -629,6 +656,24 @@
Next Next
</button> </button>
</div> </div>
@* QR Modal *@
<div class="modal fade" id="zoomQrModal" tabindex="-1" aria-labelledby="zoomQrModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-sm">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="zoomQrModalLabel">QR Code</h5>
<button type="button" class="btn-close closeModal" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body d-flex justify-content-center">
<div id="zoomedQrContainer"></div>
</div>
<div class="modal-footer justify-content-center">
<h4 id="zoomedQrText" style="margin: 0; font-family: 'OCR A', monospace;"></h4>
</div>
</div>
</div>
</div>
</div> </div>
@section Scripts { @section Scripts {
@ -663,6 +708,8 @@
dropdownOpen: false, dropdownOpen: false,
currentUser: null, currentUser: null,
stations:[], stations:[],
selectedLocation: "all",
allItemsListDatatable: null,
}; };
}, },
computed: { computed: {
@ -823,11 +870,14 @@
}, },
}, },
async mounted() { async mounted() {
this.fetchItemMovement(); try {
await this.fetchUser(); await this.fetchUser();
await Promise.all([ await this.fetchStation();
this.fetchStation(), await this.fetchItemMovement();
]);
} catch (error) {
console.error("Initialization failed:", error);
}
}, },
methods: { methods: {
formatDate(dateString) { formatDate(dateString) {
@ -876,7 +926,159 @@
let modal = new bootstrap.Modal(document.getElementById("remarkModal")); let modal = new bootstrap.Modal(document.getElementById("remarkModal"));
modal.show(); modal.show();
}, },
updateAllItemsTable() {
if (this.allItemsListDatatable) {
this.allItemsListDatatable.destroy();
}
let filteredData = [];
let latestMovementsMap = {};
this.itemMovements.forEach(movement => {
let id = movement.itemId;
if (!latestMovementsMap[id] || latestMovementsMap[id].id < movement.id) {
latestMovementsMap[id] = movement;
}
});
let latestMovements = Object.values(latestMovementsMap);
latestMovements.forEach(movement => {
let isReturned = movement.toOther === 'Return' && movement.movementComplete == 1;
let isCanceled = movement.latestStatus === 'Ready To Deploy' && movement.movementComplete == 1;
if (!isReturned && !isCanceled && movement.movementComplete == 1) {
// Check if the item is currently with the user OR deployed to a station
let isWithUser = movement.toUser === this.userId;
// Extract all station IDs where the current user is the PIC
let userStationIds = this.stations.map(s => s.stationId);
let isDeployedToUserStation = userStationIds.includes(movement.toStation);
if (isWithUser || isDeployedToUserStation) {
if (this.selectedLocation === "all" || this.selectedLocation === "" || !this.selectedLocation) {
filteredData.push(movement);
}
else if (this.selectedLocation === "user") {
if (isWithUser) {
filteredData.push(movement);
}
}
else if (this.selectedLocation.startsWith("station_")) {
let stationId = parseInt(this.selectedLocation.split("_")[1]);
if (movement.toStation === stationId) {
filteredData.push(movement);
}
}
}
}
});
function renderFile(data, type, full, meta) {
if (!data) {
return "No Document";
}
var isImage = /\.(jpeg|jpg|png|gif)$/i.test(data);
var isPdf = /\.pdf$/i.test(data);
if (isImage) {
return `<a href="${data}" target="_blank" data-lightbox="image-1">
<img src="${data}" alt="Image" class="img-thumbnail" style="width: 100px; height: 100px;" />
</a>`;
} else if (isPdf) {
return `<a href="${data}" target="_blank">
<img src="https://upload.wikimedia.org/wikipedia/commons/8/87/PDF_file_icon.svg"
alt="PDF Document" class="img-thumbnail" style="width: 50px; height: 50px;" />
<br>View PDF
</a>`;
} else {
return `<a href="${data}" target="_blank">Download File</a>`;
}
}
this.allItemsListDatatable = $("#allItemsListDatatable").DataTable({
data: filteredData,
columns: [
{
title: "Unique ID",
data: "uniqueID",
render: function (data, type, row) {
return `
<strong>${data}</strong>
<div id="qr_movement_${row.itemId}" class="mt-2"></div>
`;
}
},
{ title: "Product Name", data: "productName", render: (data, type, full) => { return `${data} <br> ${renderFile(full.productImage)}`; } },
{ title: "Current Location", data: null, render: (data, type, row) => {
return row.toStationName ? row.toStationName : (row.toUserName ? row.toUserName : "Unknown");
}},
{ title: "Receive Date", data: "receiveDate", render: this.formatDate.bind(this) },
{ title: "Send Date", data: "sendDate", render: this.formatDate.bind(this) },
{ title: "Action", data: "action" },
{ title: "From User", data: "lastUserName" },
{ title: "From Station", data: "lastStationName" },
{ title: "From Store", data: "lastStoreName" },
{ title: "Last User", data: "toUserName" },
{ title: "Last Station", data: "toStationName" },
{ title: "Last Store", data: "toStoreName" },
{ title: "Quantity", data: "quantity" },
],
responsive: true,
drawCallback: function (settings) {
setTimeout(() => {
const api = this.api();
api.rows().every(function () {
const data = this.data();
const containerId = `qr_movement_${data.itemId}`;
const container = document.getElementById(containerId);
if (!container) return;
container.innerHTML = "";
const qrText = data.qrString ? data.qrString : data.uniqueID;
if (!qrText) return;
// small QR
new QRCode(container, {
text: qrText,
width: 100,
height: 100,
colorDark: "#000000",
colorLight: "#ffffff",
correctLevel: QRCode.CorrectLevel.M
});
container.style.cursor = "pointer";
container.title = "Click to enlarge";
container.onclick = function () {
const zoomContainer = document.getElementById("zoomedQrContainer");
zoomContainer.innerHTML = "";
// big QR
new QRCode(zoomContainer, {
text: qrText,
width: 250,
height: 250,
colorDark: "#000000",
colorLight: "#ffffff",
correctLevel: QRCode.CorrectLevel.M
});
document.getElementById("zoomedQrText").innerText = data.uniqueID;
$('#zoomQrModal').modal('show');
};
});
}, 100);
},
});
},
consignmentNote(consignmentNote) { consignmentNote(consignmentNote) {
if (!consignmentNote) { if (!consignmentNote) {
this.consignmentNoteUrl = "No consignment note available."; this.consignmentNoteUrl = "No consignment note available.";
@ -884,10 +1086,8 @@
return; return;
} }
// Pastikan URL betul
this.consignmentNoteUrl = consignmentNote; this.consignmentNoteUrl = consignmentNote;
// Tunggu Vue update sebelum buka modal
this.$nextTick(() => { this.$nextTick(() => {
new bootstrap.Modal(document.getElementById('consignmentModal')).show(); new bootstrap.Modal(document.getElementById('consignmentModal')).show();
}); });
@ -960,6 +1160,7 @@
renderTables() { renderTables() {
if (this.sortBy === "all") { if (this.sortBy === "all") {
this.initAllTables(); this.initAllTables();
this.updateAllItemsTable(); // Initialize the new list table on load
} }
}, },

View File

@ -1382,7 +1382,8 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
i.ToStation, i.ToStation,
i.LatestStatus, i.LatestStatus,
i.receiveDate, i.receiveDate,
i.MovementComplete i.MovementComplete,
QRString = $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host.Value}/I/{i.Item?.UniqueID}"
})); }));
} }
catch (Exception ex) catch (Exception ex)