This commit is contained in:
ameerulrasyid 2025-03-06 15:53:28 +08:00
commit e78e1c979e
3 changed files with 202 additions and 26 deletions

View File

@ -577,8 +577,8 @@
};
},
computed: {
groupedItems() {
return this.itemMovements.reduce((acc, movement) => {
processedGroupedItems() {
let grouped = this.itemMovements.reduce((acc, movement) => {
if (!acc[movement.itemId]) {
acc[movement.itemId] = {
uniqueID: movement.uniqueID,
@ -588,6 +588,24 @@
acc[movement.itemId].movements.push(movement);
return acc;
}, {});
// Sort items from newest to oldest & filter them
for (let itemId in grouped) {
let movements = grouped[itemId].movements
.sort((a, b) => b.id - a.id); // Newest to oldest
let stopIndex = movements.findIndex(m =>
m.toOther === 'Return' && m.movementComplete == 1
);
if (stopIndex !== -1) {
movements = movements.slice(0, stopIndex);
}
grouped[itemId].movements = movements;
}
return grouped;
},
groupedByStation() {
@ -659,11 +677,11 @@
filteredItems() {
if (!this.searchQuery.trim()) {
return this.groupedItems;
return this.processedGroupedItems;
}
const searchLower = this.searchQuery.toLowerCase();
return Object.fromEntries(
Object.entries(this.groupedItems).filter(([_, group]) =>
Object.entries(this.processedGroupedItems).filter(([_, group]) =>
group.uniqueID.toLowerCase().includes(searchLower)
)
);

View File

@ -4,6 +4,62 @@
Layout = "~/Views/Shared/_Layout.cshtml";
}
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartialUser.cshtml")
<style>
.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;
}
.modal-body img {
max-width: 100%; /* Ensure it fits the modal */
max-height: 150px; /* Adjust max height */
display: block;
margin: auto;
}
</style>
<div id="requestProduct" class="row">
<!-- Document Preview Modal -->
@ -74,32 +130,71 @@
<div class="row register-form">
<div class="col-md-13">
<!-- Product Dropdown -->
<!-- Product Name -->
<div class="form-group row">
<label class="col-sm-4 col-form-label">Product</label>
<label class="col-sm-2 col-form-label">Product</label>
<div class="col-sm-8">
<div class="dropdown">
<select class="btn btn-primary dropdown-toggle col-md-12" data-toggle="dropdown" aria-expanded="false" v-model="productId" required>
<option class="btn-light" value="" disabled selected>Select Product</option>
<option class="btn-light" v-for="(item, index) in products" :key="index" :value="item.productId">
<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="searchQuery"
placeholder="Search product..." 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 filteredProducts"
:key="index" class="dropdown-item" v-on:mousedown.prevent="selectProduct(item)">
{{ item.productName + ' (' + item.modelNo + ')' }}
</option>
</select>
</div>
</div>
</div>
</div>
</div>
<!-- Category Dropdown -->
<div class="form-group row">
<label class="col-sm-4 col-form-label">Category:</label>
<label class="col-sm-2 col-form-label">Category:</label>
<div class="col-sm-8">
<input type="text" id="category" name="category" v-model="showProduct.category" class="form-control" readonly />
</div>
</div>
<!-- Image Product Dropdown-->
<div class="form-group row align-items-center">
<label for="imageProduct" class="col-sm-2 col-form-label">Product Image: </label>
<div class="col-sm-8">
<img v-if="showProduct.imageProduct" :src="showProduct.imageProduct" alt="Product Image" class="img-fluid" data-toggle="modal" data-target="#imageModal" />
<input type="hidden" id="imageProduct" name="imageProduct" v-model="showProduct">
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="imageModal" tabindex="-1" role="dialog" aria-labelledby="imageModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="imageModalLabel">Product Image</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<img :src="showProduct.imageProduct" alt="Product Image" class="img-fluid">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Quantity Input -->
<div class="form-group row d-flex align-items-center">
<label class="col-sm-4 col-form-label">Quantity:</label>
<label class="col-sm-2 col-form-label">Quantity:</label>
<div class="col-sm-8">
<input type="number" class="form-control" v-model="quantity" required />
</div>
@ -107,7 +202,7 @@
@* Who will assign to *@
<div class="form-group row">
<label class="col-sm-4 col-form-label">Item Assignment : </label>
<label class="col-sm-2 col-form-label">Item Assignment : </label>
<div class="col-sm-8">
<div class="dropdown">
<select class="btn btn-primary dropdown-toggle col-md-12" v-model="assign"required>
@ -120,7 +215,7 @@
<!-- Station Dropdown -->
<div class="form-group row" v-if="assign === 'Station'">
<label class="col-sm-4 col-form-label">Station:</label>
<label class="col-sm-2 col-form-label">Station:</label>
<div class="col-sm-8">
<div class="dropdown">
<select class="btn btn-primary dropdown-toggle col-md-12" data-toggle="dropdown" aria-expanded="false" v-model="stationId" required>
@ -136,7 +231,7 @@
<!-- Remark Input (Wider) -->
<div class="form-group row d-flex align-items-center">
<label class="col-sm-4 col-form-label">Remark:</label>
<label class="col-sm-2 col-form-label">Remark:</label>
<div class="col-sm-8">
<input type="text" class="form-control col-md-10" v-model="remark" />
</div>
@ -144,7 +239,7 @@
<!-- Document/Picture Input -->
<div class="form-group row d-flex align-items-center">
<label class="col-sm-4 col-form-label">Document/Picture:</label>
<label class="col-sm-2 col-form-label">Document/Picture:</label>
<div class="col-sm-8">
<input type="file" id="document" name="document" class="form-control-file" v-on:change="handleFileUpload" accept="image/png, image/jpeg, application/pdf" />
</div>
@ -205,6 +300,8 @@
assign: "",
productName: null,
searchQuery: "",
dropdownOpen: false,
stations: [],
selectedProduct: "",
selectedStation: "",
@ -236,8 +333,25 @@
return product ? product : {};
},
filteredProducts() {
return this.products.filter(item =>
item.productName.toLowerCase().includes(this.searchQuery.toLowerCase()) ||
item.modelNo.toLowerCase().includes(this.searchQuery.toLowerCase())
);
}
},
methods: {
closeDropdown() {
this.dropdownOpen = false; // Tutup dropdown
},
selectProduct(item) {
this.selectedProduct = item;
this.productId = item.productId;
this.searchQuery = item.productName + " (" + item.modelNo + ")";
this.dropdownOpen = false;
},
handleFileUpload(event) {
const file = event.target.files[0];
@ -251,6 +365,7 @@
this.document = null;
}
},
async addRequest() {
try {
const requiredFields = ['productId', 'quantity', 'assign'];
@ -308,11 +423,11 @@
});
if (response.ok) {
this.resetForm();
alert('Success!', 'Request form has been successfully submitted.', 'success');
const requestItem = await response.json();
this.request.push(requestItem);
this.fetchRequest();
this.resetForm();
} else {
throw new Error('Failed to submit form.');
}
@ -337,6 +452,15 @@
$(td).attr('id', `qr${cellData}`);
},
},
{ "title": "Product Image",
"data": "productPicture",
"render": function (data, type, full, meta) {
var image = `<a href="${data}" target="_blank" data-lightbox="image-1">
<img src="${data}" alt="Image" class="img-thumbnail" style="width: 100px; height: 100px;" />
</a>`;
return image;
},
},
{
"title": "Product Id",
"data": "productName",
@ -432,6 +556,19 @@
$(td).attr('id', `qr${cellData}`);
},
},
{
"title": "Status",
"data": "status",
},
{ "title": "Product Image",
"data": "productPicture",
"render": function (data, type, full, meta) {
var image = `<a href="${data}" target="_blank" data-lightbox="image-1">
<img src="${data}" alt="Image" class="img-thumbnail" style="width: 100px; height: 100px;" />
</a>`;
return image;
},
},
{
"title": "Product Id",
"data": "productName",
@ -494,10 +631,6 @@
{
"title": "Approval Date",
"data": "approvalDate",
},
{
"title": "Status",
"data": "status",
}
],
@ -605,6 +738,7 @@
},
resetForm() {
this.searchQuery = "";
this.stationId = "";
this.productId = "";
this.remark = "";
@ -660,6 +794,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>
}

View File

@ -945,6 +945,7 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
i.requestID,
i.ProductId,
productName = i.Product?.ProductName,
productPicture = i.Product?.ImageProduct,
i.UserId,
i.status,
i.StationId,
@ -962,13 +963,20 @@ namespace PSTW_CentralSystem.Controllers.API.Inventory
[HttpDelete("DeleteRequest/{requestId}")]
public async Task<IActionResult> DeleteRequest(int requestId)
{
var request = await _centralDbContext.Requests.FindAsync(requestId);
if (request == null)
var requestApprove = _centralDbContext.Requests.FirstOrDefault(r => r.requestID == requestId && r.approvalDate != null);
var requestDelete = _centralDbContext.Requests.FirstOrDefault(r => r.requestID == requestId);
if (requestApprove != null)
{
return NotFound(new { success = false, message = "Request not found Or Admin Already Approve or Reject" });
}
if (requestDelete == null)
{
return NotFound(new { success = false, message = "Request not found" });
}
_centralDbContext.Requests.Remove(request);
_centralDbContext.Requests.Remove(requestDelete);
await _centralDbContext.SaveChangesAsync();
return Ok(new { success = true, message = "Request deleted successfully" });