PSTW_CentralizeSystem/Areas/Inventory/Views/ItemMovement/ItemMovementUser.cshtml
2025-07-24 16:58:02 +08:00

1164 lines
62 KiB
Plaintext
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@{
ViewData["Title"] = "Item Movement";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<style>
.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 */
}
.dropdown {
position: relative;
width: 100%;
}
.dropdown-toggle-box {
display: flex;
align-items: center;
border: 1px solid #ddd;
border-radius: 5px;
overflow: hidden;
}
.dropdown-toggle-box input {
flex: 1;
border: none;
padding: 8px;
}
.dropdown-btn {
border: none;
background-color: #007bff;
color: white;
padding: 8px 12px;
cursor: pointer;
}
.dropdown-content {
position: absolute;
width: 100%;
max-height: 200px;
overflow-y: auto;
background: #fff;
border: 1px solid #ddd;
z-index: 10;
}
.dropdown-content option {
padding: 10px;
cursor: pointer;
display: block;
}
.dropdown-content option:hover {
background-color: #f1f1f1;
}
</style>
@await Html.PartialAsync("~/Areas/Inventory/Views/_InventoryPartialUser.cshtml")
<div id="ItemMovement" class="row">
<div class="row mb-3">
<h2 for="sortSelect" class="col-sm-1 col-form-h2" style="min-width:150px;">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="item">Item</option>
<option value="station">Station</option>
</select>
</div>
</div>
<div class="row mb-3" v-if="sortBy === 'item'">
<h4 class="col-sm-1 col-form-h2" style="min-width:150px;">Search Item:</h4>
<div class="col-sm-4">
<input type="text" class="form-control" v-model="searchQuery" placeholder="Search by item code...">
</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">
<div class="dropdown" v-click-outside="closeDropdown">
<!-- Button + Input dalam satu box -->
<div class="dropdown-toggle-box" v-on:click="dropdownOpen = !dropdownOpen">
<input type="text" class="form-control" v-model="searchQueryStation"
placeholder="Search Station..." v-on:focus="dropdownOpen = true" v-on:click.stop />
<button type="button" class="btn btn-primary dropdown-btn" v-on:click.stop="dropdownOpen = !dropdownOpen">
</button>
</div>
<!-- Dropdown list -->
<div v-if="dropdownOpen" class="dropdown-content" v-on:click.stop>
<div v-for="(item, index) in stations"
:key="index" class="dropdown-item" v-on:mousedown.prevent="selectStation(item)">
{{ item.stationName }}
</div>
</div>
</div>
</div>
</div>
<div v-if="sortBy === 'all'">
<h5 style="color:cadetblue">*Each Items will display one record only*</h5>
<div class="row card">
<div class="card-header">
<h2>Pending Item Transit</h2>
</div>
<div class="card-body">
<table class="table table-bordered table-hover table-striped no-wrap" id="itemMovementNotCompleteDatatable"
style="width:100%;border-style: solid; border-width: 1px"></table>
</div>
</div>
<div class="row card">
<div class="card-header">
<h2>Complete Item Movement</h2>
</div>
<div class="card-header">
<h4>All Item Movement</h4>
</div>
<div class="card-body">
<table class="table table-bordered table-hover table-striped no-wrap" id="itemMovementCompleteDatatable"
style="width:100%;border-style: solid; border-width: 1px"></table>
</div>
<div class="card-header">
<h4>Assign Station</h4>
</div>
<div class="card-body">
<table class="table table-bordered table-hover table-striped no-wrap" id="assignStationDatatable"
style="width:100%;border-style: solid; border-width: 1px"></table>
</div>
</div>
</div>
<!--------------------------------------------ITEM CATEGORY---------------------------------------------------------------------->
<div v-if="sortBy === 'item'">
<div v-for="(group, itemId) in paginatedItems" :key="itemId" class="row card">
<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>
<!-- Hide all details unless button is clicked -->
<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">{{ formatDate(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">
<!-- Conditionally render Start Icon -->
<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">
<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>
<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">
<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">{{ formatDate(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">
<!-- Conditionally render Start Icon -->
<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">
<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>
<!--------------------------------------------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">{{ formatDate(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">{{ formatDate(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.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">
<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>
<!--------------------------------------------REMARK & CONSIGNMENT NOTE CATEGORY---------------------------------------------------------------------->
<div class="modal fade" id="remarkModal" tabindex="-1" aria-labelledby="remarkModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="remarkModalLabel">Remark</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="remarkContent">
<!-- Remark Content Here -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="consignmentModal" tabindex="-1" aria-labelledby="consignmentModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="consignmentModalLabel">Consignment Note</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body text-center">
<img v-if="/\.(jpeg|jpg|png|gif)$/i.test(consignmentNoteUrl)" :src="consignmentNoteUrl" class="img-fluid" alt="Consignment Note Image">
<iframe v-else-if="/\.pdf$/i.test(consignmentNoteUrl)" :src="consignmentNoteUrl" style="width:100%; height: 80vh;"></iframe>
<a v-else class="btn btn-primary">There's no Folder or Picture</a>
</div>
</div>
</div>
</div>
<div class="d-flex justify-content-center align-items-center mt-3" v-if="sortBy === 'item' && totalPages > 0">
<button class="btn btn-secondary mx-1" v-on:click="goToPage(currentPage - 1)" :disabled="currentPage === 1">
Previous
</button>
<span class="mx-2">Page {{ currentPage }} of {{ totalPages }}</span>
<button class="btn btn-secondary mx-1" v-on:click="goToPage(currentPage + 1)" :disabled="currentPage === totalPages">
Next
</button>
</div>
<div class="d-flex justify-content-center align-items-center mt-3" v-if="sortBy === 'station' && totalPagesStation > 0">
<button class="btn btn-secondary mx-1" v-on:click="goToPageStation(currentPageStation - 1)" :disabled="currentPageStation === 1">
Previous
</button>
<span class="mx-2">Page {{ currentPageStation }} of {{ itemsPerPageStation }}</span>
<button class="btn btn-secondary mx-1" v-on:click="goToPageStation(currentPageStation + 1)" :disabled="currentPageStation === totalPages">
Next
</button>
</div>
</div>
@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
<script>
$(function () {
app.mount('#ItemMovement');
});
const app = Vue.createApp({
data() {
return {
userId: null,
itemMovements: [],
assignStationDatatable: null,
itemMovementCompleteDatatable: null,
itemMovementNotCompleteDatatable: null,
searchQuery: "",
searchQueryStation: "",
searchStation: "",
sortBy: "all",
historyVisible: {},
detailsVisible: {},
categoryVisible: {},
consignmentNoteUrl: "",
stationName: "",
currentPage: 1,
itemsPerPage: 10,
currentPageStation: 1,
itemsPerPageStation: 10,
dropdownOpen: false,
currentUser: null,
stations:[],
};
},
computed: {
paginatedItems() {
const start = (this.currentPage - 1) * this.itemsPerPage;
const end = start + this.itemsPerPage;
return Object.fromEntries(
Object.entries(this.filteredItems).slice(start, end)
);
},
totalPages() {
return Math.ceil(Object.keys(this.filteredItems).length / this.itemsPerPage);
},
totalPagesStation() {
return Math.ceil(Object.keys(this.filteredStation).length / this.itemsPerPage);
},
processedGroupedItems() {
let grouped = this.itemMovements.reduce((acc, movement) => {
if (!acc[movement.itemId]) {
acc[movement.itemId] = {
uniqueID: movement.uniqueID,
movements: [],
};
}
acc[movement.itemId].movements.push(movement);
return acc;
}, {});
let filteredGrouped = {};
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
);
let nextIndex = movements.findIndex(m =>
m.latestStatus === 'Ready To Deploy' && m.movementComplete == 1
);
if (stopIndex !== -1) {
movements = movements.slice(0, stopIndex);
}
if (nextIndex !== -1) {
movements = movements.slice(0, nextIndex);
}
if (movements.length > 0) {
filteredGrouped[itemId] = {
uniqueID: grouped[itemId].uniqueID,
movements: movements,
};
}
}
return filteredGrouped;
},
groupedByStation() {
let groupedByItem = this.itemMovements.reduce((acc, movement) => {
if (!acc[movement.uniqueID]) {
acc[movement.uniqueID] = {
uniqueID: movement.uniqueID,
movements: [],
};
}
acc[movement.uniqueID].movements.push(movement);
return acc;
}, {});
let groupedByStation = {};
Object.keys(groupedByItem).forEach(itemId => {
let movements = groupedByItem[itemId].movements
.sort((a, b) => b.id - a.id); // Newest → Oldest
// Find first occurrence of 'Return' complete
let stopIndex = movements.findIndex(m =>
m.toOther === 'Return' && m.movementComplete == 1
);
let nextIndex = movements.findIndex(m =>
m.latestStatus === 'Ready To Deploy' && m.movementComplete == 1
);
if (stopIndex !== -1) {
movements = movements.slice(0, stopIndex);
}
if (nextIndex !== -1) {
movements = movements.slice(0, nextIndex);
}
if (movements.length > 0) {
let latestMovement = movements[0];
let station = latestMovement.lastStationName || latestMovement.toStationName || "Self Assigned";
if (!groupedByStation[station]) {
groupedByStation[station] = {};
}
groupedByStation[station][itemId] = { uniqueID: itemId, movements };
}
});
// 4⃣ **Sort stations & move 'Unassign Station' to last**
let sortedKeys = Object.keys(groupedByStation).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] = groupedByStation[key];
});
return sortedGrouped;
},
filteredItems() {
if (!this.searchQuery.trim()) {
return this.processedGroupedItems;
}
const searchLower = this.searchQuery.toLowerCase();
let grouped = this.processedGroupedItems;
let filtered = {};
Object.keys(grouped).forEach(item => {
if (item.toLowerCase().includes(searchLower)) {
if (grouped[item] > 0) {
filtered[item] = grouped[item];
}
}
});
return filtered;
},
filteredStation() {
if (!this.searchQueryStation) {
return this.groupedByStation;
}
let searchQueryStation = this.searchQueryStation.toLowerCase();
let grouped = this.groupedByStation;
let filtered = {};
Object.keys(grouped).forEach(station => {
if (station.toLowerCase().includes(searchQueryStation)) {
filtered[station] = grouped[station];
}
});
return filtered;
},
},
async mounted() {
this.fetchItemMovement();
await this.fetchUser();
await Promise.all([
this.fetchStation(),
]);
},
methods: {
formatDate(dateString) {
if (!dateString) return '';
const date = new Date(dateString);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
},
paginatedItemsStation(item) {
const start = (this.currentPageStation - 1) * this.itemsPerPageStation;
const end = start + this.itemsPerPageStation;
return Object.fromEntries(
Object.entries(item).slice(start, end)
);
},
selectStation(item) {
this.searchQueryStation = item.stationName;
this.dropdownOpen = false;
},
closeDropdown() {
this.dropdownOpen = false; // Tutup dropdown
},
goToPage(page) {
if (page >= 1 && page <= this.totalPages) {
this.currentPage = page;
}
},
goToPageStation(page) {
if (page >= 1 && page <= this.itemsPerPageStation) {
this.currentPageStation = page;
}
},
remark(remark) {
document.getElementById("remarkContent").innerText = remark || "No remark message provide.";
let modal = new bootstrap.Modal(document.getElementById("remarkModal"));
modal.show();
},
remark(remark) {
document.getElementById("remarkContent").innerText = remark || "No remark message provide.";
let modal = new bootstrap.Modal(document.getElementById("remarkModal"));
modal.show();
},
consignmentNote(consignmentNote) {
if (!consignmentNote) {
this.consignmentNoteUrl = "No consignment note available.";
new bootstrap.Modal(document.getElementById('consignmentModal')).show();
return;
}
// Pastikan URL betul
this.consignmentNoteUrl = consignmentNote;
// Tunggu Vue update sebelum buka modal
this.$nextTick(() => {
new bootstrap.Modal(document.getElementById('consignmentModal')).show();
});
},
async fetchItemMovement() {
try {
const response = await fetch("/InvMainAPI/ItemMovementUser", {
method: "POST",
headers: { "Content-Type": "application/json" },
});
if (!response.ok) throw new Error("Failed to fetch item movement");
const data = await response.json();
this.itemMovements = data.map((movement) => ({
...movement,
showDetails: false,
}));
this.renderTables();
} catch (error) {
console.error("Error fetching item:", error);
}
},
async fetchStation() {
try {
const response = await fetch('/InvMainAPI/StationList', {
method: 'POST', // Specify the HTTP method
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error('Failed to fetch Station');
}
const data = await response.json();
this.stations = data.filter(station => station.stationPicID === this.userId);
} catch (error) {
console.error('Error fetching Station:', error);
}
},
async fetchUser() {
try {
const response = await fetch(`/IdentityAPI/GetUserInformation/`, {
method: 'POST',
});
if (response.ok) {
const data = await response.json();
this.currentUser = data?.userInfo || null;
this.userId = await this.currentUser.id;
}
else {
console.error(`Failed to fetch user: ${response.statusText}`);
}
}
catch (error) {
console.error('There was a problem with the fetch operation:', error);
}
},
handleSorting() {
this.renderTables();
},
renderTables() {
if (this.sortBy === "all") {
this.initAllTables();
}
},
initAllTables() {
if (this.itemMovementNotCompleteDatatable) {
this.itemMovementNotCompleteDatatable.destroy();
}
if (this.itemMovementCompleteDatatable) {
this.itemMovementCompleteDatatable.destroy();
}
if (this.assignStationDatatable) {
this.assignStationDatatable.destroy();
}
// Get latest movement per uniqueID after filtering
function getLatestMovements(data) {
let latestMovements = {};
data.forEach(movement => {
let id = movement.uniqueID;
if (!latestMovements[id] || latestMovements[id].id < movement.id) {
latestMovements[id] = movement;
}
});
return Object.values(latestMovements);
}
// Filter movements based on conditions
function filterMovements(movements) {
let stopIndex = movements.findIndex(m =>
m.toOther === 'Return' && m.movementComplete == 1
);
let nextIndex = movements.findIndex(m =>
m.latestStatus === 'Ready To Deploy' && m.movementComplete == 1
);
if (stopIndex !== -1) {
movements = movements.slice(0, stopIndex);
}
if (nextIndex !== -1) {
movements = movements.slice(0, nextIndex);
}
return movements;
}
let latestMovements = getLatestMovements(this.itemMovements);
let notCompleteData = [];
let completeData = [];
let completeStationData = [];
latestMovements.forEach(movement => {
let filteredMovements = filterMovements([movement]);
if (filteredMovements.length > 0) {
if (movement.movementComplete == 0) {
notCompleteData.push(movement);
} else if (movement.movementComplete == 1 && movement.action !== "Assign") {
completeData.push(movement);
} else {
completeStationData.push(movement);
}
}
});
// Table 1: Not Complete Movements
this.itemMovementNotCompleteDatatable = $("#itemMovementNotCompleteDatatable").DataTable({
data: notCompleteData,
columns: [
{ title: "Unique Id", data: "id" },
{ title: "Product Name", data: "productName", render: (data, type, full) => { return `${data} <br> ${renderFile(full.productImage)}`; } },
{ title: "Product Code", data: "uniqueID" },
{ title: "Action", data: "action" },
{ title: "Send Date", data: "sendDate", render: this.formatDate.bind(this) },
{ title: "Start Status", data: "toOther" },
{ title: "From User", data: "toUserName" },
{ title: "Last User", data: "lastUserName" },
{ title: "From Station", data: "toStationName" },
{ title: "From Store", data: "toStoreName" },
{ title: "Quantity", data: "quantity" },
{ title: "Note", data: "consignmentNote", render: renderFile },
{ title: "Remark", data: "remark" },
],
responsive: true,
});
// Table 2: Completed Movements
this.itemMovementCompleteDatatable = $("#itemMovementCompleteDatatable").DataTable({
data: completeData,
columns: [
{ title: "Unique Id", data: "id" },
{ title: "Product Name", data: "productName", render: (data, type, full) => { return `${data} <br> ${renderFile(full.productImage)}`; } },
{ title: "Product Code", data: "uniqueID" },
{ title: "Send Date", data: "sendDate", render: this.formatDate.bind(this) },
{ title: "Receive Date", data: "receiveDate" },
{ title: "Action", data: "action" },
{ title: "Start Status", data: "toOther" },
{ title: "Latest Status", data: "latestStatus" },
{ 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: "Qty", data: "quantity" },
{ title: "Note", data: "consignmentNote", render: renderFile },
{ title: "Remark", data: "remark" },
],
responsive: true,
});
// Table 3: Station Movements
this.assignStationDatatable = $("#assignStationDatatable").DataTable({
data: completeStationData,
columns: [
{ title: "Unique Id", data: "id" },
{ title: "Product Name", data: "productName", render: (data, type, full) => { return `${data} <br> ${renderFile(full.productImage)}`; }},
{ title: "Product Code", data: "uniqueID" },
{ title: "Assign Date", data: "sendDate", render: this.formatDate.bind(this) },
{ title: "Action", data: "action" },
{ title: "Station User PIC", data: "toUserName" },
{ title: "From Station", data: "toStationName" },
{ title: "Last Station", data: "lastStationName" },
{ title: "Qty", data: "quantity" },
{ title: "Note", data: "consignmentNote", render: renderFile },
{ title: "Remark", data: "remark" },
],
responsive: true,
});
// Function to render file (image/PDF)
function renderFile(data, type, full, meta) {
if (!data) {
return "No Document";
}
var isImage = /\.(jpeg|jpg|png|gif)$/i.test(data);
var isPdf = /\.pdf$/i.test(data);
if (isImage) {
return `<a href="${data}" target="_blank" data-lightbox="image-1">
<img src="${data}" alt="Image" class="img-thumbnail" style="width: 100px; height: 100px;" />
</a>`;
} else if (isPdf) {
return `<a href="${data}" target="_blank">
<img src="https://upload.wikimedia.org/wikipedia/commons/8/87/PDF_file_icon.svg"
alt="PDF Document" class="img-thumbnail"
style="width: 50px; height: 50px;" />
<br>View PDF
</a>`;
} else {
return `<a href="${data}" target="_blank">Download File</a>`;
}
}
},
toggleCategory(itemId) {
this.categoryVisible[itemId] = !this.categoryVisible[itemId];
this.detailsVisible = {};
this.historyVisible = {};
},
toggleHistory(itemId) {
// Jika item yang ditekan sudah terbuka, tutup
if (this.historyVisible[itemId]) {
this.historyVisible[itemId] = false;
} else {
// Tutup semua history lain dahulu
this.historyVisible = {};
// Buka hanya item yang ditekan
this.historyVisible[itemId] = true;
}
},
toggleDetails(movementId) {
this.detailsVisible[movementId] = !this.detailsVisible[movementId];
},
},
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>
}