This commit is contained in:
ArifHilmi 2025-03-05 15:55:25 +08:00
commit f4fc5dc103
3 changed files with 605 additions and 123 deletions

View File

@ -15,16 +15,41 @@
.table td img {
display: block !important;
}
.text-true {
color: green;
}
.text-false {
color: red;
}
.text-primary {
color: blue; /* Warna asal untuk 'Receive' */
}
.text-warning {
color: orange; /* Warna oren untuk 'Return' */
}
.text-success {
color: greenyellow;
}
.ms-auto {
margin-left: auto !important; /* Push Complete/Incomplete to right */
}
</style>
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartial.cshtml");
<div id="registerItem" class="row">
<div class="row mb-3" v-if="currentRole == 'Super Admin'">
<div class="row mb-3" >
<h2 for="sortSelect" class="col-sm-1 col-form-h2" style="min-width:140px;">Sort by:</h2>
<div class="col-sm-4">
<select id="sortSelect" class="form-control" v-model="sortBy" v-on:change="handleSorting">
<option value="all">All</option>
<option value="all" >All</option>
<option value="item">Item</option>
<option value="logs">Logs</option>
<option value="station">Station</option>
<option value="logs" v-if="currentRole == 'Super Admin'">Logs</option>
</select>
</div>
</div>
@ -36,6 +61,13 @@
</div>
</div>
<div class="row mb-3" v-if="sortBy === 'station'">
<h4 class="col-sm-1 col-form-h2" style="min-width:150px;">Search Station:</h4>
<div class="col-sm-4">
<input type="text" class="form-control" v-model="searchStation" placeholder="Search by station name...">
</div>
</div>
<div v-if="sortBy === 'all'">
<div class="row card">
<div class="card-header">
@ -104,37 +136,36 @@
<strong>Latest Movement</strong>
<div class="col-md-12 d-flex flex-wrap align-items-center gap-3 p-2 border-bottom">
<!-- Movement Type -->
<h3 :class="{'text-primary': movement.toOther === 'On Delivery', 'text-warning': movement.toOther !== 'On Delivery' && movement.action !== 'Assign',
'text-info': movement.action === 'Assign'}"
class="flex-shrink-0 text-nowrap"
style="max-width:90px; min-width:90px;">
<h3 :class="{'text-primary': movement.toOther === 'On Delivery', 'text-warning': movement.toOther === 'Return',
'text-success': movement.toStation !== null, 'text-info': movement.action === 'Assign' && movement.toStation === null}"
class="flex-shrink-0 text-nowrap" style="max-width:90px; min-width:90px;">
{{ movement.action === 'Assign' ? 'Assign' : (movement.toOther === 'On Delivery' ? 'Receive' : 'Return') }}
{{ movement.toOther === 'Return' ? 'Return' : (movement.toOther === 'On Delivery' ? 'Receive' : ( movement.toStation !== null ? 'Change' : 'Assign')) }}
</h3>
<!-- Send Date -->
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1">
<h4 class="fixed-label m-0 text-nowrap">Send Date:</h4>
<span class="fixed-value">{{ movement.sendDate }}</span>
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:285px; min-width:285px;">
<h4 class="fixed-label m-0 text-nowrap">{{movement.action === 'Assign' ? 'Assign Date' : 'Send Date'}}</h4>
<span class="fixed-value text-truncate">{{ movement.sendDate }}</span>
</div>
<!-- Receive Date -->
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1">
<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;">
<h4 class="fixed-label m-0 text-nowrap">Receive Date:</h4>
<span class="fixed-value">{{ movement.receiveDate || 'Not arrive' }}</span>
<span class="fixed-value text-truncate" style="max-width:160px;">{{ movement.receiveDate || 'Not arrive' }}</span>
</div>
<!-- Action -->
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1">
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:150px; min-width:150px;">
<h4 class="fixed-labelStatus m-0 text-nowrap">Action:</h4>
<span class="fixed-value">{{ movement.action }}</span>
<span class="fixed-value text-truncate">{{ movement.action }}</span>
</div>
<!-- Status -->
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1">
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:160px; min-width:160px;">
<h4 class="fixed-labelStatus m-0 text-nowrap">Status:</h4>
<span class="fixed-value">{{ movement.latestStatus || movement.toOther }}</span>
<span class="fixed-value text-truncate" style="max-width:90px;">{{ movement.latestStatus || movement.toOther }}</span>
</div>
<!-- More Details Button -->
@ -143,7 +174,8 @@
</button>
<!-- Completion Status -->
<h4 :class="movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'text-success' : 'text-danger'" class="text-nowrap ms-3">
<h4 :class="movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'text-success' : 'text-danger'"
class="text-nowrap ms-3">
{{ movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'Complete' : (movement.latestStatus === 'Ready To Deploy' ? 'Canceled' : 'Incomplete') }}
</h4>
@ -153,7 +185,8 @@
<div class="row">
<div class="col-md-4 text-center">
<!-- Conditionally render Start Icon -->
<i v-if="movement.toOther !== 'On Delivery'" class="fas fa-user fa-2x"></i>
<i v-if="movement.toStation" class="fas fa-map-marker-alt"></i>
<i v-else-if="movement.toOther !== 'On Delivery'" class="fas fa-user fa-2x"></i>
<i v-else class="fas fa-warehouse fa-2x"></i>
<p><strong>Start</strong></p>
<p><strong>User:</strong> {{ movement.toUserName }}</p>
@ -199,23 +232,22 @@
<div v-for="(movement, i) in group.movements.slice(1)" :key="i" class="row mt-2">
<div class="col-md-12 d-flex flex-wrap align-items-center gap-3 p-2 border-bottom">
<!-- Movement Type -->
<h3 :class="{'text-primary': movement.toOther === 'On Delivery', 'text-warning': movement.toOther !== 'On Delivery' && movement.action !== 'Assign',
'text-info': movement.action === 'Assign'}"
class="flex-shrink-0 text-nowrap"
style="max-width:90px; min-width:90px;">
<h3 :class="{'text-primary': movement.toOther === 'On Delivery', 'text-warning': movement.toOther === 'Return',
'text-success': movement.toStation !== null, 'text-info': movement.action === 'Assign' && movement.toStation === null}"
class="flex-shrink-0 text-nowrap" style="max-width:90px; min-width:90px;">
{{ movement.action === 'Assign' ? 'Assign' : (movement.toOther === 'On Delivery' ? 'Receive' : 'Return') }}
{{ movement.toOther === 'Return' ? 'Return' : (movement.toOther === 'On Delivery' ? 'Receive' : ( movement.toStation !== null ? 'Change' : 'Assign')) }}
</h3>
<!-- Send Date -->
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:285px; min-width:285px;">
<h4 class="fixed-label m-0 text-nowrap">Send Date:</h4>
<h4 class="fixed-label m-0 text-nowrap">{{movement.action === 'Assign' ? 'Assign Date' : 'Send Date'}}</h4>
<span class="fixed-value text-truncate">{{ movement.sendDate }}</span>
</div>
<!-- Receive Date -->
<div 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'" 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>
<span class="fixed-value text-truncate" style="max-width:160px;">{{ movement.receiveDate || 'Not arrive' }}</span>
</div>
@ -237,12 +269,12 @@
More Details
</button>
<!-- Completion Status -->
<h4 :class="movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'text-success' : 'text-danger'"
class="text-nowrap ms-3">
{{ movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'Complete' : (movement.latestStatus === 'Ready To Deploy' ? 'Canceled' : 'Incomplete') }}
</h4>
</div>
<!-- Completion Status -->
<h4 :class="movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'text-success' : 'text-danger'"
class="text-nowrap ms-3">
{{ movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'Complete' : (movement.latestStatus === 'Ready To Deploy' ? 'Canceled' : 'Incomplete') }}
</h4>
</div>
<!-- 📌 Details Section (Hidden by Default) -->
@ -250,8 +282,8 @@
<div class="row">
<div class="col-md-4 text-center">
<!-- Conditionally render Start Icon -->
<i v-if="movement.toOther !== 'On Delivery'" class="fas fa-user fa-2x"></i>
<i v-else class="fas fa-warehouse fa-2x"></i>
<i v-if="movement.toStation" class="fas fa-map-marker-alt"></i>
<i v-else-if="movement.toOther !== 'On Delivery'" class="fas fa-user fa-2x"></i>
<p><strong>Start</strong></p>
<p><strong>User:</strong> {{ movement.toUserName }}</p>
<p><strong>Station:</strong> {{ movement.toStationName }}</p>
@ -289,6 +321,222 @@
</div>
</div>
</div>
<!--------------------------------------------STATION CATEGORY---------------------------------------------------------------------->
<div v-if="sortBy === 'station'">
<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">
<!-- Station Header -->
<div class="card-header d-flex justify-content-between align-items-center">
<h3>{{ station }}</h3>
<button class="btn btn-light" v-on:click="toggleCategory(station)">
<i :class="categoryVisible[station] ? 'fas fa-chevron-up' : 'fas fa-chevron-down'"></i> Show Items
</button>
</div>
<!-- Show Items Under Each Station -->
<div v-show="categoryVisible[station]" class="card-body">
<div v-for="(group, itemId) in items" :key="itemId" class="row card">
<!-- Item Header -->
<div class="card-header d-flex justify-content-between align-items-center">
<h2>Item : {{ group.uniqueID }}</h2>
<button class="btn btn-light" v-on:click="toggleCategory(itemId)">
<i :class="categoryVisible[itemId] ? 'fas fa-chevron-up' : 'fas fa-chevron-down'"></i> Show Details
</button>
</div>
<!-- Show Movements for Each Item -->
<div v-show="categoryVisible[itemId]" class="card-body">
<div v-for="(movement, index) in group.movements.sort((a, b) => a.id - b.id).reverse()" :key="movement.id" class="movement-row">
<div v-if="index === 0" class="row">
<strong>Latest Movement</strong>
<div class="col-md-12 d-flex flex-wrap align-items-center gap-3 p-2 border-bottom">
<h3 :class="{'text-primary': movement.toOther === 'On Delivery', 'text-warning': movement.toOther === 'Return',
'text-success': movement.toStation !== null, 'text-info': movement.action === 'Assign' && movement.toStation === null}"
class="flex-shrink-0 text-nowrap" style="max-width:90px; min-width:90px;">
{{ movement.toOther === 'Return' ? 'Return' : (movement.toOther === 'On Delivery' ? 'Receive' : ( movement.toStation !== null ? 'Change' : 'Assign')) }}
</h3>
<!-- Send Date -->
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:285px; min-width:285px;">
<h4 class="fixed-label m-0 text-nowrap">{{movement.action === 'Assign' ? 'Assign Date' : 'Send Date'}}</h4>
<span class="fixed-value text-truncate">{{ movement.sendDate }}</span>
</div>
<!-- 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;">
<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>
</div>
<!-- Action -->
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:150px; min-width:150px;">
<h4 class="fixed-labelStatus m-0 text-nowrap">Action:</h4>
<span class="fixed-value text-truncate">{{ movement.action }}</span>
</div>
<!-- Status -->
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:160px; min-width:160px;">
<h4 class="fixed-labelStatus m-0 text-nowrap">Status:</h4>
<span class="fixed-value text-truncate" style="max-width:90px;">{{ movement.latestStatus || movement.toOther }}</span>
</div>
<!-- More Details Button -->
<button class="btn btn-info btn-sm ms-auto" v-on:click="toggleDetails(movement.id)">
More Details
</button>
<!-- Completion Status -->
<h4 :class="movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'text-success' : 'text-danger'"
class="text-nowrap ms-3">
{{ movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'Complete' : (movement.latestStatus === 'Ready To Deploy' ? 'Canceled' : 'Incomplete') }}
</h4>
</div>
<div v-show="detailsVisible[movement.id]" class="col-md-12 mt-2">
<div class="row">
<div class="col-md-4 text-center">
<i v-if="movement.toStation" class="fas fa-map-marker-alt"></i>
<i v-else-if="movement.toOther !== 'On Delivery'" class="fas fa-user fa-2x"></i>
<i v-else class="fas fa-warehouse fa-2x"></i>
<p><strong>Start</strong></p>
<p v-if="movement.toUser !== null"><strong>User:</strong> {{ movement.toUserName }}</p>
<p v-if="movement.toStation !== null"><strong>Station:</strong> {{ movement.toStationName }}</p>
<p v-if="movement.toStore !== null"><strong>Store:</strong> {{ movement.toStoreName }}</p>
</div>
<div class="col-md-4 text-center">
<i class="fas fa-arrow-right fa-2x"></i>
<p>{{ movement.latestStatus || movement.toOther }}</p>
<p>
<button class="btn btn-info btn-sm ms-auto" v-on:click="remark(movement.remark)">
Remark
</button>
</p>
<p>
<button class="btn btn-info btn-sm ms-auto" v-on:click="consignmentNote(movement.consignmentNote)">
Consignment Note
</button>
</p>
</div>
<div class="col-md-4 text-center">
<i v-if="movement.lastStation" class="fas fa-map-marker-alt"></i>
<i v-else-if="movement.toOther !== 'On Delivery'" class="fas fa-warehouse fa-2x"></i>
<i v-else class="fas fa-user fa-2x"></i>
<p><strong>End</strong></p>
<p v-if="movement.lastUser !== null"><strong>User:</strong> {{ movement.lastUserName }}</p>
<p v-if="movement.lastStation !== null"><strong>Station:</strong> {{ movement.lastStationName }}</p>
<p v-if="movement.lastStore !== null"><strong>Store:</strong> {{ movement.lastStoreName }}</p>
</div>
</div>
</div>
</div>
</div>
<!-- Single View History Button -->
<button class="btn btn-light w-100 text-left" v-on:click="toggleHistory(itemId)">
<i :class="historyVisible[itemId] ? 'fas fa-chevron-up' : 'fas fa-chevron-down'"></i> View History
</button>
<div v-show="historyVisible[itemId]" class="history-row">
<div v-for="(movement, i) in group.movements.slice(1)" :key="i" class="row mt-2">
<div class="col-md-12 d-flex flex-wrap align-items-center gap-3 p-2 border-bottom">
<!-- Movement Type -->
<h3 :class="{'text-primary': movement.toOther === 'On Delivery', 'text-warning': movement.toOther === 'Return',
'text-success': movement.toStation !== null, 'text-info': movement.action === 'Assign' && movement.toStation === null}"
class="flex-shrink-0 text-nowrap" style="max-width:90px; min-width:90px;">
{{ movement.toOther === 'Return' ? 'Return' : (movement.toOther === 'On Delivery' ? 'Receive' : ( movement.toStation !== null ? 'Change' : 'Assign')) }}
</h3>
<!-- Send Date -->
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:285px; min-width:285px;">
<h4 class="fixed-label m-0 text-nowrap">{{movement.action === 'Assign' ? 'Assign Date' : 'Send Date'}}</h4>
<span class="fixed-value text-truncate">{{ movement.sendDate }}</span>
</div>
<!-- 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;">
<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>
</div>
<!-- Action -->
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:150px; min-width:150px;">
<h4 class="fixed-labelStatus m-0 text-nowrap">Action:</h4>
<span class="fixed-value text-truncate">{{ movement.action }}</span>
</div>
<!-- Status -->
<div class="d-flex flex-wrap align-items-center gap-2 flex-grow-1" style="max-width:160px; min-width:160px;">
<h4 class="fixed-labelStatus m-0 text-nowrap">Status:</h4>
<span class="fixed-value text-truncate" style="max-width:90px;">{{ movement.latestStatus || movement.toOther }}</span>
</div>
<!-- More Details Button -->
<button class="btn btn-info btn-sm ms-auto" v-on:click="toggleDetails(movement.id)">
More Details
</button>
<!-- Completion Status -->
<h4 :class="movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'text-success' : 'text-danger'"
class="text-nowrap ms-3">
{{ movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'Complete' : (movement.latestStatus === 'Ready To Deploy' ? 'Canceled' : 'Incomplete') }}
</h4>
</div>
<!-- Details Section (Hidden by Default) -->
<div v-show="detailsVisible[movement.id]" class="col-md-12 mt-2">
<div class="row">
<div class="col-md-4 text-center">
<!-- Conditionally render Start Icon -->
<i v-if="movement.toOther !== 'On Delivery'" class="fas fa-user fa-2x"></i>
<i v-else class="fas fa-warehouse fa-2x"></i>
<p><strong>Start</strong></p>
<p v-if="movement.toUser !== null"><strong>User:</strong> {{ movement.toUserName }}</p>
<p v-if="movement.toStation !== null"><strong>Station:</strong> {{ movement.toStationName }}</p>
<p v-if="movement.toStore !== null"><strong>Store:</strong> {{ movement.toStoreName }}</p>
</div>
<div class="col-md-4 text-center">
<p></p>
<i class="fas fa-arrow-right fa-2x"></i>
<p>{{ movement.latestStatus || movement.toOther }}</p>
<p>
<button class="btn btn-info btn-sm ms-auto" v-on:click="remark(movement.remark)">
Remark
</button>
</p>
<p>
<button class="btn btn-info btn-sm ms-auto" v-on:click="consignmentNote(movement.consignmentNote)">
Consignment Note
</button>
</p>
</div>
<div class="col-md-4 text-center">
<!-- Conditionally render End Icon -->
<i v-if="movement.lastStation" class="fas fa-map-marker-alt"></i>
<i v-else-if="movement.toOther !== 'On Delivery'" class="fas fa-warehouse fa-2x"></i>
<i v-else class="fas fa-user fa-2x"></i>
<p><strong>End</strong></p>
<p v-if="movement.lastUser !== null"><strong>User:</strong> {{ movement.lastUserName }}</p>
<p v-if="movement.lastStation !== null"><strong>Station:</strong> {{ movement.lastStationName }}</p>
<p v-if="movement.lastStore !== null"><strong>Store:</strong> {{ movement.lastStoreName }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="remarkModal" tabindex="-1" aria-labelledby="remarkModalLabel" aria-hidden="true">
<div class="modal-dialog">
@ -346,36 +594,6 @@
const app = Vue.createApp({
data() {
return {
// companies: [
// {
// companyId: 1,
// companyName: "PSTW",
// departments: [{ departmentId: 1, departmentName: "Air" }, { departmentId: 2, departmentName: "Marine" }, { departmentId: 3, departmentName: "River" }]
// },
// {
// companyId: 2,
// companyName: "TES",
// departments: [{ departmentId: 1, departmentName: "Air" }],
// },
// ],
// company: "",
// Dept: null,
// teamTypes: ["Continuous", "Manual"],
// teamType: "",
// productName: null,
// imageProduct: null,
// productCategory: null,
// serialNumber: "",
// quantity: 1,
// supplierName: null,
// purchaseDate: null,
// PO: null,
// currency: "MYR",
// DefaultPrice: 0.01,
// currencyRate: 1,
// convertPrice: 0.01,
// DONo:null,
// DODate: null,
warranty: 0,
EndWDate: null,
invoiceNo: null,
@ -386,16 +604,6 @@
stations: [],
stores: [],
users:[],
// suppliers: [
// {
// supplierId: 1,
// supplierName: "Pang",
// },
// {
// supplierId: 2,
// supplierName: "Ms Kim",
// },
// ],
isModalOpen: false,
selectedProduct: "",
selectedSupplier: "",
@ -404,45 +612,107 @@
selectedTeamType: "",
selectedtoStation: "",
consignmentNoteUrl: "",
// currencies: {},
showItemModal: false,
loading: false,
// thisQRInfo: {
// uniqueID: null,
// departmentName: null,
// serialNumber: null,
// endWDate: null,
// },
items: [],
currentUser: null,
currentUserCompanyDept: null,
sortBy: "item",
sortBy: "all",
searchQuery: "",
categoryVisible: {},
historyVisible: {},
detailsVisible: {},
currentRole:"",
stationName: "",
searchStation: "",
}
},
mounted() {
this.fetchItem();
console.log("Filtered Station:", this.filteredStation);
},
computed: {
groupedItems() {
return this.items.reduce((acc, movement) => {
if (!acc[movement.itemId]) {
acc[movement.itemId] = {
uniqueID: movement.uniqueID,
movements: [],
};
return this.items.reduce((acc, movement) => {
if (!acc[movement.itemId]) {
acc[movement.itemId] = {
uniqueID: movement.uniqueID,
movements: [],
};
}
acc[movement.itemId].movements.push(movement);
return acc;
}, {});
},
groupedByStation() {
let grouped = {};
this.items.forEach((movement) => {
if (movement.toStation !== null) {
let station = movement.toStationName;
let itemId = movement.uniqueID;
if (!grouped[station]) {
grouped[station] = {};
}
acc[movement.itemId].movements.push(movement);
return acc;
}, {});
},
if (!grouped[station][itemId]) {
grouped[station][itemId] = { uniqueID: itemId, movements: [] };
}
grouped[station][itemId].movements.push(movement);
}
if (movement.lastStation !== null) {
let station = movement.lastStationName;
let itemId = movement.uniqueID;
if (!grouped[station]) {
grouped[station] = {};
}
if (!grouped[station][itemId]) {
grouped[station][itemId] = { uniqueID: itemId, movements: [] };
}
grouped[station][itemId].movements.push(movement);
}
else if (movement.lastStation == null || movement.toStation == null) {
let station = "Self Assigned";
let itemId = movement.uniqueID;
if (!grouped[station]) {
grouped[station] = {};
}
if (!grouped[station][itemId]) {
grouped[station][itemId] = { uniqueID: itemId, movements: [] };
}
grouped[station][itemId].movements.push(movement);
}
});
// Sort stations and move "Unassign Station" to the last position
let sortedKeys = Object.keys(grouped).sort((a, b) => {
if (a === "Unassign Station") return 1;
if (b === "Unassign Station") return -1;
return a.localeCompare(b);
});
let sortedGrouped = {};
sortedKeys.forEach((key) => {
sortedGrouped[key] = grouped[key];
});
return sortedGrouped;
},
filteredItems() {
if (!this.searchQuery.trim()) {
return this.groupedItems;
@ -455,6 +725,24 @@
);
},
filteredStation() {
if (!this.searchStation) {
return this.groupedByStation;
}
let searchQuery = this.searchStation.toLowerCase();
let grouped = this.groupedByStation;
let filtered = {};
Object.keys(grouped).forEach(station => {
if (station.toLowerCase().includes(searchQuery)) {
filtered[station] = grouped[station];
}
});
return filtered;
},
},
methods: {
@ -614,6 +902,8 @@
if(this.currentRole == "Super Admin"){
this.items = await response.json();
// console.log(this.items);
this.initAllTables();
} else {
const data = await response.json();
@ -623,8 +913,10 @@
item.toStore === this.currentUser.store
);
this.initAllTables();
console.log(this.items);
// console.log(this.items);
}
@ -759,10 +1051,10 @@
if (roles.includes("SuperAdmin")) {
this.currentRole = "Super Admin";
this.sortBy = 'logs';
// this.sortBy = 'logs';
} else if (roles.includes("Inventory Master")) {
this.currentRole = "Inventory Master";
this.sortBy = "item";
// this.sortBy = "item";
}
}
@ -859,16 +1151,19 @@
// },
handleSorting() {
this.renderTables();
console.log(this.sortBy);
},
renderTables() {
if (this.sortBy === "logs") {
// this.initAllTables();
this.initiateTable();
} else
if (this.sortBy === "all") {
this.initAllTables();
// this.initiateTable();
}
// if (this.sortBy === "logs") {
// // this.initAllTables();
// this.initiateTable();
// } else
// if (this.sortBy === "all") {
// this.initAllTables();
// // this.initiateTable();
// }
this.initAllTables();
},
initAllTables() {
if (this.itemMovementNotCompleteDatatable) {
@ -883,15 +1178,17 @@
columns: [
{ title: "Unique Id", data: "id" },
{ title: "Product Code", data: "uniqueID" },
{ title: "Action", data: "action" },
{ title: "Send Date", data: "sendDate" },
{ title: "From User", data: "toUserName" },
{ title: "Last User", data: "lastUserName" },
{ title: "From Station", data: "toStationName" },
{ title: "From Store", data: "toStoreName" },
{ title: "Action", data: "action" },
// { title: "Action", data: "action" },
{ title: "Start Status", data: "toOther" },
{ title: "Product Category", data: "productCategory" },
{ title: "Qty", data: "quantity" },
{ title: "Send Date", data: "sendDate" },
// { title: "Send Date", data: "sendDate" },
{
title: "Note",
data: "consignmentNote",
@ -932,19 +1229,22 @@
columns: [
{ title: "Unique Id", data: "id" },
{ title: "Product Code", data: "uniqueID" },
{ title: "Send Date", data: "sendDate" },
{ title: "Receive Date", data: "receiveDate" },
{ title: "Action", data: "action" },
{ title: "From User", data: "toUserName" },
{ title: "Last User", data: "lastUserName" },
{ title: "From Station", data: "toStationName" },
{ title: "Last Station", data: "lastStationName" },
{ title: "From Store", data: "toStoreName" },
{ title: "Last Store", data: "lastStoreName" },
{ title: "Action", data: "action" },
// { title: "Action", data: "action" },
{ title: "Start Status", data: "toOther" },
{ title: "Latest Status", data: "latestStatus" },
{ title: "Product Category", data: "productCategory" },
{ title: "Qty", data: "quantity" },
{ title: "Send Date", data: "sendDate" },
{ title: "Receive Date", data: "receiveDate" },
// { title: "Send Date", data: "sendDate" },
// { title: "Receive Date", data: "receiveDate" },
{ title: "Note",
data: "consignmentNote",
render: function (data, type, full, meta) {
@ -978,6 +1278,50 @@
order: [[0, "desc"]],
responsive: true,
});
this.stationDatatable = $("#stationDatatable").DataTable({
data: this.items.filter((m) => m.action === "Assign" ),
columns: [
{ title: "Unique Id", data: "id" },
{ title: "Product Code", data: "uniqueID" },
{ title: "Assign Date", data: "sendDate" },
{ title: "From User", data: "toUserName" },
{ title: "From Station", data: "toStationName" },
{ title: "Last Station", data: "lastStationName" },
{ title: "Qty", data: "quantity" },
{
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" },
],
responsive: true,
});
},
toggleCategory(itemId) {
this.categoryVisible[itemId] = !this.categoryVisible[itemId];

View File

@ -45,9 +45,6 @@
v-on:error="onError">
</qrcode-stream>
<!-- Debugging: Show Processed Video -->
<canvas ref="sharpenedCanvas" style="display:none;"></canvas>
<video ref="preview" autoplay style="width: 100%;"></video>
<p class="error">{{ error }}</p>
</div>
@ -295,7 +292,7 @@
<div class="form-group row">
<label for="invoiceDate" class="col-sm-4">Assign Date:</label>
<div class="col-sm-8">
<input type="date" id="assigndate" name="assigndate" class="form-control" v-model="assigndate">
<input type="date" id="assigndate" name="assigndate" class="form-control" v-model="assigndate" required>
</div>
</div>
@ -359,7 +356,7 @@
<div class="form-group row">
<label for="invoiceDate" class="col-sm-4">Assign Date:</label>
<div class="col-sm-8">
<input type="date" id="assigndate" name="assigndate" class="form-control" v-model="assigndate">
<input type="date" id="assigndate" name="assigndate" class="form-control" v-model="assigndate" required>
</div>
</div>
@ -410,7 +407,7 @@
<div class="form-group row">
<label for="invoiceDate" class="col-sm-4">Assign Date:</label>
<div class="col-sm-8">
<input type="date" id="assigndate" name="assigndate" class="form-control" v-model="assigndate">
<input type="date" id="assigndate" name="assigndate" class="form-control" v-model="assigndate" required>
</div>
</div>
@ -475,7 +472,7 @@
<div class="form-group row">
<label for="invoiceDate" class="col-sm-4">Assign Date:</label>
<div class="col-sm-8">
<input type="date" id="assigndate" name="assigndate" class="form-control" v-model="assigndate">
<input type="date" id="assigndate" name="assigndate" class="form-control" v-model="assigndate" required>
</div>
</div>
@ -625,7 +622,7 @@
qrCodeResult: null,
debounceTimeout: null,
error: "",
selectedConstraints: { facingMode: "environment" },
selectedConstraints: { facingMode: "user"},
trackFunctionSelected: { text: 'outline', value: null },
barcodeFormats: {
qr_code: true, // Hanya mendukung QR Code
@ -685,6 +682,59 @@
},
},
methods: {
sharpenFrame(videoCanvas) {
const ctx = videoCanvas.getContext("2d");
if (!ctx) return;
const { width, height } = videoCanvas;
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data;
// Sharpening kernel (edge enhancement)
const kernel = [0, -1, 0, -1, 5, -1, 0, -1, 0];
// Apply convolution
const newData = new Uint8ClampedArray(data);
const w = width * 4; // Each pixel has 4 values (RGBA)
for (let y = 1; y < height - 1; y++) {
for (let x = 1; x < width - 1; x++) {
const i = (y * width + x) * 4;
let r = 0, g = 0, b = 0;
for (let ky = -1; ky <= 1; ky++) {
for (let kx = -1; kx <= 1; kx++) {
const j = i + (ky * w) + (kx * 4);
const weight = kernel[(ky + 1) * 3 + (kx + 1)];
r += data[j] * weight;
g += data[j + 1] * weight;
b += data[j + 2] * weight;
}
}
// Apply sharpening
newData[i] = Math.min(255, Math.max(0, r));
newData[i + 1] = Math.min(255, Math.max(0, g));
newData[i + 2] = Math.min(255, Math.max(0, b));
}
}
// Update canvas with the sharpened frame
ctx.putImageData(new ImageData(newData, width, height));
},
processFrame(videoElement) {
console.log(videoElement);
console.log("Type:", typeof videoElement);
console.log(videoElement.constructor.name);
console.log(Array.isArray(videoElement)); // Check if it's an array
console.log(Object.keys(videoElement));
for (const key in videoElement) {
if (videoElement[key] instanceof HTMLVideoElement) {
console.log("Found HTMLVideoElement at:", key, videoElement[key]);
}
}
},
// Split Url dapatkan unique ID Je
onDecode(detectedCodes) {
// const endTime = performance.now();
@ -713,9 +763,95 @@
},
//Setting Camera
async onCameraReady() {
async onCameraReady(videoElement) {
const video = document.querySelector("video");
console.log("📸 Found Video Element:", video);
console.log("🎬 Video.srcObject:", video?.srcObject);
const track = video.srcObject.getVideoTracks()[0];
console.log("🎞 Video Track:", track);
if (track && track.getCapabilities) {
const capabilities = track.getCapabilities(); // Get camera capabilities
console.log("🎛 Camera Capabilities:", capabilities);
if (capabilities.sharpness) {
// track.applyConstraints({
// advanced: [ { sharpness: 100 }, Max sharpness
// { contrast: 50 }, Boost contrast
// { brightness: 60 }, Increase brightness
// { frameRate: 30 }, Try max FPS
// { exposureTime: 100 }, Lower for less motion blur
// { focusMode: "continuous" }, Ensure auto-focus
// { width: 1280, height: 720 }]}) Max resolution] Max sharpness
track.applyConstraints ({
advanced: [
{ width: 1280, height: 720 }
]
})
.then(() => console.log("✅ Sharpness applied"))
.catch(err => console.error("❌ Failed to apply sharpness:", err));
} else {
console.warn("⚠️ Sharpness not supported on this camera");
}
}
console.log("📷 Applied Constraints:", track.getSettings());
console.log("Is it a video element?", videoElement instanceof HTMLVideoElement);
console.log("Camera Ready! Video element:", videoElement);
this.videoElement = videoElement; // Store for later use
// this.scanStartTime = performance.now(); Start timing
// this.scanTime = null; Reset previous scan time
// if (!videoElement || !videoElement.srcObject) {
// console.error("❌ No video source found.");
// return;
// }
// const track = videoElement.srcObject.getVideoTracks()[0];
// if (!videoElement) {
// console.error("❌ No video element provided.");
// return;
// } else {
// console.log(" video element provided.");
// }
// Wait a bit to ensure the video stream is attached
// await new Promise(resolve => setTimeout(resolve, 1000));
// if (!videoElement.srcObject) {
// console.error("❌ No video source (srcObject is missing).");
// return;
// }
// const track = videoElement.srcObject.getVideoTracks()[0];
// if (!track) {
// console.error("❌ No video track found.");
// return;
// }
// const capabilities = track.getCapabilities();
// console.log("📸 Camera Capabilities:", capabilities); Debugging
// if ("sharpness" in capabilities) {
// try {
// await track.applyConstraints({
// advanced: [{ sharpness: 100 }] Max sharpness
// });
// console.log("✅ Sharpness applied:", track.getSettings().sharpness);
// } catch (error) {
// console.error("❌ Failed to apply sharpness:", error);
// }
// } else {
// console.warn("⚠️ Sharpness not supported on this camera.");
// };
try {
const devices = await navigator.mediaDevices.enumerateDevices();
@ -868,10 +1004,10 @@
});
if (response.ok) {
// If the form submission was successful, display a success message
alert('Success!', 'Item form has been successfully submitted.', 'success');
// alert('Success!', 'Item form has been successfully submitted.', 'success');
const updatedItem = await response.json();
// this.items.push(updatedItem);
console.log(updatedItem);
// console.log(updatedItem);
// Reset the form
this.resetForm();
@ -900,7 +1036,7 @@
this.thisItem = await response.json();
this.itemlateststatus = this.thisItem.latestStatus ? this.thisItem.latestStatus : this.thisItem.toOther;
this.itemassignedtouser = this.thisItem.toUser == this.currentUser.id || this.thisItem.lastUser == this.currentUser.id ? true : false;
console.log(this.thisItem);
// console.log(this.thisItem);
// if ((this.thisItem.toOther === "Repair" || this.thisItem.toOther === "Calibration" || this.thisItem.toOther === "Return" ) && this.thisItem.lastUser === this.currentUser.id && this.thisItem.movementComplete === false) {
@ -1013,7 +1149,7 @@
throw new Error('Failed to fetch item');
}
this.stationlist = await response.json();
console.log(this.stationlist);
// console.log(this.stationlist);
}

View File

@ -726,6 +726,8 @@
showDetails: false,
}));
console.log(this.itemMovements);
this.renderTables();
} catch (error) {
console.error("Error fetching item:", error);