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

View File

@ -15,16 +15,41 @@
.table td img { .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>
@ -36,6 +61,13 @@
</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">
<div class="card-header"> <div class="card-header">
@ -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>
@ -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,30 +612,25 @@
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: {
@ -443,6 +646,73 @@
return acc; 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] = {};
}
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();
} else
if (this.sortBy === "all") {
this.initAllTables();
// this.initiateTable(); // this.initiateTable();
} // } else
// if (this.sortBy === "all") {
// this.initAllTables();
// // 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];

View File

@ -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);
} }

View File

@ -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);