PSTW_CentralizeSystem/Areas/Inventory/Views/InventoryMaster/ItemMovement.cshtml
2025-03-10 08:57:27 +08:00

1393 lines
80 KiB
Plaintext
Raw 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>
@@font-face {
font-family: 'OCR-A';
src: url('../assets/fonts/ocraext.ttf');
}
.QrPrintFont {
font-family: 'OCR-A', monospace;
}
.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;
}
.text-numb {
color: brown;
}
.text-weird {
color: purple;
}
.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" >
<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="item">Item</option>
<option value="station">Station</option>
<option value="logs" v-if="currentRole == 'Super Admin'">Logs</option>
</select>
</div>
</div>
<div class="row mb-3" v-if="sortBy === 'item'">
<h4 class="col-sm-1 col-form-h2" style="min-width:140px;">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">
<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">
<h2>Pending Item Movement</h2>
</div>
<div class="card-body">
<div v-if="loading">
<div class="spinner-border text-info" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<table class="table table-bordered table-hover table-striped no-wrap" id="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-body">
<div v-if="loading">
<div class="spinner-border text-info" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<table class="table table-bordered table-hover table-striped no-wrap" id="itemMovementCompleteDatatable" style="width:100%;border-style: solid; border-width: 1px"></table>
</div>
</div>
</div>
<div v-if="sortBy === 'logs'">
<div v-if="loading">
<div class="spinner-border text-info" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<div class="row card">
<div class="card-header">
<h2>Item Movement List</h2>
</div>
<div class="card-body">
<table class="table table-bordered table-hover table-striped no-wrap" id="itemDatatable" style=" width:100%;border-style: solid; border-width: 1px"></table>
</div>
</div>
</div>
<div v-if="sortBy === 'item'">
<div v-if="loading">
<div class="spinner-border text-info" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<div v-for="(group, itemId) in filteredItems" :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">
<!-- 📌 Show Only Latest Movement -->
<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">
<!-- 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,
'text-numb': movement.toOther === 'Faulty' || movement.toOther === 'Calibration' || movement.toOther === 'Repair',
'text-weird': movement.action === 'Register'}"
class="flex-shrink-0 text-nowrap" style="max-width:140px; min-width:90px;">
{{ movement.toOther === 'Return' ? 'Return' : (movement.toOther === 'On Delivery' ? 'Receive' : ( movement.toStation !== null ? 'Change' : ( movement.toOther == 'Faulty' || movement.toOther == 'Calibration' || movement.toOther == 'Repair' ? movement.toOther : ( movement.action == 'Register' ? 'Register' : '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.action == 'Register' ? 'text-success' : movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'text-success' : movement.toOther === 'Repair' || movement.toOther === 'Calibration' && movement.latestStatus === 'Ready To Deploy' ? 'text-success' :'text-danger'"
class="text-nowrap ms-3">
{{ movement.action == 'Register' ? 'Complete' : (movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'Complete' : ( movement.toOther === 'Repair' || movement.toOther === 'Calibration' && 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><strong>User:</strong> {{ movement.toUserName }}</p>
<p><strong>Station:</strong> {{ movement.toStationName }}</p>
<p><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)" v-if="movement.toOther !== 'On Delivery'">
Remark
</button>
</p>
<p>
<button class="btn btn-info btn-sm ms-auto" v-on:click="consignmentNote(movement.consignmentNote)" v-if="movement.toOther !== 'On Delivery'">
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>
<!-- 📌 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,
'text-numb': movement.toOther === 'Faulty' || movement.toOther === 'Calibration' || movement.toOther === 'Repair',
'text-weird': movement.action === 'Register'}"
class="flex-shrink-0 text-nowrap" style="max-width:140px; min-width:90px;">
{{ movement.toOther === 'Return' ? 'Return' : (movement.toOther === 'On Delivery' ? 'Receive' : ( movement.toStation !== null ? 'Change' : ( movement.toOther == 'Faulty' || movement.toOther == 'Calibration' || movement.toOther == 'Repair' ? movement.toOther : ( movement.action == 'Register' ? 'Register' : '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.action == 'Register' ? 'text-success' : movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'text-success' : movement.toOther === 'Repair' || movement.toOther === 'Calibration' && movement.latestStatus === 'Ready To Deploy' ? 'text-success' :'text-danger'"
class="text-nowrap ms-3">
{{ movement.action == 'Register' ? 'Complete' : (movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'Complete' : ( movement.toOther === 'Repair' || movement.toOther === 'Calibration' && 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><strong>User:</strong> {{ movement.toUserName }}</p>
<p><strong>Station:</strong> {{ movement.toStationName }}</p>
<p><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)" v-if="movement.toOther !== 'On Delivery'">
Remark
</button>
</p>
<p>
<button class="btn btn-info btn-sm ms-auto" v-on:click="consignmentNote(movement.consignmentNote)" v-if="movement.toOther !== 'On Delivery'">
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,
'text-numb': movement.toOther === 'Faulty' || movement.toOther === 'Calibration' || movement.toOther === 'Repair',
'text-weird': movement.action === 'Register'}"
class="flex-shrink-0 text-nowrap" style="max-width:140px; min-width:90px;">
{{ movement.toOther === 'Return' ? 'Return' : (movement.toOther === 'On Delivery' ? 'Receive' : ( movement.toStation !== null ? 'Change' : ( movement.toOther == 'Faulty' || movement.toOther == 'Calibration' || movement.toOther == 'Repair' ? movement.toOther : ( movement.action == 'Register' ? 'Register' : '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.action == 'Register' ? 'text-success' : movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'text-success' : movement.toOther === 'Repair' || movement.toOther === 'Calibration' && movement.latestStatus === 'Ready To Deploy' ? 'text-success' :'text-danger'"
class="text-nowrap ms-3">
{{ movement.action == 'Register' ? 'Complete' : (movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'Complete' : ( movement.toOther === 'Repair' || movement.toOther === 'Calibration' && 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,
'text-numb': movement.toOther === 'Faulty' || movement.toOther === 'Calibration' || movement.toOther === 'Repair',
'text-weird': movement.action === 'Register'}"
class="flex-shrink-0 text-nowrap" style="max-width:140px; min-width:90px;">
{{ movement.toOther === 'Return' ? 'Return' : (movement.toOther === 'On Delivery' ? 'Receive' : ( movement.toStation !== null ? 'Change' : ( movement.toOther == 'Faulty' || movement.toOther == 'Calibration' || movement.toOther == 'Repair' ? movement.toOther : ( movement.action == 'Register' ? 'Register' : '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.action == 'Register' ? 'text-success' : movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'text-success' : movement.toOther === 'Repair' || movement.toOther === 'Calibration' && movement.latestStatus === 'Ready To Deploy' ? 'text-success' :'text-danger'"
class="text-nowrap ms-3">
{{ movement.action == 'Register' ? 'Complete' : (movement.movementComplete == 1 && movement.latestStatus !== 'Ready To Deploy' ? 'Complete' : ( movement.toOther === 'Repair' || movement.toOther === 'Calibration' && 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">
<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>
</div>
@section Scripts {
@{
await Html.RenderPartialAsync("_ValidationScriptsPartial");
}
<script>
$(function () {
app.mount('#registerItem');
// Attach a click event listener to elements with the class 'btn-success'.
$('#addItemBtn').on('click', function () {
// Show the modal with the ID 'addManufacturerModal'.
$('#itemMovementModal').modal('show');
});
$('.closeModal').on('click', function () {
// Show the modal with the ID 'addManufacturerModal'.
$('.modal').modal('hide');
});
});
const app = Vue.createApp({
data() {
return {
warranty: 0,
EndWDate: null,
invoiceNo: null,
invoiceDate: null,
partNumber: null,
products: [],
depts: [],
stations: [],
stores: [],
users:[],
isModalOpen: false,
selectedProduct: "",
selectedSupplier: "",
selectedCompany: "",
selectedDepartment: "",
selectedTeamType: "",
selectedtoStation: "",
consignmentNoteUrl: "",
showItemModal: false,
loading: false,
items: [],
currentUser: null,
currentUserCompanyDept: null,
sortBy: "all",
searchQuery: "",
categoryVisible: {},
historyVisible: {},
detailsVisible: {},
currentRole:"",
stationName: "",
searchStation: "",
}
},
mounted() {
this.fetchItem();
},
computed: {
groupedItems() {
let grouped = this.items.reduce((acc, movement) => {
if (!acc[movement.itemId]) {
acc[movement.itemId] = {
uniqueID: movement.uniqueID,
movements: [],
};
}
acc[movement.itemId].movements.push(movement);
return acc;
}, {});
console.log(grouped);
// Sort items from newest to oldest & filter them
for (let itemId in grouped) {
let movements = grouped[itemId].movements
.sort((a, b) => b.id - a.id); // Newest to oldest
// console.log(movements);
// let stopIndex = movements.findIndex(m =>
// m.toOther === 'Return' && m.movementComplete == 1
// );
// if (stopIndex !== -1) {
// movements = movements.slice(0, stopIndex + 1);
// }
// Ensure at least 3 movements before stopping
let stopIndex = movements.slice(3).findIndex(m =>
m.toOther === 'Return' && m.movementComplete == 1
);
if (stopIndex !== -1) {
stopIndex += 3; // Adjust index since we sliced after the first 3
movements = movements.slice(0, stopIndex + 1); // Keep at least 3 + the "Return" row
}
grouped[itemId].movements = movements;
}
return grouped;
},
groupedByStation() {
let groupedByItem = this.items.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
// Ensure at least 3 movements before stopping
let stopIndex = movements.slice(3).findIndex(m =>
m.toOther === 'Return' && m.movementComplete == 1
);
// Remove older movements
if (stopIndex !== -1) {
stopIndex += 3; // Adjust index since we sliced after the first 3
movements = movements.slice(0, stopIndex);
}
if (movements.length > 0) {
let latestMovement = movements[0];
let station = latestMovement.lastStationName || latestMovement.toStationName || "Not 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.groupedItems;
}
const searchLower = this.searchQuery.toLowerCase();
return Object.fromEntries(
Object.entries(this.groupedItems).filter(([_, group]) =>
group.uniqueID.toLowerCase().includes(searchLower)
)
);
},
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: {
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();
});
},
initiateTable() {
this.loading = true;
self = this;
this.itemDatatable = $('#itemDatatable').DataTable({
"data": this.items,
"columns": [
{ title: "Unique Id", data: "id" },
{ title: "Product Code", data: "uniqueID" },
{ title: "From User", data: "toUserName" },
{ title: "Last User", data: "lastUserName" },
{ title: "Completion", data: "movementComplete", render: function (data) {return data ? "Completed" : "Pending";}},
{ 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: "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: "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" },
],
order: [[0, "desc"]], // Sorting by "Product Code" (uniqueID) in descending order
responsive: true,
drawCallback: function (settings) {
// Generate QR codes after rows are rendered
// const api = this.api();
// api.rows().every(function () {
// const data = this.data();
// const containerId = `qr${data.id}`; containerid is by increments from API
// const container = $(`#${containerId}`);
// container.empty();
// container.append(`${data.item.itemId}`);
// if (container.length) {
// new QRCode(container[0], {
// text: data.qrString,
// width: 100,
// height: 100,
// colorDark: "#000000",
// colorLight: "#ffffff",
// correctLevel: QRCode.CorrectLevel.M,
// });
// }
// container.on('click', function() {
// window.open(data.qrString, '_blank');
// });
// });
},
})
// Attach click event listener to the delete buttons
$('#itemDatatable tbody').on('click', '.delete-btn', function () {
const itemId = $(this).data('id');
self.deleteItem(itemId);
});
// $('#itemDatatable tbody').on('click', '.print-btn', function () {
// const $button = $(this); The clicked button
// const $row = $button.closest('tr'); The parent row of the button
// const itemId = $button.data('id'); Get the item ID from the button's data attribute
// let imageSrc;
// Check if the table is collapsed
// if ($row.hasClass('child')) {
// For collapsed view: Look for the closest `.dtr-data` that contains the img
// imageSrc = $row.prev('tr').find('td:nth-child(1) img').attr('src');
// } else {
// For expanded view: Find the img in the first column of the current row
// imageSrc = $row.find('td:nth-child(1) img').attr('src');
// }
// if (imageSrc) {
// self.printItem(itemId, imageSrc); Call the print function with the itemId and imageSrc
// } else {
// console.error("Image source not found.");
// }
// });
this.loading = false;
},
async fetchItem() {
this.loading = true;
await this.fetchUser();
try {
// const token = localStorage.getItem('token'); // Get the token from localStorage
const response = await fetch('/InvMainAPI/ItemMovementList', {
method: 'POST', // Specify the HTTP method
headers: {
'Content-Type': 'application/json', // Set content type
// 'Authorization': `Bearer ${token}` // Include the token in the headers
}
});
if (!response.ok) {
throw new Error('Failed to fetch item');
}
if(this.currentRole == "Super Admin"){
this.items = await response.json();
console.log(this.items);
this.initAllTables();
} else {
const data = await response.json();
this.items = data.filter(item =>
item.toUser === this.currentUser.id ||
item.lastUser === this.currentUser.id ||
item.toStore === this.currentUser.store ||
item.lastStore === this.currentUser.store
);
this.initAllTables();
// console.log(this.items);
}
// const allowedKeys = ["toUser", "lastUser"];
// this.items = (await response.json()).filter(item => allowedKeys.some(key => item[key] === this.currentUser.id));
// this.items = (await response.json()).filter(item => item.lastUser === this.currentUser);
// const allowedKeys = ["toUser", "lastUser"];
// this.items = (await response.json()).filter(item =>
// allowedKeys.some(key => item[key] === this.currentuser.id)
// );
if (this.itemDatatable) {
this.itemDatatable.clear().destroy();
}
this.initiateTable();
}
catch (error) {
console.error('Error fetching item:', error);
}
this.loading = false;
},
// FRONT END FUNCTIONS
//----------------------//
//Calculate Total Price
convertCurrency() {
const total = this.DefaultPrice * this.currencyRate;
this.convertPrice = total.toFixed(2);
this.DefaultPrice = this.DefaultPrice
// .replace(/[^0-9.]/g, '') // Remove non-numeric characters except decimal points
// .replace(/(\..*)\..*/g, '$1') // Allow only one decimal point
// .replace(/^(\d*\.\d{0,2})\d*$/, '$1'); // Limit to two decimal places
},
calculateWarrantyEndDate() {
// Check if DODate and warranty are valid
if (!this.DODate || isNaN(Date.parse(this.DODate))) {
this.EndWDate = null;
return;
}
const DODates = new Date(this.DODate);
const warrantyMonth = parseInt(this.warranty);
// Ensure warranty is a valid number
if (!isNaN(warrantyMonth)) {
DODates.setMonth(DODates.getMonth() + warrantyMonth);
this.EndWDate = DODates.toISOString().split('T')[0];
} else {
this.EndWDate = null;
}
},
async deleteItem(itemId) {
if (!confirm("Are you sure you want to delete this item?")) {
return;
}
try {
const response = await fetch(`/InvMainAPI/DeleteItem/${itemId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
});
const result = await response.json();
if (result.success) {
alert(result.message);
// Remove the row from DataTables
this.itemDatatable
.row($(`.delete-btn[data-id="${itemId}"]`).closest('tr'))
.remove()
.draw();
} else {
alert(result.message);
}
}
catch (error) {
console.error("Error deleting item:", error);
alert("An error occurred while deleting the item.");
}
finally {
this.loading = false;
}
},
async printItem(itemId, imgSrc) {
try {
this.thisQRInfo.uniqueID = itemId;
const uniqueQR = itemId;
const container = document.getElementById("QrContainer");
if (!container) {
console.error("Container not found.");
return;
}
// Safely set image content
const sanitizedImgSrc = encodeURI(imgSrc); // Sanitize the URL
container.innerHTML = `<img src="${sanitizedImgSrc}" alt="QR Code" class="text-center " />`;
// Fetch QR information
const qrInfo = this.getPrintedQR(uniqueQR);
if (!qrInfo) {
console.error("QR Info not found.");
return;
}
this.thisQRInfo = qrInfo;
this.thisQRInfo.imgSrc = sanitizedImgSrc
this.thisQRInfo.imgContainer = container.innerHTML
$(`#QrItemModal`).modal('show'); // Show modal
}
catch (error) {
console.error("Error generating QR code:", error);
alert("An error occurred while generating the QR code.");
}
},
async fetchUser() {
try {
const response = await fetch(`/IdentityAPI/GetUserInformation/`, {
method: 'POST',
});
if (response.ok) {
const data = await response.json();
this.currentUser = data?.userInfo || null;
const companyDeptData = await this.currentUser.department;
this.currentUserCompanyDept = companyDeptData;
this.selectedCompany = companyDeptData?.companyId || "";
this.selectedDepartment = companyDeptData?.departmentId || "";
const roles = this.currentUser.role;
if (roles.includes("SuperAdmin")) {
this.currentRole = "Super Admin";
// this.sortBy = 'logs';
} else if (roles.includes("Inventory Master")) {
this.currentRole = "Inventory Master";
// this.sortBy = "item";
}
}
else {
console.error(`Failed to fetch user: ${response.statusText}`);
}
}
catch (error) {
console.error('There was a problem with the fetch operation:', error);
}
},
// getPrintedQR(uniqueID) {
// if (!this.items || !Array.isArray(this.items)) {
// console.error("Items list is not available or is not an array.");
// return null;
// }
// return this.items.find(item => item.uniqueID === uniqueID);
// },
// printQRInfo() {
// Create a virtual DOM element
// const virtualElement = document.createElement('div');
// virtualElement.style.width = '330px '; Match label size for 2 inches at 203 DPI
// virtualElement.style.height = '160px';
// virtualElement.style.position = 'absolute';
// virtualElement.style.left = '-9999px'; Position offscreen to avoid rendering on the main UI
// virtualElement.style.border = '1px solid #000'; Optional: Add a border for debugging dimensions
// Populate the virtual DOM with content
// virtualElement.innerHTML = `
// <div class="container-fluid my-3 QrPrintFont" style="font-family: 'OCR A', monospace;">
// <div class="row" >
// <div class="col-5 text-center d-flex align-items-center justify-content-center">
// <div class="row">
// <div class="col-12">
// <div>${this.thisQRInfo.imgContainer}</div>
// <div class="col-12 h4"style="font-family: 'Arial', monospace;"><b>${this.thisQRInfo.uniqueID}</b></div>
// </div>
// </div>
// </div>
// <div class="col-7 d-flex align-items-center justify-content-left">
// <div class="row-fluid">
// <div class="col-12 h3"style="font-family: 'Verdana', monospace;"><b>${this.thisQRInfo.departmentName}</b></div>
// <div class="col-12 h4"style="font-family: 'Arial', monospace;"><b>${this.thisQRInfo.productShortName}</b></div>
// <div class="col-12 h4"style="font-family: 'Arial', monospace;"><b>${this.thisQRInfo.serialNumber??"-"}</b></div>
// <div class="col-12 h4"style="font-family: 'Arial', monospace;"><b>${this.thisQRInfo.partNumber}</b></div>
// </div>
// </div>
// </div>
// </div>
// `;
// Append the virtual DOM to the body (temporarily)
// document.body.appendChild(virtualElement);
// Wait for the font to be loaded (important for custom fonts like OCR-A)
// document.fonts.load('1em "OCR A"').then(() => {
// Use html2canvas to convert the virtual DOM to an image
// html2canvas(virtualElement, {
// scale: 1, Increase scale for sharper images
// }).then((canvas) => {
// Convert the canvas to an image
// const imgData = canvas.toDataURL('image/png');
// Open the image in a new tab for preview (optional)
// const newWindow = window.open();
// newWindow.location.href = imgData;
// console.log(imgData)
// Use printJS to print the image
// printJS({
// printable: imgData,
// type: 'image',
// css: '/../lib/bootstrap/dist/css/bootstrap.css',
// style: `
// @@media print {
// @@page {
// margin: 5px 5px 0px 5px;
// }
// body { margin: 0; }
// }
// `
// });
// Remove the virtual DOM from the body after use
// document.body.removeChild(virtualElement);
// }).catch((error) => {
// console.error("Error generating image:", error);
// Remove the virtual DOM if an error occurs
// document.body.removeChild(virtualElement);
// });
// }).catch((error) => {
// console.error("Error loading font:", error);
// Remove the virtual DOM if font loading fails
// document.body.removeChild(virtualElement);
// });
// },
handleSorting() {
this.renderTables();
},
renderTables() {
// if (this.sortBy === "logs") {
// // this.initAllTables();
// this.initiateTable();
// } else
// if (this.sortBy === "all") {
// this.initAllTables();
// // this.initiateTable();
// }
this.initAllTables();
},
initAllTables() {
if (this.itemMovementNotCompleteDatatable) {
this.itemMovementNotCompleteDatatable.destroy();
}
if (this.itemMovementCompleteDatatable) {
this.itemMovementCompleteDatatable.destroy();
}
this.itemMovementNotCompleteDatatable = $("#itemMovementNotCompleteDatatable").DataTable({
data: this.items.filter((m) => m.movementComplete == 0),
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: "Start Status", data: "toOther" },
{ title: "Product Category", data: "productCategory" },
{ title: "Qty", data: "quantity" },
// { title: "Send Date", data: "sendDate" },
{
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" },
],
order: [[0, "desc"]],
responsive: true,
});
this.itemMovementCompleteDatatable = $("#itemMovementCompleteDatatable").DataTable({
data: this.items.filter((m) => m.movementComplete == 1),
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: "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: "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" },
],
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];
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];
},
},
});
</script>
}