bd77e1a4e7
NPM Installation / build (16.x, ubuntu-latest) (push) Has been cancelled
NPM Installation / build (16.x, windows-latest) (push) Has been cancelled
NPM Installation / build (17.x, ubuntu-latest) (push) Has been cancelled
NPM Installation / build (17.x, windows-latest) (push) Has been cancelled
NPM Installation / build (18.x, ubuntu-latest) (push) Has been cancelled
NPM Installation / build (18.x, windows-latest) (push) Has been cancelled
432 lines
12 KiB
Vue
432 lines
12 KiB
Vue
<script setup>
|
|
import { ref, reactive, computed, watch, onMounted } from 'vue'
|
|
import Swal from 'sweetalert2'
|
|
import { useMaterialStore } from '@/stores/materialStore'
|
|
import { materialService } from '@/services/materialService'
|
|
|
|
import MaterialTable from '@/components/materials/MaterialTable.vue'
|
|
import MaterialHistory from '@/components/materials/MaterialHistory.vue'
|
|
import AssignMaterialModal from '@/components/materials/AssignMaterialModal.vue'
|
|
import TransferMaterialModal from '@/components/materials/TransferMaterialModal.vue'
|
|
import ReturnMaterialModal from '@/components/materials/ReturnMaterialModal.vue'
|
|
import MaterialDetailModal from '@/components/materials/MaterialDetailModal.vue'
|
|
|
|
const store = useMaterialStore()
|
|
|
|
const currentTab = ref('user')
|
|
const searchBarcode = ref('')
|
|
const searchMaterial = ref('')
|
|
const selectedUser = ref(null)
|
|
const selectedBarcode = ref('')
|
|
|
|
const currentPage = ref(1)
|
|
const perPage = ref(15)
|
|
|
|
const filterType = ref('')
|
|
|
|
const assignModal = ref(false)
|
|
const transferModal = ref(false)
|
|
const returnModal = ref(false)
|
|
const detailModal = ref(false)
|
|
|
|
const saving = ref(false)
|
|
|
|
const assignForm = reactive({
|
|
user_id: null,
|
|
barcode_id: '',
|
|
material_name: '',
|
|
type: 'serialized',
|
|
serial_number: '',
|
|
qty: 1,
|
|
unit: '',
|
|
})
|
|
|
|
const transferForm = reactive({
|
|
barcode_id: '',
|
|
from_user_id: null,
|
|
to_user_id: null,
|
|
})
|
|
|
|
const returnForm = reactive({
|
|
barcode_id: '',
|
|
user_id: null,
|
|
})
|
|
|
|
const detailItem = ref({})
|
|
|
|
const filteredMaterials = computed(() => {
|
|
return store.materials.filter((item) => {
|
|
const barcodeMatch =
|
|
!searchBarcode.value ||
|
|
item.barcode_id?.toLowerCase().includes(searchBarcode.value.toLowerCase())
|
|
|
|
const materialMatch =
|
|
!searchMaterial.value ||
|
|
item.material_name?.toLowerCase().includes(searchMaterial.value.toLowerCase())
|
|
|
|
const userMatch = !selectedUser.value || item.user_id == selectedUser.value
|
|
|
|
const typeMatch = !filterType.value || item.type === filterType.value
|
|
|
|
return barcodeMatch && materialMatch && userMatch && typeMatch
|
|
})
|
|
})
|
|
|
|
const isFilteringActive = computed(() => {
|
|
return (
|
|
searchBarcode.value !== '' ||
|
|
searchMaterial.value !== '' ||
|
|
selectedUser.value !== null ||
|
|
filterType.value !== ''
|
|
)
|
|
})
|
|
|
|
const currentMaterialsSource = computed(() => {
|
|
return isFilteringActive.value ? filteredMaterials.value : store.materials
|
|
})
|
|
|
|
const totalPages = computed(() => Math.ceil(currentMaterialsSource.value.length / perPage.value))
|
|
|
|
const paginatedMaterials = computed(() => {
|
|
const start = (currentPage.value - 1) * perPage.value
|
|
return currentMaterialsSource.value.slice(start, start + perPage.value)
|
|
})
|
|
|
|
const assignedMaterial = computed(
|
|
() => store.materials.filter((i) => i.status === 'Assigned').length,
|
|
)
|
|
const returnedMaterial = computed(
|
|
() => store.materials.filter((i) => i.status === 'Returned').length,
|
|
)
|
|
const serializedMaterial = computed(
|
|
() => store.materials.filter((i) => i.type === 'serialized').length,
|
|
)
|
|
const consumableMaterial = computed(
|
|
() => store.materials.filter((i) => i.type === 'consumable').length,
|
|
)
|
|
|
|
const totalMaterial = computed(() => store.materials.length)
|
|
|
|
watch(searchBarcode, () => {
|
|
currentPage.value = 1
|
|
})
|
|
watch(searchMaterial, () => {
|
|
currentPage.value = 1
|
|
})
|
|
watch(filterType, () => {
|
|
currentPage.value = 1
|
|
})
|
|
|
|
watch([selectedUser], async ([newSelectedUser]) => {
|
|
currentPage.value = 1
|
|
if (newSelectedUser) {
|
|
await store.fetchByUser(newSelectedUser)
|
|
} else {
|
|
await store.fetchAll()
|
|
}
|
|
})
|
|
|
|
watch(selectedBarcode, async (val) => {
|
|
if (val) await store.fetchHistory(val)
|
|
})
|
|
|
|
onMounted(() => {
|
|
store.fetchAll()
|
|
})
|
|
|
|
function openTransferModal(item) {
|
|
transferForm.barcode_id = item.barcode_id
|
|
transferForm.from_user_id = item.user_id
|
|
transferForm.to_user_id = null
|
|
transferModal.value = true
|
|
}
|
|
|
|
function openReturnModal(item) {
|
|
returnForm.barcode_id = item.barcode_id
|
|
returnForm.user_id = item.user_id
|
|
returnModal.value = true
|
|
}
|
|
|
|
function openDetail(item) {
|
|
detailItem.value = { ...item }
|
|
detailModal.value = true
|
|
}
|
|
|
|
async function assignMaterial() {
|
|
saving.value = true
|
|
try {
|
|
await materialService.assign({
|
|
...assignForm,
|
|
qty: Number(assignForm.qty),
|
|
})
|
|
assignModal.value = false
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: 'Berhasil',
|
|
text: 'Material berhasil di-assign',
|
|
timer: 1500,
|
|
showConfirmButton: false,
|
|
})
|
|
const userId = selectedUser.value || assignForm.user_id
|
|
if (userId) {
|
|
selectedUser.value = userId
|
|
await store.fetchByUser(userId)
|
|
} else {
|
|
await store.fetchAll()
|
|
}
|
|
assignForm.barcode_id = ''
|
|
assignForm.user_id = null
|
|
assignForm.material_name = ''
|
|
assignForm.type = 'serialized'
|
|
assignForm.serial_number = ''
|
|
assignForm.qty = 1
|
|
assignForm.unit = ''
|
|
} catch (err) {
|
|
Swal.fire({ icon: 'error', title: 'Gagal', text: err.response?.data?.message || err.message })
|
|
} finally {
|
|
saving.value = false
|
|
}
|
|
}
|
|
|
|
async function transferMaterial() {
|
|
const confirm = await Swal.fire({
|
|
title: 'Transfer Material?',
|
|
text: 'Pastikan user tujuan sudah benar.',
|
|
icon: 'question',
|
|
showCancelButton: true,
|
|
confirmButtonText: 'Transfer',
|
|
cancelButtonText: 'Batal',
|
|
})
|
|
if (!confirm.isConfirmed) return
|
|
|
|
saving.value = true
|
|
try {
|
|
await materialService.transfer({ ...transferForm })
|
|
transferModal.value = false
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: 'Berhasil',
|
|
text: 'Material berhasil dipindahkan',
|
|
timer: 1500,
|
|
showConfirmButton: false,
|
|
})
|
|
if (selectedUser.value) {
|
|
await store.fetchByUser(selectedUser.value)
|
|
} else {
|
|
await store.fetchAll()
|
|
}
|
|
if (selectedBarcode.value) await store.fetchHistory(selectedBarcode.value)
|
|
} catch (err) {
|
|
Swal.fire({ icon: 'error', title: 'Gagal', text: err.response?.data?.message || err.message })
|
|
} finally {
|
|
saving.value = false
|
|
}
|
|
}
|
|
|
|
async function returnMaterial() {
|
|
const confirm = await Swal.fire({
|
|
title: 'Return Material?',
|
|
text: 'Material akan dikembalikan.',
|
|
icon: 'warning',
|
|
showCancelButton: true,
|
|
confirmButtonText: 'Ya',
|
|
})
|
|
if (!confirm.isConfirmed) return
|
|
|
|
saving.value = true
|
|
try {
|
|
await materialService.returnMaterial({ ...returnForm })
|
|
returnModal.value = false
|
|
Swal.fire({
|
|
icon: 'success',
|
|
title: 'Berhasil',
|
|
text: 'Material berhasil dikembalikan',
|
|
timer: 1500,
|
|
showConfirmButton: false,
|
|
})
|
|
if (selectedUser.value) {
|
|
await store.fetchByUser(selectedUser.value)
|
|
} else {
|
|
await store.fetchAll()
|
|
}
|
|
if (selectedBarcode.value) await store.fetchHistory(selectedBarcode.value)
|
|
} catch (err) {
|
|
Swal.fire({ icon: 'error', title: 'Gagal', text: err.response?.data?.message || err.message })
|
|
} finally {
|
|
saving.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<CRow>
|
|
<CCol :xs="12">
|
|
<CCard class="mb-4">
|
|
<CCardHeader class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<strong>Material Management</strong>
|
|
<div class="small text-body-secondary">
|
|
Assign, Transfer, Return dan History Material
|
|
</div>
|
|
</div>
|
|
<CButton color="primary" @click="assignModal = true">+ Tambah Material</CButton>
|
|
</CCardHeader>
|
|
|
|
<CCardBody>
|
|
<CRow class="mb-4">
|
|
<CCol md
|
|
><CCard class="border-start border-primary border-4"
|
|
><CCardBody
|
|
><div>Total Material</div>
|
|
<h3>{{ totalMaterial }}</h3></CCardBody
|
|
></CCard
|
|
></CCol
|
|
>
|
|
<CCol md
|
|
><CCard class="border-start border-success border-4"
|
|
><CCardBody
|
|
><div>Assigned</div>
|
|
<h3>{{ assignedMaterial }}</h3></CCardBody
|
|
></CCard
|
|
></CCol
|
|
>
|
|
<CCol md
|
|
><CCard class="border-start border-secondary border-4"
|
|
><CCardBody
|
|
><div>Returned</div>
|
|
<h3>{{ returnedMaterial }}</h3></CCardBody
|
|
></CCard
|
|
></CCol
|
|
>
|
|
<CCol md
|
|
><CCard class="border-start border-info border-4"
|
|
><CCardBody
|
|
><div>Serialized</div>
|
|
<h3>{{ serializedMaterial }}</h3></CCardBody
|
|
></CCard
|
|
></CCol
|
|
>
|
|
<CCol md
|
|
><CCard class="border-start border-warning border-4"
|
|
><CCardBody
|
|
><div>Consumable</div>
|
|
<h3>{{ consumableMaterial }}</h3></CCardBody
|
|
></CCard
|
|
></CCol
|
|
>
|
|
</CRow>
|
|
|
|
<CTabs :active-item-key="currentTab" @change="(key) => (currentTab = key)">
|
|
<CTabList variant="tabs">
|
|
<CTab item-key="user">Material User</CTab>
|
|
<CTab item-key="history">History</CTab>
|
|
</CTabList>
|
|
|
|
<CTabPanel item-key="user" class="mt-4">
|
|
<CRow class="mb-3">
|
|
<CCol md="3"
|
|
><CFormLabel>Barcode</CFormLabel><CFormInput v-model="searchBarcode"
|
|
/></CCol>
|
|
<CCol md="3"
|
|
><CFormLabel>Material</CFormLabel><CFormInput v-model="searchMaterial"
|
|
/></CCol>
|
|
<CCol md="2">
|
|
<CFormLabel>Type</CFormLabel>
|
|
<CFormSelect v-model="filterType">
|
|
<option value="">Semua Type</option>
|
|
<option value="serialized">Serialized</option>
|
|
<option value="consumable">Consumable</option>
|
|
</CFormSelect>
|
|
</CCol>
|
|
<CCol md="2">
|
|
<CFormLabel>User ID</CFormLabel>
|
|
<CFormInput v-model="selectedUser" placeholder="Filter by User ID" />
|
|
</CCol>
|
|
<CCol md="2" class="d-flex align-items-end">
|
|
<CButton
|
|
color="secondary"
|
|
@click="
|
|
((searchBarcode = ''),
|
|
(searchMaterial = ''),
|
|
(selectedUser = null),
|
|
(filterType = ''))
|
|
"
|
|
>Reset</CButton
|
|
>
|
|
</CCol>
|
|
</CRow>
|
|
|
|
<MaterialTable
|
|
:data="paginatedMaterials"
|
|
:totalPages="totalPages"
|
|
:currentPage="currentPage"
|
|
:perPage="perPage"
|
|
:totalItems="currentMaterialsSource.length"
|
|
:loading="store.loading"
|
|
@update:perPage="
|
|
(val) => {
|
|
perPage = val
|
|
currentPage = 1
|
|
}
|
|
"
|
|
@page="(p) => (currentPage = p)"
|
|
@detail="openDetail"
|
|
@transfer="openTransferModal"
|
|
@return="openReturnModal"
|
|
/>
|
|
</CTabPanel>
|
|
|
|
<CTabPanel item-key="history" class="mt-4">
|
|
<CRow class="mb-3">
|
|
<CCol md="4">
|
|
<CFormLabel>Barcode</CFormLabel>
|
|
<CFormInput v-model="selectedBarcode" placeholder="Masukkan Barcode" />
|
|
</CCol>
|
|
<CCol md="2" class="d-flex align-items-end">
|
|
<CButton color="primary" @click="store.fetchHistory(selectedBarcode)"
|
|
>Lihat History</CButton
|
|
>
|
|
</CCol>
|
|
</CRow>
|
|
|
|
<MaterialHistory :histories="store.histories" />
|
|
</CTabPanel>
|
|
</CTabs>
|
|
</CCardBody>
|
|
</CCard>
|
|
</CCol>
|
|
</CRow>
|
|
|
|
<AssignMaterialModal
|
|
:visible="assignModal"
|
|
:form="assignForm"
|
|
:saving="saving"
|
|
@close="assignModal = false"
|
|
@save="assignMaterial"
|
|
/>
|
|
|
|
<TransferMaterialModal
|
|
:visible="transferModal"
|
|
:form="transferForm"
|
|
:saving="saving"
|
|
@close="transferModal = false"
|
|
@save="transferMaterial"
|
|
/>
|
|
|
|
<ReturnMaterialModal
|
|
:visible="returnModal"
|
|
:form="returnForm"
|
|
:saving="saving"
|
|
@close="returnModal = false"
|
|
@save="returnMaterial"
|
|
/>
|
|
|
|
<MaterialDetailModal
|
|
:visible="detailModal"
|
|
:item="detailItem"
|
|
@close="detailModal = false"
|
|
@transfer="openTransferModal"
|
|
@return="openReturnModal"
|
|
/>
|
|
</template>
|