Merge branch 'Dev2' of https://git.pstw.com.my/catalyx/PSTW_CentralizeSystem into Dev2
This commit is contained in:
commit
f4fc5dc103
@ -15,16 +15,41 @@
|
|||||||
.table td img {
|
.table td img {
|
||||||
display: block !important;
|
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>
|
</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">
|
||||||
<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>
|
<h2 for="sortSelect" class="col-sm-1 col-form-h2" style="min-width:140px;">Sort by:</h2>
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<select id="sortSelect" class="form-control" v-model="sortBy" v-on:change="handleSorting">
|
<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="item">Item</option>
|
||||||
<option value="logs">Logs</option>
|
<option value="station">Station</option>
|
||||||
|
<option value="logs" v-if="currentRole == 'Super Admin'">Logs</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -34,7 +59,14 @@
|
|||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<input type="text" class="form-control" v-model="searchQuery" placeholder="Search by item code...">
|
<input type="text" class="form-control" v-model="searchQuery" placeholder="Search by item code...">
|
||||||
</div>
|
</div>
|
||||||
</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 v-if="sortBy === 'all'">
|
||||||
<div class="row card">
|
<div class="row card">
|
||||||
@ -104,37 +136,36 @@
|
|||||||
<strong>Latest Movement</strong>
|
<strong>Latest Movement</strong>
|
||||||
<div class="col-md-12 d-flex flex-wrap align-items-center gap-3 p-2 border-bottom">
|
<div class="col-md-12 d-flex flex-wrap align-items-center gap-3 p-2 border-bottom">
|
||||||
<!-- Movement Type -->
|
<!-- Movement Type -->
|
||||||
<h3 :class="{'text-primary': movement.toOther === 'On Delivery', 'text-warning': movement.toOther !== 'On Delivery' && movement.action !== 'Assign',
|
<h3 :class="{'text-primary': movement.toOther === 'On Delivery', 'text-warning': movement.toOther === 'Return',
|
||||||
'text-info': movement.action === 'Assign'}"
|
'text-success': movement.toStation !== null, 'text-info': movement.action === 'Assign' && movement.toStation === null}"
|
||||||
class="flex-shrink-0 text-nowrap"
|
class="flex-shrink-0 text-nowrap" style="max-width:90px; min-width:90px;">
|
||||||
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>
|
</h3>
|
||||||
|
|
||||||
<!-- Send Date -->
|
<!-- Send Date -->
|
||||||
<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: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">{{ movement.sendDate }}</span>
|
<span class="fixed-value text-truncate">{{ movement.sendDate }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Receive Date -->
|
<!-- 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>
|
<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>
|
</div>
|
||||||
|
|
||||||
<!-- Action -->
|
<!-- 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>
|
<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>
|
</div>
|
||||||
|
|
||||||
<!-- Status -->
|
<!-- 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>
|
<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>
|
</div>
|
||||||
|
|
||||||
<!-- More Details Button -->
|
<!-- More Details Button -->
|
||||||
@ -143,7 +174,8 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Completion Status -->
|
<!-- 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') }}
|
{{ movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'Complete' : (movement.latestStatus === 'Ready To Deploy' ? 'Canceled' : 'Incomplete') }}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
@ -153,7 +185,8 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4 text-center">
|
<div class="col-md-4 text-center">
|
||||||
<!-- Conditionally render Start Icon -->
|
<!-- 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>
|
<i v-else class="fas fa-warehouse fa-2x"></i>
|
||||||
<p><strong>Start</strong></p>
|
<p><strong>Start</strong></p>
|
||||||
<p><strong>User:</strong> {{ movement.toUserName }}</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 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">
|
<div class="col-md-12 d-flex flex-wrap align-items-center gap-3 p-2 border-bottom">
|
||||||
<!-- Movement Type -->
|
<!-- Movement Type -->
|
||||||
<h3 :class="{'text-primary': movement.toOther === 'On Delivery', 'text-warning': movement.toOther !== 'On Delivery' && movement.action !== 'Assign',
|
<h3 :class="{'text-primary': movement.toOther === 'On Delivery', 'text-warning': movement.toOther === 'Return',
|
||||||
'text-info': movement.action === 'Assign'}"
|
'text-success': movement.toStation !== null, 'text-info': movement.action === 'Assign' && movement.toStation === null}"
|
||||||
class="flex-shrink-0 text-nowrap"
|
class="flex-shrink-0 text-nowrap" style="max-width:90px; min-width:90px;">
|
||||||
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>
|
</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: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>
|
<span class="fixed-value text-truncate">{{ movement.sendDate }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Receive Date -->
|
<!-- 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>
|
<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>
|
||||||
@ -237,12 +269,12 @@
|
|||||||
More Details
|
More Details
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Completion Status -->
|
<!-- Completion Status -->
|
||||||
<h4 :class="movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'text-success' : 'text-danger'"
|
<h4 :class="movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'text-success' : 'text-danger'"
|
||||||
class="text-nowrap ms-3">
|
class="text-nowrap ms-3">
|
||||||
{{ movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'Complete' : (movement.latestStatus === 'Ready To Deploy' ? 'Canceled' : 'Incomplete') }}
|
{{ movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'Complete' : (movement.latestStatus === 'Ready To Deploy' ? 'Canceled' : 'Incomplete') }}
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- 📌 Details Section (Hidden by Default) -->
|
<!-- 📌 Details Section (Hidden by Default) -->
|
||||||
@ -250,8 +282,8 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4 text-center">
|
<div class="col-md-4 text-center">
|
||||||
<!-- Conditionally render Start Icon -->
|
<!-- 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 class="fas fa-warehouse fa-2x"></i>
|
<i v-else-if="movement.toOther !== 'On Delivery'" class="fas fa-user fa-2x"></i>
|
||||||
<p><strong>Start</strong></p>
|
<p><strong>Start</strong></p>
|
||||||
<p><strong>User:</strong> {{ movement.toUserName }}</p>
|
<p><strong>User:</strong> {{ movement.toUserName }}</p>
|
||||||
<p><strong>Station:</strong> {{ movement.toStationName }}</p>
|
<p><strong>Station:</strong> {{ movement.toStationName }}</p>
|
||||||
@ -289,6 +321,222 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</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 fade" id="remarkModal" tabindex="-1" aria-labelledby="remarkModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
@ -346,36 +594,6 @@
|
|||||||
const app = Vue.createApp({
|
const app = Vue.createApp({
|
||||||
data() {
|
data() {
|
||||||
return {
|
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,
|
warranty: 0,
|
||||||
EndWDate: null,
|
EndWDate: null,
|
||||||
invoiceNo: null,
|
invoiceNo: null,
|
||||||
@ -386,16 +604,6 @@
|
|||||||
stations: [],
|
stations: [],
|
||||||
stores: [],
|
stores: [],
|
||||||
users:[],
|
users:[],
|
||||||
// suppliers: [
|
|
||||||
// {
|
|
||||||
// supplierId: 1,
|
|
||||||
// supplierName: "Pang",
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// supplierId: 2,
|
|
||||||
// supplierName: "Ms Kim",
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
isModalOpen: false,
|
isModalOpen: false,
|
||||||
selectedProduct: "",
|
selectedProduct: "",
|
||||||
selectedSupplier: "",
|
selectedSupplier: "",
|
||||||
@ -404,45 +612,107 @@
|
|||||||
selectedTeamType: "",
|
selectedTeamType: "",
|
||||||
selectedtoStation: "",
|
selectedtoStation: "",
|
||||||
consignmentNoteUrl: "",
|
consignmentNoteUrl: "",
|
||||||
// currencies: {},
|
|
||||||
showItemModal: false,
|
showItemModal: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
// thisQRInfo: {
|
|
||||||
// uniqueID: null,
|
|
||||||
// departmentName: null,
|
|
||||||
// serialNumber: null,
|
|
||||||
// endWDate: null,
|
|
||||||
// },
|
|
||||||
items: [],
|
items: [],
|
||||||
currentUser: null,
|
currentUser: null,
|
||||||
currentUserCompanyDept: null,
|
currentUserCompanyDept: null,
|
||||||
sortBy: "item",
|
sortBy: "all",
|
||||||
searchQuery: "",
|
searchQuery: "",
|
||||||
categoryVisible: {},
|
categoryVisible: {},
|
||||||
historyVisible: {},
|
historyVisible: {},
|
||||||
detailsVisible: {},
|
detailsVisible: {},
|
||||||
currentRole:"",
|
currentRole:"",
|
||||||
|
stationName: "",
|
||||||
|
searchStation: "",
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.fetchItem();
|
this.fetchItem();
|
||||||
|
console.log("Filtered Station:", this.filteredStation);
|
||||||
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
groupedItems() {
|
groupedItems() {
|
||||||
return this.items.reduce((acc, movement) => {
|
return this.items.reduce((acc, movement) => {
|
||||||
if (!acc[movement.itemId]) {
|
if (!acc[movement.itemId]) {
|
||||||
acc[movement.itemId] = {
|
acc[movement.itemId] = {
|
||||||
uniqueID: movement.uniqueID,
|
uniqueID: movement.uniqueID,
|
||||||
movements: [],
|
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() {
|
filteredItems() {
|
||||||
if (!this.searchQuery.trim()) {
|
if (!this.searchQuery.trim()) {
|
||||||
return this.groupedItems;
|
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: {
|
methods: {
|
||||||
@ -614,6 +902,8 @@
|
|||||||
if(this.currentRole == "Super Admin"){
|
if(this.currentRole == "Super Admin"){
|
||||||
this.items = await response.json();
|
this.items = await response.json();
|
||||||
|
|
||||||
|
// console.log(this.items);
|
||||||
|
this.initAllTables();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
@ -623,8 +913,10 @@
|
|||||||
item.toStore === this.currentUser.store
|
item.toStore === this.currentUser.store
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.initAllTables();
|
||||||
|
|
||||||
console.log(this.items);
|
|
||||||
|
// console.log(this.items);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -759,10 +1051,10 @@
|
|||||||
|
|
||||||
if (roles.includes("SuperAdmin")) {
|
if (roles.includes("SuperAdmin")) {
|
||||||
this.currentRole = "Super Admin";
|
this.currentRole = "Super Admin";
|
||||||
this.sortBy = 'logs';
|
// this.sortBy = 'logs';
|
||||||
} else if (roles.includes("Inventory Master")) {
|
} else if (roles.includes("Inventory Master")) {
|
||||||
this.currentRole = "Inventory Master";
|
this.currentRole = "Inventory Master";
|
||||||
this.sortBy = "item";
|
// this.sortBy = "item";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -859,16 +1151,19 @@
|
|||||||
// },
|
// },
|
||||||
handleSorting() {
|
handleSorting() {
|
||||||
this.renderTables();
|
this.renderTables();
|
||||||
|
console.log(this.sortBy);
|
||||||
},
|
},
|
||||||
renderTables() {
|
renderTables() {
|
||||||
if (this.sortBy === "logs") {
|
// if (this.sortBy === "logs") {
|
||||||
// this.initAllTables();
|
// // this.initAllTables();
|
||||||
this.initiateTable();
|
// this.initiateTable();
|
||||||
} else
|
// } else
|
||||||
if (this.sortBy === "all") {
|
// if (this.sortBy === "all") {
|
||||||
this.initAllTables();
|
// this.initAllTables();
|
||||||
// this.initiateTable();
|
// // this.initiateTable();
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
this.initAllTables();
|
||||||
},
|
},
|
||||||
initAllTables() {
|
initAllTables() {
|
||||||
if (this.itemMovementNotCompleteDatatable) {
|
if (this.itemMovementNotCompleteDatatable) {
|
||||||
@ -883,15 +1178,17 @@
|
|||||||
columns: [
|
columns: [
|
||||||
{ title: "Unique Id", data: "id" },
|
{ title: "Unique Id", data: "id" },
|
||||||
{ title: "Product Code", data: "uniqueID" },
|
{ title: "Product Code", data: "uniqueID" },
|
||||||
|
{ title: "Action", data: "action" },
|
||||||
|
{ title: "Send Date", data: "sendDate" },
|
||||||
{ title: "From User", data: "toUserName" },
|
{ title: "From User", data: "toUserName" },
|
||||||
{ title: "Last User", data: "lastUserName" },
|
{ title: "Last User", data: "lastUserName" },
|
||||||
{ title: "From Station", data: "toStationName" },
|
{ title: "From Station", data: "toStationName" },
|
||||||
{ title: "From Store", data: "toStoreName" },
|
{ title: "From Store", data: "toStoreName" },
|
||||||
{ title: "Action", data: "action" },
|
// { title: "Action", data: "action" },
|
||||||
{ 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: "Send Date", data: "sendDate" },
|
// { title: "Send Date", data: "sendDate" },
|
||||||
{
|
{
|
||||||
title: "Note",
|
title: "Note",
|
||||||
data: "consignmentNote",
|
data: "consignmentNote",
|
||||||
@ -932,19 +1229,22 @@
|
|||||||
columns: [
|
columns: [
|
||||||
{ title: "Unique Id", data: "id" },
|
{ title: "Unique Id", data: "id" },
|
||||||
{ title: "Product Code", data: "uniqueID" },
|
{ 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: "From User", data: "toUserName" },
|
||||||
{ title: "Last User", data: "lastUserName" },
|
{ title: "Last User", data: "lastUserName" },
|
||||||
{ title: "From Station", data: "toStationName" },
|
{ title: "From Station", data: "toStationName" },
|
||||||
{ title: "Last Station", data: "lastStationName" },
|
{ title: "Last Station", data: "lastStationName" },
|
||||||
{ title: "From Store", data: "toStoreName" },
|
{ title: "From Store", data: "toStoreName" },
|
||||||
{ title: "Last Store", data: "lastStoreName" },
|
{ title: "Last Store", data: "lastStoreName" },
|
||||||
{ title: "Action", data: "action" },
|
// { title: "Action", data: "action" },
|
||||||
{ 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: "Qty", data: "quantity" },
|
||||||
{ title: "Send Date", data: "sendDate" },
|
// { title: "Send Date", data: "sendDate" },
|
||||||
{ title: "Receive Date", data: "receiveDate" },
|
// { title: "Receive Date", data: "receiveDate" },
|
||||||
{ title: "Note",
|
{ title: "Note",
|
||||||
data: "consignmentNote",
|
data: "consignmentNote",
|
||||||
render: function (data, type, full, meta) {
|
render: function (data, type, full, meta) {
|
||||||
@ -978,6 +1278,50 @@
|
|||||||
order: [[0, "desc"]],
|
order: [[0, "desc"]],
|
||||||
responsive: true,
|
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) {
|
toggleCategory(itemId) {
|
||||||
this.categoryVisible[itemId] = !this.categoryVisible[itemId];
|
this.categoryVisible[itemId] = !this.categoryVisible[itemId];
|
||||||
|
|||||||
@ -45,9 +45,6 @@
|
|||||||
v-on:error="onError">
|
v-on:error="onError">
|
||||||
</qrcode-stream>
|
</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>
|
<p class="error">{{ error }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -295,7 +292,7 @@
|
|||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="invoiceDate" class="col-sm-4">Assign Date:</label>
|
<label for="invoiceDate" class="col-sm-4">Assign Date:</label>
|
||||||
<div class="col-sm-8">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -359,7 +356,7 @@
|
|||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="invoiceDate" class="col-sm-4">Assign Date:</label>
|
<label for="invoiceDate" class="col-sm-4">Assign Date:</label>
|
||||||
<div class="col-sm-8">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -410,7 +407,7 @@
|
|||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="invoiceDate" class="col-sm-4">Assign Date:</label>
|
<label for="invoiceDate" class="col-sm-4">Assign Date:</label>
|
||||||
<div class="col-sm-8">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -475,7 +472,7 @@
|
|||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="invoiceDate" class="col-sm-4">Assign Date:</label>
|
<label for="invoiceDate" class="col-sm-4">Assign Date:</label>
|
||||||
<div class="col-sm-8">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -625,7 +622,7 @@
|
|||||||
qrCodeResult: null,
|
qrCodeResult: null,
|
||||||
debounceTimeout: null,
|
debounceTimeout: null,
|
||||||
error: "",
|
error: "",
|
||||||
selectedConstraints: { facingMode: "environment" },
|
selectedConstraints: { facingMode: "user"},
|
||||||
trackFunctionSelected: { text: 'outline', value: null },
|
trackFunctionSelected: { text: 'outline', value: null },
|
||||||
barcodeFormats: {
|
barcodeFormats: {
|
||||||
qr_code: true, // Hanya mendukung QR Code
|
qr_code: true, // Hanya mendukung QR Code
|
||||||
@ -685,6 +682,59 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
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
|
// Split Url dapatkan unique ID Je
|
||||||
onDecode(detectedCodes) {
|
onDecode(detectedCodes) {
|
||||||
// const endTime = performance.now();
|
// const endTime = performance.now();
|
||||||
@ -713,9 +763,95 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
//Setting Camera
|
//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.scanStartTime = performance.now(); Start timing
|
||||||
// this.scanTime = null; Reset previous scan time
|
// 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 {
|
try {
|
||||||
const devices = await navigator.mediaDevices.enumerateDevices();
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||||
@ -868,10 +1004,10 @@
|
|||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
// If the form submission was successful, display a success message
|
// 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();
|
const updatedItem = await response.json();
|
||||||
// this.items.push(updatedItem);
|
// this.items.push(updatedItem);
|
||||||
console.log(updatedItem);
|
// console.log(updatedItem);
|
||||||
|
|
||||||
// Reset the form
|
// Reset the form
|
||||||
this.resetForm();
|
this.resetForm();
|
||||||
@ -900,7 +1036,7 @@
|
|||||||
this.thisItem = await response.json();
|
this.thisItem = await response.json();
|
||||||
this.itemlateststatus = this.thisItem.latestStatus ? this.thisItem.latestStatus : this.thisItem.toOther;
|
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;
|
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) {
|
// 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');
|
throw new Error('Failed to fetch item');
|
||||||
}
|
}
|
||||||
this.stationlist = await response.json();
|
this.stationlist = await response.json();
|
||||||
console.log(this.stationlist);
|
// console.log(this.stationlist);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -726,6 +726,8 @@
|
|||||||
showDetails: false,
|
showDetails: false,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
console.log(this.itemMovements);
|
||||||
|
|
||||||
this.renderTables();
|
this.renderTables();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching item:", error);
|
console.error("Error fetching item:", error);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user