Ticket material
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
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
This commit is contained in:
@@ -142,14 +142,11 @@ async function saveTicket() {
|
||||
}
|
||||
try {
|
||||
if (isEdit.value && editingIndex.value >= 0) {
|
||||
const res = await fetch(
|
||||
`https://api.radiq.my.id/api/tickets/${form.ticket_no}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: { Authorization: token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
},
|
||||
)
|
||||
const res = await fetch(`https://api.radiq.my.id/api/tickets/${form.ticket_no}`, {
|
||||
method: 'PUT',
|
||||
headers: { Authorization: token, 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body),
|
||||
})
|
||||
if (res.ok) store.updateTicket(editingIndex.value, form)
|
||||
} else {
|
||||
const res = await fetch('https://api.radiq.my.id/api/tickets', {
|
||||
@@ -227,7 +224,7 @@ function isPending(item) {
|
||||
<CCard class="mb-4">
|
||||
<CCardHeader>
|
||||
<strong>Ticket</strong>
|
||||
<CButton color="primary" size="sm" class="float-end" @click="openAddModal">
|
||||
<CButton color="primary" class="float-end" @click="openAddModal">
|
||||
+ Tambah Ticket
|
||||
</CButton>
|
||||
</CCardHeader>
|
||||
|
||||
@@ -1,32 +1,21 @@
|
||||
<script setup>
|
||||
import axios from 'axios'
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useTicketIncidentStore } from '@/stores/ticketIncident.js'
|
||||
import { formatTanggal } from '@/utils/tglindo.js'
|
||||
import { showConfirm, showSuccess, showSuccessr, showError } from '@/utils/swal.js'
|
||||
import { CButton } from '@coreui/vue'
|
||||
import api from '@/api/axios'
|
||||
|
||||
const store = useTicketIncidentStore()
|
||||
const loading = ref(false)
|
||||
|
||||
const BASE_URL = 'https://api.radiq.my.id/api/ticket-incident-types'
|
||||
const token = 'Bearer uZ1vM4UON3CsDV9niGD1gLS4sHpCxT9nzadkITmc6caf2ea2'
|
||||
|
||||
// Axios instance with default headers
|
||||
const api = axios.create({
|
||||
baseURL: BASE_URL,
|
||||
headers: {
|
||||
Authorization: token,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
async function fetchTicketIncidentTypes() {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await api.get('')
|
||||
const response = await api.get('/ticket-incident-types')
|
||||
store.setItems(response.data.data || [])
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch:', err)
|
||||
errorMsg.value = 'Gagal memuat data: ' + err.message
|
||||
showError('Gagal memuat data: ' + err.message)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -35,15 +24,11 @@ async function fetchTicketIncidentTypes() {
|
||||
onMounted(fetchTicketIncidentTypes)
|
||||
|
||||
const modal = ref(false)
|
||||
const deleteModal = ref(false)
|
||||
const detailModal = ref(false)
|
||||
const editingIndex = ref(-1)
|
||||
const isEdit = ref(false)
|
||||
const deleteIndex = ref(-1)
|
||||
const saving = ref(false)
|
||||
const errorMsg = ref('')
|
||||
const successMsg = ref('')
|
||||
let successTimer = null
|
||||
|
||||
const currentPage = ref(1)
|
||||
const perPage = ref(10)
|
||||
@@ -64,14 +49,6 @@ function changePerPage(val) {
|
||||
currentPage.value = 1
|
||||
}
|
||||
|
||||
function showSuccess(msg) {
|
||||
successMsg.value = msg
|
||||
if (successTimer) clearTimeout(successTimer)
|
||||
successTimer = setTimeout(() => {
|
||||
successMsg.value = ''
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
const form = reactive({
|
||||
name: '',
|
||||
is_active: '1',
|
||||
@@ -107,29 +84,33 @@ function openDetail(item) {
|
||||
getTicketIncidentType(item.id)
|
||||
}
|
||||
|
||||
function openDeleteModal(item) {
|
||||
async function openDeleteModal(item) {
|
||||
const idx = store.items.findIndex((t) => t.id === item.id)
|
||||
if (idx === -1) return
|
||||
const result = await showConfirm(
|
||||
'Konfirmasi Hapus',
|
||||
'Apakah Anda yakin ingin menghapus ticket incident ini?',
|
||||
)
|
||||
if (!result.isConfirmed) return
|
||||
deleteIndex.value = idx
|
||||
deleteModal.value = true
|
||||
await deleteTicketIncidentType(item.id)
|
||||
}
|
||||
|
||||
async function createTicketIncidentType() {
|
||||
errorMsg.value = ''
|
||||
saving.value = true
|
||||
const body = {
|
||||
name: form.name,
|
||||
is_active: form.is_active === '1',
|
||||
}
|
||||
try {
|
||||
const response = await api.post('', body)
|
||||
const response = await api.post('/ticket-incident-types', body)
|
||||
store.addItem(response.data.data)
|
||||
modal.value = false
|
||||
currentPage.value = totalPages.value
|
||||
showSuccess('Data berhasil disimpan')
|
||||
fetchTicketIncidentTypes() // Refresh data after creation
|
||||
fetchTicketIncidentTypes()
|
||||
} catch (err) {
|
||||
errorMsg.value = err.response?.data?.message || 'Gagal menyimpan data: ' + err.message
|
||||
showError(err.response?.data?.message || 'Gagal menyimpan data: ' + err.message)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
@@ -137,30 +118,28 @@ async function createTicketIncidentType() {
|
||||
|
||||
async function getTicketIncidentType(id) {
|
||||
try {
|
||||
const response = await api.get(`/${id}`)
|
||||
const response = await api.get(`/ticket-incident-types/${id}`)
|
||||
detailItem.value = response.data.data
|
||||
detailModal.value = true
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch detail:', err)
|
||||
errorMsg.value = 'Gagal memuat detail data: ' + err.message
|
||||
showError('Gagal memuat detail data: ' + err.message)
|
||||
}
|
||||
}
|
||||
|
||||
async function updateTicketIncidentType(id) {
|
||||
errorMsg.value = ''
|
||||
saving.value = true
|
||||
const body = {
|
||||
name: form.name,
|
||||
is_active: form.is_active === '1',
|
||||
}
|
||||
try {
|
||||
const response = await api.put(`/${id}`, body)
|
||||
const response = await api.put(`/ticket-incident-types/${id}`, body)
|
||||
store.updateItem(editingIndex.value, response.data.data)
|
||||
modal.value = false
|
||||
showSuccess('Data berhasil diupdate')
|
||||
fetchTicketIncidentTypes() // Refresh data after update
|
||||
showSuccessr('Data Berhasil Di Simpan')
|
||||
fetchTicketIncidentTypes()
|
||||
} catch (err) {
|
||||
errorMsg.value = err.response?.data?.message || 'Gagal mengupdate data: ' + err.message
|
||||
showError(err.response?.data?.message || 'Gagal mengupdate data: ' + err.message)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
@@ -168,17 +147,15 @@ async function updateTicketIncidentType(id) {
|
||||
|
||||
async function deleteTicketIncidentType(id) {
|
||||
try {
|
||||
await api.delete(`/${id}`)
|
||||
await api.delete(`/ticket-incident-types/${id}`)
|
||||
store.deleteItem(deleteIndex.value)
|
||||
if (paginatedItems.length === 0 && currentPage.value > 1) currentPage.value--
|
||||
showSuccess('Data berhasil dihapus')
|
||||
fetchTicketIncidentTypes() // Refresh data after deletion
|
||||
fetchTicketIncidentTypes()
|
||||
} catch (err) {
|
||||
console.error('Delete failed:', err)
|
||||
errorMsg.value = 'Gagal menghapus data: ' + err.message
|
||||
showError('Gagal menghapus data: ' + err.message)
|
||||
} finally {
|
||||
deleteIndex.value = -1
|
||||
deleteModal.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,12 +167,6 @@ async function saveItem() {
|
||||
await createTicketIncidentType()
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmDelete() {
|
||||
if (deleteIndex.value < 0) return
|
||||
const item = store.items[deleteIndex.value]
|
||||
await deleteTicketIncidentType(item.id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -204,15 +175,12 @@ async function confirmDelete() {
|
||||
<CCard class="mb-4">
|
||||
<CCardHeader>
|
||||
<strong>Ticket Incident</strong>
|
||||
<CButton color="warning" size="sm" class="float-end" @click="openAddModal">
|
||||
<CButton color="primary" class="float-end" @click="openAddModal">
|
||||
+ Tambah Ticket Incident
|
||||
</CButton>
|
||||
</CCardHeader>
|
||||
<CCardBody>
|
||||
<CAlert color="success" v-if="successMsg" dismissible @close="successMsg = ''">
|
||||
{{ successMsg }}
|
||||
</CAlert>
|
||||
<div class="table-responsive">
|
||||
<div class="">
|
||||
<CTable striped hover small>
|
||||
<CTableHead>
|
||||
<CTableRow>
|
||||
@@ -265,19 +233,20 @@ async function confirmDelete() {
|
||||
@change="changePerPage(Number($event.target.value))"
|
||||
>
|
||||
<option :value="10">10</option>
|
||||
<option :value="15">15</option>
|
||||
<option :value="25">25</option>
|
||||
<option :value="50">50</option>
|
||||
<option :value="100">100</option>
|
||||
</CFormSelect>
|
||||
<span>dari {{ store.items.length }} data</span>
|
||||
</div>
|
||||
<CPagination v-if="totalPages > 1" size="sm" aria-label="pagination">
|
||||
<CPagination v-if="totalPages > 1" aria-label="pagination">
|
||||
<CPaginationItem
|
||||
aria-label="Previous"
|
||||
:disabled="currentPage === 1"
|
||||
@click="goToPage(currentPage - 1)"
|
||||
>
|
||||
<span aria-hidden="true">«</span>
|
||||
Previous
|
||||
</CPaginationItem>
|
||||
<CPaginationItem
|
||||
v-for="page in totalPages"
|
||||
@@ -292,7 +261,7 @@ async function confirmDelete() {
|
||||
:disabled="currentPage === totalPages"
|
||||
@click="goToPage(currentPage + 1)"
|
||||
>
|
||||
<span aria-hidden="true">»</span>
|
||||
Next
|
||||
</CPaginationItem>
|
||||
</CPagination>
|
||||
</div>
|
||||
@@ -308,9 +277,6 @@ async function confirmDelete() {
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
<CForm>
|
||||
<CAlert color="danger" v-if="errorMsg" dismissible @close="errorMsg = ''">
|
||||
{{ errorMsg }}
|
||||
</CAlert>
|
||||
<div class="mb-3">
|
||||
<CFormLabel>Name</CFormLabel>
|
||||
<CFormInput v-model="form.name" placeholder="Contoh: Kabel putus" />
|
||||
@@ -333,20 +299,6 @@ async function confirmDelete() {
|
||||
</CModalFooter>
|
||||
</CModal>
|
||||
|
||||
<!-- Delete Confirmation Modal -->
|
||||
<CModal :visible="deleteModal" @close="deleteModal = false" alignment="center">
|
||||
<CModalHeader dismiss @close="deleteModal = false">
|
||||
<CModalTitle>Konfirmasi Hapus</CModalTitle>
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
<p class="mb-0">Apakah Anda yakin ingin menghapus ticket incident ini?</p>
|
||||
</CModalBody>
|
||||
<CModalFooter>
|
||||
<CButton color="secondary" @click="deleteModal = false">Batal</CButton>
|
||||
<CButton color="danger" @click="confirmDelete">Hapus</CButton>
|
||||
</CModalFooter>
|
||||
</CModal>
|
||||
|
||||
<!-- Detail Modal -->
|
||||
<CModal :visible="detailModal" @close="detailModal = false" alignment="center">
|
||||
<CModalHeader dismiss @close="detailModal = false">
|
||||
|
||||
@@ -1,57 +1,431 @@
|
||||
<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> <strong>Ticket Materials</strong> </CCardHeader>
|
||||
<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>
|
||||
<CTable striped hover>
|
||||
<CTableHead>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell scope="col">#</CTableHeaderCell>
|
||||
<CTableHeaderCell scope="col">Material Name</CTableHeaderCell>
|
||||
<CTableHeaderCell scope="col">Ticket</CTableHeaderCell>
|
||||
<CTableHeaderCell scope="col">Quantity</CTableHeaderCell>
|
||||
<CTableHeaderCell scope="col">Unit</CTableHeaderCell>
|
||||
<CTableHeaderCell scope="col">Cost</CTableHeaderCell>
|
||||
</CTableRow>
|
||||
</CTableHead>
|
||||
<CTableBody>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell scope="row">1</CTableHeaderCell>
|
||||
<CTableDataCell>Cable CAT6</CTableDataCell>
|
||||
<CTableDataCell>Network setup</CTableDataCell>
|
||||
<CTableDataCell>100</CTableDataCell>
|
||||
<CTableDataCell>Meters</CTableDataCell>
|
||||
<CTableDataCell>$150.00</CTableDataCell>
|
||||
</CTableRow>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell scope="row">2</CTableHeaderCell>
|
||||
<CTableDataCell>RJ45 Connector</CTableDataCell>
|
||||
<CTableDataCell>Network setup</CTableDataCell>
|
||||
<CTableDataCell>50</CTableDataCell>
|
||||
<CTableDataCell>Pcs</CTableDataCell>
|
||||
<CTableDataCell>$25.00</CTableDataCell>
|
||||
</CTableRow>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell scope="row">3</CTableHeaderCell>
|
||||
<CTableDataCell>SSD 512GB</CTableDataCell>
|
||||
<CTableDataCell>Hardware upgrade</CTableDataCell>
|
||||
<CTableDataCell>2</CTableDataCell>
|
||||
<CTableDataCell>Pcs</CTableDataCell>
|
||||
<CTableDataCell>$200.00</CTableDataCell>
|
||||
</CTableRow>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell scope="row">4</CTableHeaderCell>
|
||||
<CTableDataCell>RAM 16GB</CTableDataCell>
|
||||
<CTableDataCell>Hardware upgrade</CTableDataCell>
|
||||
<CTableDataCell>2</CTableDataCell>
|
||||
<CTableDataCell>Pcs</CTableDataCell>
|
||||
<CTableDataCell>$180.00</CTableDataCell>
|
||||
</CTableRow>
|
||||
</CTableBody>
|
||||
</CTable>
|
||||
<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>
|
||||
|
||||
@@ -1,27 +1,22 @@
|
||||
<script setup>
|
||||
import axios from 'axios'
|
||||
import { ref, reactive, computed, watch, onMounted } from 'vue'
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useTicketTypeStore } from '@/stores/ticketType.js'
|
||||
import { formatTanggal } from '@/utils/tglindo.js'
|
||||
import { showConfirm, showSuccess, showSuccessr, showError } from '@/utils/swal.js'
|
||||
import { CButton } from '@coreui/vue'
|
||||
import { CBadge } from '@coreui/vue'
|
||||
import api from '@/api/axios'
|
||||
|
||||
const store = useTicketTypeStore()
|
||||
const loading = ref(false)
|
||||
|
||||
const token = 'Bearer uZ1vM4UON3CsDV9niGD1gLS4sHpCxT9nzadkITmc6caf2ea2'
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: 'https://api.radiq.my.id/api',
|
||||
headers: { Authorization: token, 'Content-Type': 'application/json' },
|
||||
})
|
||||
|
||||
async function fetchAllData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await api.get('/ticket-types?per_page=1000')
|
||||
store.setTicketTypes(res.data.data || [])
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch data:', err)
|
||||
errorMsg.value = 'Gagal memuat data: ' + err.message
|
||||
showError('Gagal memuat data: ' + err.message)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -32,15 +27,11 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
const modal = ref(false)
|
||||
const deleteModal = ref(false)
|
||||
const detailModal = ref(false)
|
||||
const editingIndex = ref(-1)
|
||||
const isEdit = ref(false)
|
||||
const deleteIndex = ref(-1)
|
||||
const saving = ref(false)
|
||||
const errorMsg = ref('')
|
||||
const successMsg = ref('')
|
||||
let successTimer = null
|
||||
|
||||
const currentPage = ref(1)
|
||||
const perPage = ref(10)
|
||||
@@ -61,14 +52,6 @@ function changePerPage(val) {
|
||||
currentPage.value = 1
|
||||
}
|
||||
|
||||
function showSuccess(msg) {
|
||||
successMsg.value = msg
|
||||
if (successTimer) clearTimeout(successTimer)
|
||||
successTimer = setTimeout(() => {
|
||||
successMsg.value = ''
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
const form = reactive({
|
||||
code: '',
|
||||
name: '',
|
||||
@@ -99,8 +82,7 @@ async function getTicketType(id) {
|
||||
detailItem.value = res.data.data
|
||||
detailModal.value = true
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch detail:', err)
|
||||
errorMsg.value = 'Gagal memuat detail: ' + err.message
|
||||
showError('Gagal memuat detail: ' + err.message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,15 +115,19 @@ function openEditModal(item) {
|
||||
modal.value = true
|
||||
}
|
||||
|
||||
function openDeleteModal(item) {
|
||||
async function openDeleteModal(item) {
|
||||
const idx = store.ticketTypes.findIndex((t) => t.code === item.code)
|
||||
if (idx === -1) return
|
||||
const result = await showConfirm(
|
||||
'Konfirmasi Hapus',
|
||||
'Apakah Anda yakin ingin menghapus ticket type ini?',
|
||||
)
|
||||
if (!result.isConfirmed) return
|
||||
deleteIndex.value = idx
|
||||
deleteModal.value = true
|
||||
await deleteTicketType(item.id)
|
||||
}
|
||||
|
||||
async function createTicketType() {
|
||||
errorMsg.value = ''
|
||||
saving.value = true
|
||||
const body = {
|
||||
code: form.code,
|
||||
@@ -161,14 +147,13 @@ async function createTicketType() {
|
||||
showSuccess('Data berhasil disimpan')
|
||||
fetchAllData()
|
||||
} catch (err) {
|
||||
errorMsg.value = err.response?.data?.message || 'Gagal menyimpan data: ' + err.message
|
||||
showError(err.response?.data?.message || 'Gagal menyimpan data: ' + err.message)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function updateTicketType(id) {
|
||||
errorMsg.value = ''
|
||||
saving.value = true
|
||||
const body = {
|
||||
code: form.code,
|
||||
@@ -184,10 +169,10 @@ async function updateTicketType(id) {
|
||||
await api.put(`/ticket-types/${id}`, body)
|
||||
store.updateTicketType(editingIndex.value, { ...body })
|
||||
modal.value = false
|
||||
showSuccess('Data berhasil diupdate')
|
||||
showSuccessr('Data berhasil diupdate')
|
||||
fetchAllData()
|
||||
} catch (err) {
|
||||
errorMsg.value = err.response?.data?.message || 'Gagal mengupdate data: ' + err.message
|
||||
showError(err.response?.data?.message || 'Gagal mengupdate data: ' + err.message)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
@@ -201,11 +186,9 @@ async function deleteTicketType(id) {
|
||||
showSuccess('Data berhasil dihapus')
|
||||
fetchAllData()
|
||||
} catch (err) {
|
||||
console.error('Delete failed:', err)
|
||||
errorMsg.value = 'Gagal menghapus data: ' + err.message
|
||||
showError('Gagal menghapus data: ' + err.message)
|
||||
} finally {
|
||||
deleteIndex.value = -1
|
||||
deleteModal.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,12 +200,6 @@ async function saveItem() {
|
||||
await createTicketType()
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmDelete() {
|
||||
if (deleteIndex.value < 0) return
|
||||
const item = store.ticketTypes[deleteIndex.value]
|
||||
await deleteTicketType(item.id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -231,15 +208,12 @@ async function confirmDelete() {
|
||||
<CCard class="mb-4">
|
||||
<CCardHeader>
|
||||
<strong>Ticket Type</strong>
|
||||
<CButton color="warning" size="sm" class="float-end" @click="openAddModal">
|
||||
<CButton color="primary" class="float-end" @click="openAddModal">
|
||||
+ Tambah Ticket Type
|
||||
</CButton>
|
||||
</CCardHeader>
|
||||
<CCardBody>
|
||||
<CAlert color="success" v-if="successMsg" dismissible @close="successMsg = ''">
|
||||
{{ successMsg }}
|
||||
</CAlert>
|
||||
<div class="table-responsive">
|
||||
<div class="">
|
||||
<CTable striped hover small>
|
||||
<CTableHead>
|
||||
<CTableRow>
|
||||
@@ -256,7 +230,7 @@ async function confirmDelete() {
|
||||
(currentPage - 1) * perPage + index + 1
|
||||
}}</CTableHeaderCell>
|
||||
<CTableDataCell
|
||||
><code>{{ item.code }}</code></CTableDataCell
|
||||
><CBadge color="primary">{{ item.code }}</CBadge></CTableDataCell
|
||||
>
|
||||
<CTableDataCell>{{ item.name }}</CTableDataCell>
|
||||
<CTableDataCell class="text-center">{{ item.sla_minutes }}</CTableDataCell>
|
||||
@@ -291,13 +265,13 @@ async function confirmDelete() {
|
||||
</CFormSelect>
|
||||
<span>dari {{ store.ticketTypes.length }} data</span>
|
||||
</div>
|
||||
<CPagination v-if="totalPages > 1" size="sm" aria-label="pagination">
|
||||
<CPagination v-if="totalPages > 1" aria-label="pagination">
|
||||
<CPaginationItem
|
||||
aria-label="Previous"
|
||||
:disabled="currentPage === 1"
|
||||
@click="goToPage(currentPage - 1)"
|
||||
>
|
||||
<span aria-hidden="true">«</span>
|
||||
Previous
|
||||
</CPaginationItem>
|
||||
<CPaginationItem
|
||||
v-for="page in totalPages"
|
||||
@@ -312,7 +286,7 @@ async function confirmDelete() {
|
||||
:disabled="currentPage === totalPages"
|
||||
@click="goToPage(currentPage + 1)"
|
||||
>
|
||||
<span aria-hidden="true">»</span>
|
||||
Next
|
||||
</CPaginationItem>
|
||||
</CPagination>
|
||||
</div>
|
||||
@@ -328,9 +302,6 @@ async function confirmDelete() {
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
<CForm>
|
||||
<CAlert color="danger" v-if="errorMsg" dismissible @close="errorMsg = ''">
|
||||
{{ errorMsg }}
|
||||
</CAlert>
|
||||
<div class="mb-3">
|
||||
<CFormLabel>Code</CFormLabel>
|
||||
<CFormInput v-model="form.code" placeholder="Contoh: BUG" :disabled="isEdit" />
|
||||
@@ -369,20 +340,6 @@ async function confirmDelete() {
|
||||
</CModalFooter>
|
||||
</CModal>
|
||||
|
||||
<!-- Delete Confirmation Modal -->
|
||||
<CModal :visible="deleteModal" @close="deleteModal = false" alignment="center">
|
||||
<CModalHeader dismiss @close="deleteModal = false">
|
||||
<CModalTitle>Konfirmasi Hapus</CModalTitle>
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
<p class="mb-0">Apakah Anda yakin ingin menghapus ticket type ini?</p>
|
||||
</CModalBody>
|
||||
<CModalFooter>
|
||||
<CButton color="secondary" @click="deleteModal = false">Batal</CButton>
|
||||
<CButton color="danger" @click="confirmDelete">Hapus</CButton>
|
||||
</CModalFooter>
|
||||
</CModal>
|
||||
|
||||
<!-- Detail Modal -->
|
||||
<CModal :visible="detailModal" @close="detailModal = false" alignment="center">
|
||||
<CModalHeader dismiss @close="detailModal = false">
|
||||
@@ -394,7 +351,7 @@ async function confirmDelete() {
|
||||
<CTableRow>
|
||||
<CTableHeaderCell style="width: 40%">Code</CTableHeaderCell>
|
||||
<CTableDataCell
|
||||
><code>{{ detailItem.code }}</code></CTableDataCell
|
||||
><b>{{ detailItem.code }}</b></CTableDataCell
|
||||
>
|
||||
</CTableRow>
|
||||
<CTableRow>
|
||||
@@ -404,7 +361,7 @@ async function confirmDelete() {
|
||||
<CTableRow>
|
||||
<CTableHeaderCell>Need Approval</CTableHeaderCell>
|
||||
<CTableDataCell>
|
||||
<CBadge :color="detailItem.need_approval ? 'warning' : 'secondary'">
|
||||
<CBadge :color="detailItem.need_approval ? 'success' : 'secondary'">
|
||||
{{ detailItem.need_approval ? 'Ya' : 'Tidak' }}
|
||||
</CBadge>
|
||||
</CTableDataCell>
|
||||
@@ -412,7 +369,7 @@ async function confirmDelete() {
|
||||
<CTableRow>
|
||||
<CTableHeaderCell>Require Photo</CTableHeaderCell>
|
||||
<CTableDataCell>
|
||||
<CBadge :color="detailItem.require_photo ? 'warning' : 'secondary'">
|
||||
<CBadge :color="detailItem.require_photo ? 'success' : 'secondary'">
|
||||
{{ detailItem.require_photo ? 'Ya' : 'Tidak' }}
|
||||
</CBadge>
|
||||
</CTableDataCell>
|
||||
@@ -420,7 +377,7 @@ async function confirmDelete() {
|
||||
<CTableRow>
|
||||
<CTableHeaderCell>Require Material</CTableHeaderCell>
|
||||
<CTableDataCell>
|
||||
<CBadge :color="detailItem.require_material ? 'warning' : 'secondary'">
|
||||
<CBadge :color="detailItem.require_material ? 'success' : 'secondary'">
|
||||
{{ detailItem.require_material ? 'Ya' : 'Tidak' }}
|
||||
</CBadge>
|
||||
</CTableDataCell>
|
||||
@@ -428,7 +385,7 @@ async function confirmDelete() {
|
||||
<CTableRow>
|
||||
<CTableHeaderCell>Need Customer</CTableHeaderCell>
|
||||
<CTableDataCell>
|
||||
<CBadge :color="detailItem.need_customer ? 'warning' : 'secondary'">
|
||||
<CBadge :color="detailItem.need_customer ? 'success' : 'secondary'">
|
||||
{{ detailItem.need_customer ? 'Ya' : 'Tidak' }}
|
||||
</CBadge>
|
||||
</CTableDataCell>
|
||||
@@ -436,7 +393,7 @@ async function confirmDelete() {
|
||||
<CTableRow>
|
||||
<CTableHeaderCell>Need Incident Type</CTableHeaderCell>
|
||||
<CTableDataCell>
|
||||
<CBadge :color="detailItem.need_incident_type ? 'warning' : 'secondary'">
|
||||
<CBadge :color="detailItem.need_incident_type ? 'success' : 'secondary'">
|
||||
{{ detailItem.need_incident_type ? 'Ya' : 'Tidak' }}
|
||||
</CBadge>
|
||||
</CTableDataCell>
|
||||
|
||||
Reference in New Issue
Block a user