Update & Fix Eerror

This commit is contained in:
ArifHilmi 2025-03-20 15:46:52 +08:00
parent 7a08ac374a
commit d6d151a3c7
11 changed files with 501 additions and 184 deletions

View File

@ -4,7 +4,6 @@
ViewData["Title"] = "Forgot your password?"; ViewData["Title"] = "Forgot your password?";
} }
<h1>@ViewData["Title"]</h1>
<h2>Enter your email.</h2> <h2>Enter your email.</h2>
<hr /> <hr />
<div class="row"> <div class="row">

View File

@ -4,7 +4,6 @@
ViewData["Title"] = "Forgot password confirmation"; ViewData["Title"] = "Forgot password confirmation";
} }
<h1>@ViewData["Title"]</h1>
<p> <p>
Please check your email to reset your password. Please check your email to reset your password.
</p> </p>

View File

@ -44,10 +44,57 @@
color: purple; color: purple;
} }
.ms-auto { .ms-auto {
margin-left: auto !important; /* Push Complete/Incomplete to right */ margin-left: auto !important; /* Push Complete/Incomplete to right */
} }
.dropdown {
position: relative;
width: 100%;
}
.dropdown-toggle-box {
display: flex;
align-items: center;
border: 1px solid #ddd;
border-radius: 5px;
overflow: hidden;
}
.dropdown-toggle-box input {
flex: 1;
border: none;
padding: 8px;
}
.dropdown-btn {
border: none;
background-color: #007bff;
color: white;
padding: 8px 12px;
cursor: pointer;
}
.dropdown-content {
position: absolute;
width: 100%;
max-height: 200px;
overflow-y: auto;
background: #fff;
border: 1px solid #ddd;
z-index: 10;
}
.dropdown-content option {
padding: 10px;
cursor: pointer;
display: block;
}
.dropdown-content option:hover {
background-color: #f1f1f1;
}
</style> </style>
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartial.cshtml") @await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartial.cshtml")
<div id="registerItem" class="row"> <div id="registerItem" class="row">
@ -73,14 +120,31 @@
<div class="row mb-3" v-if="sortBy === 'station'"> <div class="row mb-3" v-if="sortBy === 'station'">
<h4 class="col-sm-1 col-form-h2" style="min-width:150px;">Search Station:</h4> <h4 class="col-sm-1 col-form-h2" style="min-width:150px;">Search Station:</h4>
<div class="col-sm-4"> <div class="col-sm-4">
<input type="text" class="form-control" v-model="searchStation" placeholder="Search by station name..."> <div class="dropdown" v-click-outside="closeDropdown">
<!-- Button + Input dalam satu box -->
<div class="dropdown-toggle-box" v-on:click="dropdownOpen = !dropdownOpen">
<input type="text" class="form-control" v-model="searchQueryStation"
placeholder="Search Station..." v-on:focus="dropdownOpen = true" v-on:click.stop />
<button type="button" class="btn btn-primary dropdown-btn" v-on:click.stop="dropdownOpen = !dropdownOpen">
</button>
</div>
<!-- Dropdown list -->
<div v-if="dropdownOpen" class="dropdown-content" v-on:click.stop>
<div v-for="(item, index) in stations"
:key="index" class="dropdown-item" v-on:mousedown.prevent="selectStation(item)">
{{ item.stationName }}
</div>
</div>
</div>
</div> </div>
</div> </div>
<div v-if="sortBy === 'all'"> <div v-if="sortBy === 'all'">
<div class="row card"> <div class="row card">
<div class="card-header"> <div class="card-header">
<h2>Pending Item Movement</h2> <h2>Pending Item Transit</h2>
</div> </div>
<div class="card-body"> <div class="card-body">
<div v-if="loading"> <div v-if="loading">
@ -96,6 +160,9 @@
<div class="card-header"> <div class="card-header">
<h2>Complete Item Movement</h2> <h2>Complete Item Movement</h2>
</div> </div>
<div class="card-header">
<h4>All Item Movement</h4>
</div>
<div class="card-body"> <div class="card-body">
<div v-if="loading"> <div v-if="loading">
<div class="spinner-border text-info" role="status"> <div class="spinner-border text-info" role="status">
@ -104,6 +171,18 @@
</div> </div>
<table class="table table-bordered table-hover table-striped no-wrap" id="itemMovementCompleteDatatable" style="width:100%;border-style: solid; border-width: 1px"></table> <table class="table table-bordered table-hover table-striped no-wrap" id="itemMovementCompleteDatatable" style="width:100%;border-style: solid; border-width: 1px"></table>
</div> </div>
<div class="card-header">
<h4>Assign Station</h4>
</div>
<div class="card-body">
<div v-if="loading">
<div class="spinner-border text-info" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<table class="table table-bordered table-hover table-striped no-wrap" id="assignStationDatatable"
style="width:100%;border-style: solid; border-width: 1px"></table>
</div>
</div> </div>
</div> </div>
@ -130,7 +209,7 @@
<span class="visually-hidden">Loading...</span> <span class="visually-hidden">Loading...</span>
</div> </div>
</div> </div>
<div v-for="(group, itemId) in filteredItems" :key="itemId" class="row card"> <div v-for="(group, itemId) in paginatedItems" :key="itemId" class="row card">
<div class="card-header d-flex justify-content-between align-items-center"> <div class="card-header d-flex justify-content-between align-items-center">
@* <h2>Item : {{ group.uniqueID }}</h2> *@ @* <h2>Item : {{ group.uniqueID }}</h2> *@
<h2 v-if="group.uniqueID">Item : {{ group.uniqueID }}</h2> <h2 v-if="group.uniqueID">Item : {{ group.uniqueID }}</h2>
@ -159,13 +238,13 @@
</h3> </h3>
<!-- Send Date --> <!-- Send Date -->
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:285px; min-width:285px;"> <div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:310px; min-width:310px;">
<h4 class="fixed-label m-0 text-nowrap">{{movement.action === 'Assign' ? 'Assign Date' : 'Send Date'}}</h4> <h4 class="fixed-label m-0 text-nowrap">{{movement.action === 'Assign' ? 'Assign Date' : movement.action === 'Register' ? 'Register Date' : 'Send Date'}}</h4>
<span class="fixed-value text-truncate">{{ movement.sendDate }}</span> <span class="fixed-value text-truncate">{{movement.action !== 'Register' ? movement.sendDate : movement.date }}</span>
</div> </div>
<!-- Receive Date --> <!-- Receive Date -->
<div v-if="movement.action !== 'Assign'" class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:290px; min-width:290px;"> <div v-if="movement.action !== 'Assign' && movement.action !== 'Register'" class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:290px; min-width:290px;">
<h4 class="fixed-label m-0 text-nowrap">Receive Date:</h4> <h4 class="fixed-label m-0 text-nowrap">Receive Date:</h4>
<span class="fixed-value text-truncate" style="max-width:160px;">{{ movement.receiveDate || 'Not arrive' }}</span> <span class="fixed-value text-truncate" style="max-width:160px;">{{ movement.receiveDate || 'Not arrive' }}</span>
</div> </div>
@ -260,13 +339,13 @@
</h3> </h3>
<!-- Send Date --> <!-- Send Date -->
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:285px; min-width:285px;"> <div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:310px; min-width:310px;">
<h4 class="fixed-label m-0 text-nowrap">{{movement.action === 'Assign' ? 'Assign Date' : 'Send Date'}}</h4> <h4 class="fixed-label m-0 text-nowrap">{{movement.action === 'Assign' ? 'Assign Date' : movement.action === 'Register' ? 'Register Date' : 'Send Date'}}</h4>
<span class="fixed-value text-truncate">{{ movement.sendDate }}</span> <span class="fixed-value text-truncate">{{movement.action !== 'Register' ? movement.sendDate : movement.date }}</span>
</div> </div>
<!-- Receive Date --> <!-- Receive Date -->
<div v-if="movement.action !== 'Assign'" class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:290px; min-width:290px;"> <div v-if="movement.action !== 'Assign' && movement.action !== 'Register'" class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:290px; min-width:290px;">
<h4 class="fixed-label m-0 text-nowrap">Receive Date:</h4> <h4 class="fixed-label m-0 text-nowrap">Receive Date:</h4>
<span class="fixed-value text-truncate" style="max-width:160px;">{{ movement.receiveDate || 'Not arrive' }}</span> <span class="fixed-value text-truncate" style="max-width:160px;">{{ movement.receiveDate || 'Not arrive' }}</span>
</div> </div>
@ -347,11 +426,9 @@
</div> </div>
</div> </div>
</div> </div>
<!--------------------------------------------STATION CATEGORY----------------------------------------------------------------------> <!--------------------------------------------STATION CATEGORY---------------------------------------------------------------------->
<div v-if="sortBy === 'station'"> <div v-if="sortBy === 'station'">
<div v-for="(items, station) in filteredStation" :key="stationName" <div v-for="(items, station) in filteredStation " :key="stationName"
:class="{'bg-light-gray': station === 'Unassign Station', 'bg-white': station !== 'Unassign Station'}" class="station-category card mt-3"> :class="{'bg-light-gray': station === 'Unassign Station', 'bg-white': station !== 'Unassign Station'}" class="station-category card mt-3">
<!-- Station Header --> <!-- Station Header -->
<div class="card-header d-flex justify-content-between align-items-center"> <div class="card-header d-flex justify-content-between align-items-center">
@ -363,7 +440,7 @@
<!-- Show Items Under Each Station --> <!-- Show Items Under Each Station -->
<div v-show="categoryVisible[station]" class="card-body"> <div v-show="categoryVisible[station]" class="card-body">
<div v-for="(group, itemId) in items" :key="itemId" class="row card"> <div v-for="(group, itemId) in paginatedItemsStation(items)" :key="itemId" class="row card">
<!-- Item Header --> <!-- Item Header -->
<div class="card-header d-flex justify-content-between align-items-center"> <div class="card-header d-flex justify-content-between align-items-center">
@* <h2>Item : {{ group.uniqueID }}</h2> *@ @* <h2>Item : {{ group.uniqueID }}</h2> *@
@ -391,13 +468,13 @@
</h3> </h3>
<!-- Send Date --> <!-- Send Date -->
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:285px; min-width:285px;"> <div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:310px; min-width:310px;">
<h4 class="fixed-label m-0 text-nowrap">{{movement.action === 'Assign' ? 'Assign Date' : 'Send Date'}}</h4> <h4 class="fixed-label m-0 text-nowrap">{{movement.action === 'Assign' ? 'Assign Date' : movement.action === 'Register' ? 'Register Date' : 'Send Date'}}</h4>
<span class="fixed-value text-truncate">{{ movement.sendDate }}</span> <span class="fixed-value text-truncate">{{movement.action !== 'Register' ? movement.sendDate : movement.date }}</span>
</div> </div>
<!-- Receive Date --> <!-- Receive Date -->
<div v-if="movement.action !== 'Assign'" class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:290px; min-width:290px;"> <div v-if="movement.action !== 'Assign' && movement.action !== 'Register'" class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:290px; min-width:290px;">
<h4 class="fixed-label m-0 text-nowrap">Receive Date:</h4> <h4 class="fixed-label m-0 text-nowrap">Receive Date:</h4>
<span class="fixed-value text-truncate" style="max-width:160px;">{{ movement.receiveDate || 'Not arrive' }}</span> <span class="fixed-value text-truncate" style="max-width:160px;">{{ movement.receiveDate || 'Not arrive' }}</span>
</div> </div>
@ -485,13 +562,13 @@
</h3> </h3>
<!-- Send Date --> <!-- Send Date -->
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:285px; min-width:285px;"> <div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:310px; min-width:310px;">
<h4 class="fixed-label m-0 text-nowrap">{{movement.action === 'Assign' ? 'Assign Date' : 'Send Date'}}</h4> <h4 class="fixed-label m-0 text-nowrap">{{movement.action === 'Assign' ? 'Assign Date' : movement.action === 'Register' ? 'Register Date' : 'Send Date'}}</h4>
<span class="fixed-value text-truncate">{{ movement.sendDate }}</span> <span class="fixed-value text-truncate">{{movement.action !== 'Register' ? movement.sendDate : movement.date }}</span>
</div> </div>
<!-- Receive Date --> <!-- Receive Date -->
<div v-if="movement.action !== 'Assign'" class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:290px; min-width:290px;"> <div v-if="movement.action !== 'Assign' && movement.action !== 'Register'" class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:290px; min-width:290px;">
<h4 class="fixed-label m-0 text-nowrap">Receive Date:</h4> <h4 class="fixed-label m-0 text-nowrap">Receive Date:</h4>
<span class="fixed-value text-truncate" style="max-width:160px;">{{ movement.receiveDate || 'Not arrive' }}</span> <span class="fixed-value text-truncate" style="max-width:160px;">{{ movement.receiveDate || 'Not arrive' }}</span>
</div> </div>
@ -602,6 +679,28 @@
</div> </div>
</div> </div>
<div class="d-flex justify-content-center align-items-center mt-3" v-if="sortBy === 'item' && totalPages > 0">
<button class="btn btn-secondary mx-1" v-on:click="goToPage(currentPage - 1)" :disabled="currentPage === 1">
Previous
</button>
<span class="mx-2">Page {{ currentPage }} of {{ totalPages }}</span>
<button class="btn btn-secondary mx-1" v-on:click="goToPage(currentPage + 1)" :disabled="currentPage === totalPages">
Next
</button>
</div>
<div class="d-flex justify-content-center align-items-center mt-3" v-if="sortBy === 'station' && totalPagesStation > 0">
<button class="btn btn-secondary mx-1" v-on:click="goToPageStation(currentPageStation - 1)" :disabled="currentPageStation === 1">
Previous
</button>
<span class="mx-2">Page {{ currentPageStation }} of {{ itemsPerPageStation }}</span>
<button class="btn btn-secondary mx-1" v-on:click="goToPageStation(currentPageStation + 1)" :disabled="currentPageStation === totalPages">
Next
</button>
</div> </div>
</div> </div>
@ -644,6 +743,7 @@
selectedTeamType: "", selectedTeamType: "",
selectedtoStation: "", selectedtoStation: "",
consignmentNoteUrl: "", consignmentNoteUrl: "",
assignStationDatatable: null,
showItemModal: false, showItemModal: false,
loading: false, loading: false,
items: [], items: [],
@ -651,20 +751,39 @@
currentUserCompanyDept: null, currentUserCompanyDept: null,
sortBy: "all", sortBy: "all",
searchQuery: "", searchQuery: "",
searchQueryStation: "",
categoryVisible: {}, categoryVisible: {},
historyVisible: {}, historyVisible: {},
detailsVisible: {}, detailsVisible: {},
currentRole:"", currentRole:"",
stationName: "", stationName: "",
searchStation: "", searchStation: "",
currentPage: 1,
itemsPerPage: 10,
currentPageStation : 1,
itemsPerPageStation: 10,
dropdownOpen: false,
} }
}, },
mounted() { mounted() {
this.fetchItem(); this.fetchItem();
this.fetchStation();
}, },
computed: { computed: {
paginatedItems() {
const start = (this.currentPage - 1) * this.itemsPerPage;
const end = start + this.itemsPerPage;
return Object.fromEntries(
Object.entries(this.filteredItems).slice(start, end)
);
},
totalPages() {
return Math.ceil(Object.keys(this.filteredItems).length / this.itemsPerPage);
},
totalPagesStation() {
return Math.ceil(Object.keys(this.filteredStation).length / this.itemsPerPage);
},
groupedItems() { groupedItems() {
let grouped = this.items.reduce((acc, movement) => { let grouped = this.items.reduce((acc, movement) => {
if (!acc[movement.itemId]) { if (!acc[movement.itemId]) {
@ -761,16 +880,16 @@
}, },
filteredStation() { filteredStation() {
if (!this.searchStation) { if (!this.searchQueryStation) {
return this.groupedByStation; return this.groupedByStation;
} }
let searchQuery = this.searchStation.toLowerCase(); let searchQueryStation = this.toLowerCase();
let grouped = this.groupedByStation; let grouped = this.groupedByStation;
let filtered = {}; let filtered = {};
Object.keys(grouped).forEach(station => { Object.keys(grouped).forEach(station => {
if (station.toLowerCase().includes(searchQuery)) { if (station.toLowerCase().includes(searchQueryStation)) {
filtered[station] = grouped[station]; filtered[station] = grouped[station];
} }
}); });
@ -780,6 +899,30 @@
}, },
methods: { methods: {
paginatedItemsStation(item) {
const start = (this.currentPageStation - 1) * this.itemsPerPageStation;
const end = start + this.itemsPerPageStation;
return Object.fromEntries(
Object.entries(item).slice(start, end)
);
},
selectStation(item) {
this.searchQueryStation = item.stationName;
this.dropdownOpen = false;
},
closeDropdown() {
this.dropdownOpen = false; // Tutup dropdown
},
goToPage(page) {
if (page >= 1 && page <= this.totalPages) {
this.currentPage = page;
}
},
goToPageStation(page) {
if (page >= 1 && page <= this.itemsPerPageStation) {
this.currentPageStation = page;
}
},
remark(remark) { remark(remark) {
document.getElementById("remarkContent").innerText = remark || "No remark message provide."; document.getElementById("remarkContent").innerText = remark || "No remark message provide.";
let modal = new bootstrap.Modal(document.getElementById("remarkModal")); let modal = new bootstrap.Modal(document.getElementById("remarkModal"));
@ -963,6 +1106,25 @@
console.error('There was a problem with the fetch operation:', error); console.error('There was a problem with the fetch operation:', error);
} }
}, },
async fetchStation() {
try {
const response = await fetch('/InvMainAPI/StationList', {
method: 'POST', // Specify the HTTP method
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error('Failed to fetch Station');
}
const data = await response.json();
this.stations = data;
} catch (error) {
console.error('Error fetching Station:', error);
}
},
handleSorting() { handleSorting() {
this.renderTables(); this.renderTables();
}, },
@ -979,11 +1141,15 @@
if (this.itemMovementCompleteDatatable) { if (this.itemMovementCompleteDatatable) {
this.itemMovementCompleteDatatable.destroy(); this.itemMovementCompleteDatatable.destroy();
} }
if (this.assignStationDatatable) {
this.assignStationDatatable.destroy();
}
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 Code", data: "uniqueID" }, { title: "Product Code", data: "uniqueID" },
{ title: "Action", data: "action" }, { title: "Action", data: "action" },
{ title: "Send Date", data: "sendDate" }, { title: "Send Date", data: "sendDate" },
@ -994,35 +1160,7 @@
{ title: "Start Status", data: "toOther" }, { title: "Start Status", data: "toOther" },
{ title: "Product Category", data: "productCategory" }, { title: "Product Category", data: "productCategory" },
{ title: "Qty", data: "quantity" }, { title: "Qty", data: "quantity" },
{ { title: "Note", data: "consignmentNote", render: renderFile },
title: "Note",
data: "consignmentNote",
render: function (data, type, full, meta) {
if (!data) {
return "No Document";
}
// Check if the document is an image based on file extension
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>`;
}
},
},
{ title: "Remark", data: "remark" }, { title: "Remark", data: "remark" },
], ],
order: [[0, "desc"]], order: [[0, "desc"]],
@ -1030,9 +1168,10 @@
}); });
this.itemMovementCompleteDatatable = $("#itemMovementCompleteDatatable").DataTable({ this.itemMovementCompleteDatatable = $("#itemMovementCompleteDatatable").DataTable({
data: this.items.filter((m) => m.movementComplete == 1), 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 Code", data: "uniqueID" }, { title: "Product Code", data: "uniqueID" },
{ title: "Send Date", data: "sendDate" }, { title: "Send Date", data: "sendDate" },
{ title: "Receive Date", data: "receiveDate" }, { title: "Receive Date", data: "receiveDate" },
@ -1046,55 +1185,32 @@
{ title: "Start Status", data: "toOther" }, { title: "Start Status", data: "toOther" },
{ title: "Latest Status", data: "latestStatus" }, { title: "Latest Status", data: "latestStatus" },
{ title: "Product Category", data: "productCategory" }, { title: "Product Category", data: "productCategory" },
{ title: "Qty", data: "quantity" }, { title: "Note", data: "consignmentNote", render: renderFile },
{ title: "Note",
data: "consignmentNote",
render: function (data, type, full, meta) {
if (!data) {
return "No Document";
}
// Check if the document is an image based on file extension
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>`;
}
},
},
{ title: "Remark", data: "remark" }, { title: "Remark", data: "remark" },
], ],
order: [[0, "desc"]], order: [[0, "desc"]],
responsive: true, responsive: true,
}); });
this.stationDatatable = $("#stationDatatable").DataTable({ this.assignStationDatatable = $("#assignStationDatatable").DataTable({
data: this.items.filter((m) => m.action === "Assign" ), data: this.items.filter((m) => 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 Code", data: "uniqueID" }, { title: "Product Code", data: "uniqueID" },
{ title: "Assign Date", data: "sendDate" }, { title: "Assign Date", data: "sendDate" },
{ title: "From User", data: "toUserName" }, { title: "Action", data: "action" },
{ title: "Station User PIC", data: "toUserName" },
{ title: "From Station", data: "toStationName" }, { title: "From Station", data: "toStationName" },
{ title: "Last Station", data: "lastStationName" }, { title: "Last Station", data: "lastStationName" },
{ title: "Qty", data: "quantity" }, { title: "Qty", data: "quantity" },
{ { title: "Note", data: "consignmentNote", render: renderFile },
title: "Note", { title: "Remark", data: "remark" },
data: "consignmentNote", ],
render: function (data, type, full, meta) { responsive: true,
});
function renderFile(data, type, full, meta) {
if (!data) { if (!data) {
return "No Document"; return "No Document";
} }
@ -1118,12 +1234,7 @@
} else { } else {
return `<a href="${data}" target="_blank">Download File</a>`; return `<a href="${data}" target="_blank">Download File</a>`;
} }
}, }
},
{ title: "Remark", data: "remark" },
],
responsive: true,
});
}, },
toggleCategory(itemId) { toggleCategory(itemId) {
this.categoryVisible[itemId] = !this.categoryVisible[itemId]; this.categoryVisible[itemId] = !this.categoryVisible[itemId];
@ -1148,6 +1259,21 @@
}, },
}, },
directives: {
clickOutside: {
beforeMount(el, binding) {
el.clickOutsideEvent = (event) => {
if (!(el.contains(event.target))) {
binding.value?.(); // Guna optional chaining untuk elak error
}
};
document.body.addEventListener("click", el.clickOutsideEvent);
},
unmounted(el) {
document.body.removeEventListener("click", el.clickOutsideEvent);
}
}
}
}); });
</script> </script>
} }

View File

@ -28,6 +28,52 @@
margin-left: auto !important; /* Push Complete/Incomplete to right */ margin-left: auto !important; /* Push Complete/Incomplete to right */
} }
.dropdown {
position: relative;
width: 100%;
}
.dropdown-toggle-box {
display: flex;
align-items: center;
border: 1px solid #ddd;
border-radius: 5px;
overflow: hidden;
}
.dropdown-toggle-box input {
flex: 1;
border: none;
padding: 8px;
}
.dropdown-btn {
border: none;
background-color: #007bff;
color: white;
padding: 8px 12px;
cursor: pointer;
}
.dropdown-content {
position: absolute;
width: 100%;
max-height: 200px;
overflow-y: auto;
background: #fff;
border: 1px solid #ddd;
z-index: 10;
}
.dropdown-content option {
padding: 10px;
cursor: pointer;
display: block;
}
.dropdown-content option:hover {
background-color: #f1f1f1;
}
</style> </style>
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartialUser.cshtml") @await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartialUser.cshtml")
@ -54,14 +100,32 @@
<div class="row mb-3" v-if="sortBy === 'station'"> <div class="row mb-3" v-if="sortBy === 'station'">
<h4 class="col-sm-1 col-form-h2" style="min-width:150px;">Search Station:</h4> <h4 class="col-sm-1 col-form-h2" style="min-width:150px;">Search Station:</h4>
<div class="col-sm-4"> <div class="col-sm-4">
<input type="text" class="form-control" v-model="searchStation" placeholder="Search by station name..."> <div class="dropdown" v-click-outside="closeDropdown">
<!-- Button + Input dalam satu box -->
<div class="dropdown-toggle-box" v-on:click="dropdownOpen = !dropdownOpen">
<input type="text" class="form-control" v-model="searchQueryStation"
placeholder="Search Station..." v-on:focus="dropdownOpen = true" v-on:click.stop />
<button type="button" class="btn btn-primary dropdown-btn" v-on:click.stop="dropdownOpen = !dropdownOpen">
</button>
</div>
<!-- Dropdown list -->
<div v-if="dropdownOpen" class="dropdown-content" v-on:click.stop>
<div v-for="(item, index) in stations"
:key="index" class="dropdown-item" v-on:mousedown.prevent="selectStation(item)">
{{ item.stationName }}
</div>
</div>
</div>
</div> </div>
</div> </div>
<div v-if="sortBy === 'all'"> <div v-if="sortBy === 'all'">
<h5 style="color:cadetblue">*Each Items will display one record only*</h5>
<div class="row card"> <div class="row card">
<div class="card-header"> <div class="card-header">
<h2>Pending Item Movement</h2> <h2>Pending Item Transit</h2>
</div> </div>
<div class="card-body"> <div class="card-body">
<table class="table table-bordered table-hover table-striped no-wrap" id="itemMovementNotCompleteDatatable" <table class="table table-bordered table-hover table-striped no-wrap" id="itemMovementNotCompleteDatatable"
@ -73,16 +137,26 @@
<div class="card-header"> <div class="card-header">
<h2>Complete Item Movement</h2> <h2>Complete Item Movement</h2>
</div> </div>
<div class="card-header">
<h4>All Item Movement</h4>
</div>
<div class="card-body"> <div class="card-body">
<table class="table table-bordered table-hover table-striped no-wrap" id="itemMovementCompleteDatatable" <table class="table table-bordered table-hover table-striped no-wrap" id="itemMovementCompleteDatatable"
style="width:100%;border-style: solid; border-width: 1px"></table> style="width:100%;border-style: solid; border-width: 1px"></table>
</div> </div>
<div class="card-header">
<h4>Assign Station</h4>
</div>
<div class="card-body">
<table class="table table-bordered table-hover table-striped no-wrap" id="assignStationDatatable"
style="width:100%;border-style: solid; border-width: 1px"></table>
</div>
</div> </div>
</div> </div>
<!--------------------------------------------ITEM CATEGORY----------------------------------------------------------------------> <!--------------------------------------------ITEM CATEGORY---------------------------------------------------------------------->
<div v-if="sortBy === 'item'"> <div v-if="sortBy === 'item'">
<div v-for="(group, itemId) in filteredItems" :key="itemId" class="row card"> <div v-for="(group, itemId) in paginatedItems" :key="itemId" class="row card">
<div class="card-header d-flex justify-content-between align-items-center"> <div class="card-header d-flex justify-content-between align-items-center">
<h2>Item : {{ group.uniqueID }}</h2> <h2>Item : {{ group.uniqueID }}</h2>
<button class="btn btn-light" v-on:click="toggleCategory(itemId)"> <button class="btn btn-light" v-on:click="toggleCategory(itemId)">
@ -532,6 +606,29 @@
</div> </div>
</div> </div>
</div> </div>
<div class="d-flex justify-content-center align-items-center mt-3" v-if="sortBy === 'item' && totalPages > 0">
<button class="btn btn-secondary mx-1" v-on:click="goToPage(currentPage - 1)" :disabled="currentPage === 1">
Previous
</button>
<span class="mx-2">Page {{ currentPage }} of {{ totalPages }}</span>
<button class="btn btn-secondary mx-1" v-on:click="goToPage(currentPage + 1)" :disabled="currentPage === totalPages">
Next
</button>
</div>
<div class="d-flex justify-content-center align-items-center mt-3" v-if="sortBy === 'station' && totalPagesStation > 0">
<button class="btn btn-secondary mx-1" v-on:click="goToPageStation(currentPageStation - 1)" :disabled="currentPageStation === 1">
Previous
</button>
<span class="mx-2">Page {{ currentPageStation }} of {{ itemsPerPageStation }}</span>
<button class="btn btn-secondary mx-1" v-on:click="goToPageStation(currentPageStation + 1)" :disabled="currentPageStation === totalPages">
Next
</button>
</div>
</div> </div>
@section Scripts { @section Scripts {
@ -545,11 +642,13 @@
const app = Vue.createApp({ const app = Vue.createApp({
data() { data() {
return { return {
userId: null,
itemMovements: [], itemMovements: [],
assignStationDatatable: null,
itemMovementCompleteDatatable: null, itemMovementCompleteDatatable: null,
stationDatatable: null,
itemMovementNotCompleteDatatable: null, itemMovementNotCompleteDatatable: null,
searchQuery: "", searchQuery: "",
searchQueryStation: "",
searchStation: "", searchStation: "",
sortBy: "all", sortBy: "all",
historyVisible: {}, historyVisible: {},
@ -557,9 +656,29 @@
categoryVisible: {}, categoryVisible: {},
consignmentNoteUrl: "", consignmentNoteUrl: "",
stationName: "", stationName: "",
currentPage: 1,
itemsPerPage: 10,
currentPageStation: 1,
itemsPerPageStation: 10,
dropdownOpen: false,
currentUser: null,
stations:[],
}; };
}, },
computed: { computed: {
paginatedItems() {
const start = (this.currentPage - 1) * this.itemsPerPage;
const end = start + this.itemsPerPage;
return Object.fromEntries(
Object.entries(this.filteredItems).slice(start, end)
);
},
totalPages() {
return Math.ceil(Object.keys(this.filteredItems).length / this.itemsPerPage);
},
totalPagesStation() {
return Math.ceil(Object.keys(this.filteredStation).length / this.itemsPerPage);
},
processedGroupedItems() { processedGroupedItems() {
let grouped = this.itemMovements.reduce((acc, movement) => { let grouped = this.itemMovements.reduce((acc, movement) => {
if (!acc[movement.itemId]) { if (!acc[movement.itemId]) {
@ -686,16 +805,16 @@
}, },
filteredStation() { filteredStation() {
if (!this.searchStation) { if (!this.searchQueryStation) {
return this.groupedByStation; return this.groupedByStation;
} }
let searchQuery = this.searchStation.toLowerCase(); let searchQueryStation = this.searchQueryStation.toLowerCase();
let grouped = this.groupedByStation; let grouped = this.groupedByStation;
let filtered = {}; let filtered = {};
Object.keys(grouped).forEach(station => { Object.keys(grouped).forEach(station => {
if (station.toLowerCase().includes(searchQuery)) { if (station.toLowerCase().includes(searchQueryStation)) {
filtered[station] = grouped[station]; filtered[station] = grouped[station];
} }
}); });
@ -703,10 +822,44 @@
return filtered; return filtered;
}, },
}, },
mounted() { async mounted() {
this.fetchItemMovement(); this.fetchItemMovement();
await this.fetchUser();
await Promise.all([
this.fetchStation(),
]);
}, },
methods: { methods: {
paginatedItemsStation(item) {
const start = (this.currentPageStation - 1) * this.itemsPerPageStation;
const end = start + this.itemsPerPageStation;
return Object.fromEntries(
Object.entries(item).slice(start, end)
);
},
selectStation(item) {
this.searchQueryStation = item.stationName;
this.dropdownOpen = false;
},
closeDropdown() {
this.dropdownOpen = false; // Tutup dropdown
},
goToPage(page) {
if (page >= 1 && page <= this.totalPages) {
this.currentPage = page;
}
},
goToPageStation(page) {
if (page >= 1 && page <= this.itemsPerPageStation) {
this.currentPageStation = page;
}
},
remark(remark) {
document.getElementById("remarkContent").innerText = remark || "No remark message provide.";
let modal = new bootstrap.Modal(document.getElementById("remarkModal"));
modal.show();
},
remark(remark) { remark(remark) {
document.getElementById("remarkContent").innerText = remark || "No remark message provide."; document.getElementById("remarkContent").innerText = remark || "No remark message provide.";
let modal = new bootstrap.Modal(document.getElementById("remarkModal")); let modal = new bootstrap.Modal(document.getElementById("remarkModal"));
@ -750,6 +903,45 @@
} }
}, },
async fetchStation() {
try {
const response = await fetch('/InvMainAPI/StationList', {
method: 'POST', // Specify the HTTP method
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error('Failed to fetch Station');
}
const data = await response.json();
this.stations = data.filter(station => station.stationPicID === this.userId);
} catch (error) {
console.error('Error fetching Station:', error);
}
},
async fetchUser() {
try {
const response = await fetch(`/IdentityAPI/GetUserInformation/`, {
method: 'POST',
});
if (response.ok) {
const data = await response.json();
this.currentUser = data?.userInfo || null;
this.userId = await this.currentUser.id;
}
else {
console.error(`Failed to fetch user: ${response.statusText}`);
}
}
catch (error) {
console.error('There was a problem with the fetch operation:', error);
}
},
handleSorting() { handleSorting() {
this.renderTables(); this.renderTables();
}, },
@ -767,8 +959,8 @@
if (this.itemMovementCompleteDatatable) { if (this.itemMovementCompleteDatatable) {
this.itemMovementCompleteDatatable.destroy(); this.itemMovementCompleteDatatable.destroy();
} }
if (this.stationDatatable) { if (this.assignStationDatatable) {
this.stationDatatable.destroy(); this.assignStationDatatable.destroy();
} }
// Get latest movement per uniqueID after filtering // Get latest movement per uniqueID after filtering
@ -808,6 +1000,7 @@
let notCompleteData = []; let notCompleteData = [];
let completeData = []; let completeData = [];
let completeStationData = [];
latestMovements.forEach(movement => { latestMovements.forEach(movement => {
let filteredMovements = filterMovements([movement]); let filteredMovements = filterMovements([movement]);
@ -815,8 +1008,10 @@
if (filteredMovements.length > 0) { if (filteredMovements.length > 0) {
if (movement.movementComplete == 0) { if (movement.movementComplete == 0) {
notCompleteData.push(movement); notCompleteData.push(movement);
} else if (movement.movementComplete == 1) { } else if (movement.movementComplete == 1 && movement.action !== "Assign") {
completeData.push(movement); completeData.push(movement);
} else {
completeStationData.push(movement);
} }
} }
}); });
@ -826,7 +1021,7 @@
data: notCompleteData, data: notCompleteData,
columns: [ columns: [
{ title: "Unique Id", data: "id" }, { title: "Unique Id", data: "id" },
{ title: "Product Name", data: "productName" }, { title: "Product Name", data: "productName", render: (data, type, full) => { return `${data} <br> ${renderFile(full.productImage)}`; } },
{ title: "Product Code", data: "uniqueID" }, { title: "Product Code", data: "uniqueID" },
{ title: "Action", data: "action" }, { title: "Action", data: "action" },
{ title: "Send Date", data: "sendDate" }, { title: "Send Date", data: "sendDate" },
@ -847,7 +1042,7 @@
data: completeData, data: completeData,
columns: [ columns: [
{ title: "Unique Id", data: "id" }, { title: "Unique Id", data: "id" },
{ title: "Product Name", data: "productName" }, { title: "Product Name", data: "productName", render: (data, type, full) => { return `${data} <br> ${renderFile(full.productImage)}`; } },
{ title: "Product Code", data: "uniqueID" }, { title: "Product Code", data: "uniqueID" },
{ title: "Send Date", data: "sendDate" }, { title: "Send Date", data: "sendDate" },
{ title: "Receive Date", data: "receiveDate" }, { title: "Receive Date", data: "receiveDate" },
@ -867,6 +1062,25 @@
responsive: true, responsive: true,
}); });
// Table 3: Station Movements
this.assignStationDatatable = $("#assignStationDatatable").DataTable({
data: completeStationData,
columns: [
{ title: "Unique Id", data: "id" },
{ title: "Product Name", data: "productName", render: (data, type, full) => { return `${data} <br> ${renderFile(full.productImage)}`; }},
{ title: "Product Code", data: "uniqueID" },
{ title: "Assign Date", data: "sendDate" },
{ title: "Action", data: "action" },
{ title: "Station User PIC", data: "toUserName" },
{ title: "From Station", data: "toStationName" },
{ title: "Last Station", data: "lastStationName" },
{ title: "Qty", data: "quantity" },
{ title: "Note", data: "consignmentNote", render: renderFile },
{ title: "Remark", data: "remark" },
],
responsive: true,
});
// Function to render file (image/PDF) // Function to render file (image/PDF)
function renderFile(data, type, full, meta) { function renderFile(data, type, full, meta) {
if (!data) { if (!data) {
@ -917,6 +1131,22 @@
}, },
}, },
directives: {
clickOutside: {
beforeMount(el, binding) {
el.clickOutsideEvent = (event) => {
if (!(el.contains(event.target))) {
binding.value?.(); // Guna optional chaining untuk elak error
}
};
document.body.addEventListener("click", el.clickOutsideEvent);
},
unmounted(el) {
document.body.removeEventListener("click", el.clickOutsideEvent);
}
}
}
}); });
</script> </script>

View File

@ -573,14 +573,14 @@
} }
}); });
if (!response.ok) { if (!response.ok) {
throw new Error('Failed to fetch suppliers'); throw new Error('Failed to fetch Station');
} }
const data = await response.json(); const data = await response.json();
this.stations = data.filter(station => station.stationPicID === this.userId); this.stations = data.filter(station => station.stationPicID === this.userId);
} catch (error) { } catch (error) {
console.error('Error fetching suppliers:', error); console.error('Error fetching Station:', error);
} }
}, },

View File

@ -19,7 +19,7 @@
<h1 class="font-light text-white"> <h1 class="font-light text-white">
<i class="mdi mdi-factory"></i> <i class="mdi mdi-factory"></i>
</h1> </h1>
<h6 class="text-white">Manifacturer</h6> <h6 class="text-white">Manufacturer</h6>
</div> </div>
</a> </a>
</div> </div>

View File

@ -69,45 +69,6 @@ namespace PSTW_CentralSystem.Controllers.API
return StatusCode(500, $"An error occurred: {ex.Message}"); return StatusCode(500, $"An error occurred: {ex.Message}");
} }
} }
//[HttpPost("GetTechnicianUserInformation")]
//public async Task<IActionResult> GetTechnicianUserInformation()
//{
// try
// {
// var users = await _identityDbContext.Users
// .Include(u => u.Department)
// .ToListAsync(); // Retrieve all users with department info
// var technicianUsers = new List<object>();
// foreach (var user in users)
// {
// var roles = await _userManager.GetRolesAsync(user);
// if (roles.Contains("Technician"))
// {
// technicianUsers.Add(new
// {
// id = user.Id,
// fullname = user.FullName,
// department = user.Department
// });
// }
// }
// if (!technicianUsers.Any())
// {
// return NotFound(new { message = "No technicians found" });
// }
// return Ok(new { technicianUsers = technicianUsers });
// }
// catch (Exception ex)
// {
// return StatusCode(500, new { message = $"An error occurred: {ex.Message}" });
// }
//}
#endregion User #endregion User
#region LDAP Login #region LDAP Login

View File

@ -777,6 +777,7 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
ToUserName = i.NextUser?.FullName, ToUserName = i.NextUser?.FullName,
ToStoreName = i.NextStore?.StoreName, ToStoreName = i.NextStore?.StoreName,
ToStationName = i.NextStation?.StationName, ToStationName = i.NextStation?.StationName,
ProductImage = i.Item?.Product?.ImageProduct,
i.ToOther, i.ToOther,
i.sendDate, i.sendDate,
i.Action, i.Action,
@ -1503,6 +1504,7 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
i.Id, i.Id,
i.FullName, i.FullName,
i.Department, i.Department,
i.Department?.DepartmentName,
})); }));
} }

View File

@ -552,7 +552,7 @@
<div class="ms-auto text-end"> <div class="ms-auto text-end">
<nav aria-label="breadcrumb"> <nav aria-label="breadcrumb">
<ol class="breadcrumb"> <ol class="breadcrumb">
<li class="breadcrumb-item"><a href="#">Home</a></li> <li class="breadcrumb-item"><a asp-area="" asp-controller="Home" asp-action="Index">Home</a></li>
<li class="breadcrumb-item active" aria-current="page"> <li class="breadcrumb-item active" aria-current="page">
Library Library
</li> </li>

View File

@ -123,7 +123,7 @@
<!-- ============================================================== --> <!-- ============================================================== -->
<!-- Logo --> <!-- Logo -->
<!-- ============================================================== --> <!-- ============================================================== -->
<a class="navbar-brand" href="index.html"> <a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">
<!-- Logo icon --> <!-- Logo icon -->
<b class="logo-icon ps-2"> <b class="logo-icon ps-2">
<!--You can put here icon as well // <i class="wi wi-sunset"></i> //--> <!--You can put here icon as well // <i class="wi wi-sunset"></i> //-->
@ -747,7 +747,7 @@
<!-- footer --> <!-- footer -->
<!-- ============================================================== --> <!-- ============================================================== -->
<footer class="footer text-center"> <footer class="footer text-center">
All Rights Reserved by Matrix-admin. Designed and Developed by All Rights Reserved by PSTW. Designed and Developed by
<a href="https://www.wrappixel.com">WrapPixel</a>. <a href="https://www.wrappixel.com">WrapPixel</a>.
</footer> </footer>
<!-- ============================================================== --> <!-- ============================================================== -->