ticketType dan ticketInsidance
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:
@@ -0,0 +1,389 @@
|
||||
<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'
|
||||
|
||||
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('')
|
||||
store.setItems(response.data.data || [])
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch:', err)
|
||||
errorMsg.value = 'Gagal memuat data: ' + err.message
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
const totalPages = computed(() => Math.ceil(store.items.length / perPage.value))
|
||||
|
||||
const paginatedItems = computed(() => {
|
||||
const start = (currentPage.value - 1) * perPage.value
|
||||
return store.items.slice(start, start + perPage.value)
|
||||
})
|
||||
|
||||
function goToPage(page) {
|
||||
if (page >= 1 && page <= totalPages.value) currentPage.value = page
|
||||
}
|
||||
|
||||
function changePerPage(val) {
|
||||
perPage.value = 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',
|
||||
})
|
||||
|
||||
const emptyForm = () => ({
|
||||
name: '',
|
||||
is_active: '1',
|
||||
})
|
||||
|
||||
const detailItem = ref(null)
|
||||
|
||||
function openAddModal() {
|
||||
isEdit.value = false
|
||||
editingIndex.value = -1
|
||||
Object.assign(form, emptyForm())
|
||||
modal.value = true
|
||||
}
|
||||
|
||||
function openEditModal(item) {
|
||||
const idx = store.items.findIndex((t) => t.id === item.id)
|
||||
if (idx === -1) return
|
||||
isEdit.value = true
|
||||
editingIndex.value = idx
|
||||
Object.assign(form, {
|
||||
name: item.name,
|
||||
is_active: item.is_active ? '1' : '0',
|
||||
})
|
||||
modal.value = true
|
||||
}
|
||||
|
||||
function openDetail(item) {
|
||||
getTicketIncidentType(item.id)
|
||||
}
|
||||
|
||||
function openDeleteModal(item) {
|
||||
const idx = store.items.findIndex((t) => t.id === item.id)
|
||||
if (idx === -1) return
|
||||
deleteIndex.value = idx
|
||||
deleteModal.value = true
|
||||
}
|
||||
|
||||
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)
|
||||
store.addItem(response.data.data)
|
||||
modal.value = false
|
||||
currentPage.value = totalPages.value
|
||||
showSuccess('Data berhasil disimpan')
|
||||
fetchTicketIncidentTypes() // Refresh data after creation
|
||||
} catch (err) {
|
||||
errorMsg.value = err.response?.data?.message || 'Gagal menyimpan data: ' + err.message
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function getTicketIncidentType(id) {
|
||||
try {
|
||||
const response = await api.get(`/${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
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
store.updateItem(editingIndex.value, response.data.data)
|
||||
modal.value = false
|
||||
showSuccess('Data berhasil diupdate')
|
||||
fetchTicketIncidentTypes() // Refresh data after update
|
||||
} catch (err) {
|
||||
errorMsg.value = err.response?.data?.message || 'Gagal mengupdate data: ' + err.message
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteTicketIncidentType(id) {
|
||||
try {
|
||||
await api.delete(`/${id}`)
|
||||
store.deleteItem(deleteIndex.value)
|
||||
if (paginatedItems.length === 0 && currentPage.value > 1) currentPage.value--
|
||||
showSuccess('Data berhasil dihapus')
|
||||
fetchTicketIncidentTypes() // Refresh data after deletion
|
||||
} catch (err) {
|
||||
console.error('Delete failed:', err)
|
||||
errorMsg.value = 'Gagal menghapus data: ' + err.message
|
||||
} finally {
|
||||
deleteIndex.value = -1
|
||||
deleteModal.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function saveItem() {
|
||||
if (isEdit.value && editingIndex.value >= 0) {
|
||||
const item = store.items[editingIndex.value]
|
||||
await updateTicketIncidentType(item.id)
|
||||
} else {
|
||||
await createTicketIncidentType()
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmDelete() {
|
||||
if (deleteIndex.value < 0) return
|
||||
const item = store.items[deleteIndex.value]
|
||||
await deleteTicketIncidentType(item.id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CRow>
|
||||
<CCol :xs="12">
|
||||
<CCard class="mb-4">
|
||||
<CCardHeader>
|
||||
<strong>Ticket Incident</strong>
|
||||
<CButton color="warning" size="sm" 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">
|
||||
<CTable striped hover small>
|
||||
<CTableHead>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell scope="col">No</CTableHeaderCell>
|
||||
<CTableHeaderCell scope="col">Nama</CTableHeaderCell>
|
||||
<CTableHeaderCell scope="col">Status</CTableHeaderCell>
|
||||
<CTableHeaderCell scope="col" class="text-center">Created At</CTableHeaderCell>
|
||||
<CTableHeaderCell scope="col" class="text-center">Updated At</CTableHeaderCell>
|
||||
<CTableHeaderCell scope="col" class="text-center">Aksi</CTableHeaderCell>
|
||||
</CTableRow>
|
||||
</CTableHead>
|
||||
<CTableBody>
|
||||
<CTableRow v-for="(item, index) in paginatedItems" :key="item.id">
|
||||
<CTableHeaderCell scope="row">{{
|
||||
(currentPage - 1) * perPage + index + 1
|
||||
}}</CTableHeaderCell>
|
||||
<CTableDataCell>{{ item.name }}</CTableDataCell>
|
||||
<CTableDataCell>
|
||||
<CBadge :color="item.is_active ? 'success' : 'secondary'">
|
||||
{{ item.is_active ? 'Active' : 'Inactive' }}
|
||||
</CBadge>
|
||||
</CTableDataCell>
|
||||
<CTableDataCell class="text-center">{{
|
||||
formatTanggal(item.created_at)
|
||||
}}</CTableDataCell>
|
||||
<CTableDataCell class="text-center">{{
|
||||
formatTanggal(item.updated_at)
|
||||
}}</CTableDataCell>
|
||||
<CTableDataCell class="text-center">
|
||||
<CButton color="info" size="sm" class="me-1" @click="openDetail(item)">
|
||||
Detail
|
||||
</CButton>
|
||||
<CButton color="warning" size="sm" class="me-1" @click="openEditModal(item)">
|
||||
Edit
|
||||
</CButton>
|
||||
<CButton color="danger" size="sm" @click="openDeleteModal(item)">
|
||||
Hapus
|
||||
</CButton>
|
||||
</CTableDataCell>
|
||||
</CTableRow>
|
||||
</CTableBody>
|
||||
</CTable>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span>Tampil</span>
|
||||
<CFormSelect
|
||||
:model-value="perPage"
|
||||
style="width: auto"
|
||||
@change="changePerPage(Number($event.target.value))"
|
||||
>
|
||||
<option :value="10">10</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">
|
||||
<CPaginationItem
|
||||
aria-label="Previous"
|
||||
:disabled="currentPage === 1"
|
||||
@click="goToPage(currentPage - 1)"
|
||||
>
|
||||
<span aria-hidden="true">«</span>
|
||||
</CPaginationItem>
|
||||
<CPaginationItem
|
||||
v-for="page in totalPages"
|
||||
:key="page"
|
||||
:active="page === currentPage"
|
||||
@click="goToPage(page)"
|
||||
>
|
||||
{{ page }}
|
||||
</CPaginationItem>
|
||||
<CPaginationItem
|
||||
aria-label="Next"
|
||||
:disabled="currentPage === totalPages"
|
||||
@click="goToPage(currentPage + 1)"
|
||||
>
|
||||
<span aria-hidden="true">»</span>
|
||||
</CPaginationItem>
|
||||
</CPagination>
|
||||
</div>
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
</CCol>
|
||||
</CRow>
|
||||
|
||||
<!-- Add / Edit Modal -->
|
||||
<CModal :visible="modal" @close="modal = false" alignment="center">
|
||||
<CModalHeader dismiss @close="modal = false">
|
||||
<CModalTitle>{{ isEdit ? 'Edit Ticket Incident' : 'Tambah Ticket Incident' }}</CModalTitle>
|
||||
</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" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<CFormLabel>Status</CFormLabel>
|
||||
<CFormSelect v-model="form.is_active">
|
||||
<option value="1">Active</option>
|
||||
<option value="0">Inactive</option>
|
||||
</CFormSelect>
|
||||
</div>
|
||||
</CForm>
|
||||
</CModalBody>
|
||||
<CModalFooter>
|
||||
<CButton color="secondary" @click="modal = false" :disabled="saving">Batal</CButton>
|
||||
<CButton color="primary" @click="saveItem" :disabled="saving">
|
||||
<span v-if="saving">Menyimpan...</span>
|
||||
<span v-else>Simpan</span>
|
||||
</CButton>
|
||||
</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">
|
||||
<CModalTitle>Detail Ticket Incident</CModalTitle>
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
<CTable bordered small v-if="detailItem">
|
||||
<CTableBody>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell style="width: 40%">ID</CTableHeaderCell>
|
||||
<CTableDataCell>{{ detailItem.id }}</CTableDataCell>
|
||||
</CTableRow>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell>Name</CTableHeaderCell>
|
||||
<CTableDataCell>{{ detailItem.name }}</CTableDataCell>
|
||||
</CTableRow>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell>Status</CTableHeaderCell>
|
||||
<CTableDataCell>
|
||||
<CBadge :color="detailItem.is_active ? 'success' : 'secondary'">
|
||||
{{ detailItem.is_active ? 'Active' : 'Inactive' }}
|
||||
</CBadge>
|
||||
</CTableDataCell>
|
||||
</CTableRow>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell>Created At</CTableHeaderCell>
|
||||
<CTableDataCell>{{ formatTanggal(detailItem.created_at) }}</CTableDataCell>
|
||||
</CTableRow>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell>Updated At</CTableHeaderCell>
|
||||
<CTableDataCell>{{ formatTanggal(detailItem.updated_at) }}</CTableDataCell>
|
||||
</CTableRow>
|
||||
</CTableBody>
|
||||
</CTable>
|
||||
</CModalBody>
|
||||
<CModalFooter>
|
||||
<CButton color="secondary" @click="detailModal = false">Tutup</CButton>
|
||||
</CModalFooter>
|
||||
</CModal>
|
||||
</template>
|
||||
@@ -1,54 +1,463 @@
|
||||
<script setup>
|
||||
import axios from 'axios'
|
||||
import { ref, reactive, computed, watch, onMounted } from 'vue'
|
||||
import { useTicketTypeStore } from '@/stores/ticketType.js'
|
||||
import { formatTanggal } from '@/utils/tglindo.js'
|
||||
|
||||
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
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchAllData()
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
const totalPages = computed(() => Math.ceil(store.ticketTypes.length / perPage.value))
|
||||
|
||||
const paginatedItems = computed(() => {
|
||||
const start = (currentPage.value - 1) * perPage.value
|
||||
return store.ticketTypes.slice(start, start + perPage.value)
|
||||
})
|
||||
|
||||
function goToPage(page) {
|
||||
if (page >= 1 && page <= totalPages.value) currentPage.value = page
|
||||
}
|
||||
|
||||
function changePerPage(val) {
|
||||
perPage.value = val
|
||||
currentPage.value = 1
|
||||
}
|
||||
|
||||
function showSuccess(msg) {
|
||||
successMsg.value = msg
|
||||
if (successTimer) clearTimeout(successTimer)
|
||||
successTimer = setTimeout(() => {
|
||||
successMsg.value = ''
|
||||
}, 3000)
|
||||
}
|
||||
|
||||
const form = reactive({
|
||||
code: '',
|
||||
name: '',
|
||||
need_approval: false,
|
||||
require_photo: false,
|
||||
require_material: false,
|
||||
need_customer: false,
|
||||
need_incident_type: false,
|
||||
sla_minutes: 0,
|
||||
})
|
||||
|
||||
const emptyForm = () => ({
|
||||
code: '',
|
||||
name: '',
|
||||
need_approval: false,
|
||||
require_photo: false,
|
||||
require_material: false,
|
||||
need_customer: false,
|
||||
need_incident_type: false,
|
||||
sla_minutes: 0,
|
||||
})
|
||||
|
||||
const detailItem = ref(null)
|
||||
|
||||
async function getTicketType(id) {
|
||||
try {
|
||||
const res = await api.get(`/ticket-types/${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
|
||||
}
|
||||
}
|
||||
|
||||
function openDetail(item) {
|
||||
getTicketType(item.id)
|
||||
}
|
||||
|
||||
function openAddModal() {
|
||||
isEdit.value = false
|
||||
editingIndex.value = -1
|
||||
Object.assign(form, emptyForm())
|
||||
modal.value = true
|
||||
}
|
||||
|
||||
function openEditModal(item) {
|
||||
const idx = store.ticketTypes.findIndex((t) => t.code === item.code)
|
||||
if (idx === -1) return
|
||||
isEdit.value = true
|
||||
editingIndex.value = idx
|
||||
Object.assign(form, {
|
||||
code: item.code,
|
||||
name: item.name,
|
||||
need_approval: !!item.need_approval,
|
||||
require_photo: !!item.require_photo,
|
||||
require_material: !!item.require_material,
|
||||
need_customer: !!item.need_customer,
|
||||
need_incident_type: !!item.need_incident_type,
|
||||
sla_minutes: item.sla_minutes,
|
||||
})
|
||||
modal.value = true
|
||||
}
|
||||
|
||||
function openDeleteModal(item) {
|
||||
const idx = store.ticketTypes.findIndex((t) => t.code === item.code)
|
||||
if (idx === -1) return
|
||||
deleteIndex.value = idx
|
||||
deleteModal.value = true
|
||||
}
|
||||
|
||||
async function createTicketType() {
|
||||
errorMsg.value = ''
|
||||
saving.value = true
|
||||
const body = {
|
||||
code: form.code,
|
||||
name: form.name,
|
||||
need_approval: form.need_approval,
|
||||
require_photo: form.require_photo,
|
||||
require_material: form.require_material,
|
||||
need_customer: form.need_customer,
|
||||
need_incident_type: form.need_incident_type,
|
||||
sla_minutes: Number(form.sla_minutes),
|
||||
}
|
||||
try {
|
||||
const res = await api.post('/ticket-types', body)
|
||||
store.addTicketType({ ...res.data.data, ...body })
|
||||
modal.value = false
|
||||
currentPage.value = totalPages.value
|
||||
showSuccess('Data berhasil disimpan')
|
||||
fetchAllData()
|
||||
} catch (err) {
|
||||
errorMsg.value = 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,
|
||||
name: form.name,
|
||||
need_approval: form.need_approval,
|
||||
require_photo: form.require_photo,
|
||||
require_material: form.require_material,
|
||||
need_customer: form.need_customer,
|
||||
need_incident_type: form.need_incident_type,
|
||||
sla_minutes: Number(form.sla_minutes),
|
||||
}
|
||||
try {
|
||||
await api.put(`/ticket-types/${id}`, body)
|
||||
store.updateTicketType(editingIndex.value, { ...body })
|
||||
modal.value = false
|
||||
showSuccess('Data berhasil diupdate')
|
||||
fetchAllData()
|
||||
} catch (err) {
|
||||
errorMsg.value = err.response?.data?.message || 'Gagal mengupdate data: ' + err.message
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteTicketType(id) {
|
||||
try {
|
||||
await api.delete(`/ticket-types/${id}`)
|
||||
store.deleteTicketType(deleteIndex.value)
|
||||
if (paginatedItems.length === 0 && currentPage.value > 1) currentPage.value--
|
||||
showSuccess('Data berhasil dihapus')
|
||||
fetchAllData()
|
||||
} catch (err) {
|
||||
console.error('Delete failed:', err)
|
||||
errorMsg.value = 'Gagal menghapus data: ' + err.message
|
||||
} finally {
|
||||
deleteIndex.value = -1
|
||||
deleteModal.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function saveItem() {
|
||||
if (isEdit.value && editingIndex.value >= 0) {
|
||||
const item = store.ticketTypes[editingIndex.value]
|
||||
await updateTicketType(item.id)
|
||||
} else {
|
||||
await createTicketType()
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmDelete() {
|
||||
if (deleteIndex.value < 0) return
|
||||
const item = store.ticketTypes[deleteIndex.value]
|
||||
await deleteTicketType(item.id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CRow>
|
||||
<CCol :xs="12">
|
||||
<CCard class="mb-4">
|
||||
<CCardHeader>
|
||||
<strong>Ticket Type</strong>
|
||||
<CButton color="warning" size="sm" class="float-end" @click="openAddModal">
|
||||
+ Tambah Ticket Type
|
||||
</CButton>
|
||||
</CCardHeader>
|
||||
<CCardBody>
|
||||
<CTable striped hover>
|
||||
<CTableHead>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell scope="col">#</CTableHeaderCell>
|
||||
<CTableHeaderCell scope="col">Name</CTableHeaderCell>
|
||||
<CTableHeaderCell scope="col">Description</CTableHeaderCell>
|
||||
<CTableHeaderCell scope="col">Color</CTableHeaderCell>
|
||||
<CTableHeaderCell scope="col">Status</CTableHeaderCell>
|
||||
</CTableRow>
|
||||
</CTableHead>
|
||||
<CTableBody>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell scope="row">1</CTableHeaderCell>
|
||||
<CTableDataCell>Bug</CTableDataCell>
|
||||
<CTableDataCell>Error or issue in the system</CTableDataCell>
|
||||
<CTableDataCell>Red</CTableDataCell>
|
||||
<CTableDataCell>Active</CTableDataCell>
|
||||
</CTableRow>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell scope="row">2</CTableHeaderCell>
|
||||
<CTableDataCell>Enhancement</CTableDataCell>
|
||||
<CTableDataCell>Request for new feature</CTableDataCell>
|
||||
<CTableDataCell>Blue</CTableDataCell>
|
||||
<CTableDataCell>Active</CTableDataCell>
|
||||
</CTableRow>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell scope="row">3</CTableHeaderCell>
|
||||
<CTableDataCell>Support</CTableDataCell>
|
||||
<CTableDataCell>General support request</CTableDataCell>
|
||||
<CTableDataCell>Green</CTableDataCell>
|
||||
<CTableDataCell>Active</CTableDataCell>
|
||||
</CTableRow>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell scope="row">4</CTableHeaderCell>
|
||||
<CTableDataCell>Question</CTableDataCell>
|
||||
<CTableDataCell>General inquiry</CTableDataCell>
|
||||
<CTableDataCell>Yellow</CTableDataCell>
|
||||
<CTableDataCell>Inactive</CTableDataCell>
|
||||
</CTableRow>
|
||||
</CTableBody>
|
||||
</CTable>
|
||||
<CAlert color="success" v-if="successMsg" dismissible @close="successMsg = ''">
|
||||
{{ successMsg }}
|
||||
</CAlert>
|
||||
<div class="table-responsive">
|
||||
<CTable striped hover small>
|
||||
<CTableHead>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell scope="col">No</CTableHeaderCell>
|
||||
<CTableHeaderCell scope="col">Code</CTableHeaderCell>
|
||||
<CTableHeaderCell scope="col">Nama</CTableHeaderCell>
|
||||
<CTableHeaderCell scope="col" class="text-center">SLA (Minutes)</CTableHeaderCell>
|
||||
<CTableHeaderCell scope="col" class="text-center">Aksi</CTableHeaderCell>
|
||||
</CTableRow>
|
||||
</CTableHead>
|
||||
<CTableBody>
|
||||
<CTableRow v-for="(item, index) in paginatedItems" :key="item.id">
|
||||
<CTableHeaderCell scope="row">{{
|
||||
(currentPage - 1) * perPage + index + 1
|
||||
}}</CTableHeaderCell>
|
||||
<CTableDataCell
|
||||
><code>{{ item.code }}</code></CTableDataCell
|
||||
>
|
||||
<CTableDataCell>{{ item.name }}</CTableDataCell>
|
||||
<CTableDataCell class="text-center">{{ item.sla_minutes }}</CTableDataCell>
|
||||
<CTableDataCell class="text-center">
|
||||
<CButton color="info" size="sm" class="me-1" @click="openDetail(item)">
|
||||
Detail
|
||||
</CButton>
|
||||
<CButton color="warning" size="sm" class="me-1" @click="openEditModal(item)">
|
||||
Edit
|
||||
</CButton>
|
||||
<CButton color="danger" size="sm" @click="openDeleteModal(item)">
|
||||
Hapus
|
||||
</CButton>
|
||||
</CTableDataCell>
|
||||
</CTableRow>
|
||||
</CTableBody>
|
||||
</CTable>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span>Tampil</span>
|
||||
<CFormSelect
|
||||
:model-value="perPage"
|
||||
style="width: auto"
|
||||
@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.ticketTypes.length }} data</span>
|
||||
</div>
|
||||
<CPagination v-if="totalPages > 1" size="sm" aria-label="pagination">
|
||||
<CPaginationItem
|
||||
aria-label="Previous"
|
||||
:disabled="currentPage === 1"
|
||||
@click="goToPage(currentPage - 1)"
|
||||
>
|
||||
<span aria-hidden="true">«</span>
|
||||
</CPaginationItem>
|
||||
<CPaginationItem
|
||||
v-for="page in totalPages"
|
||||
:key="page"
|
||||
:active="page === currentPage"
|
||||
@click="goToPage(page)"
|
||||
>
|
||||
{{ page }}
|
||||
</CPaginationItem>
|
||||
<CPaginationItem
|
||||
aria-label="Next"
|
||||
:disabled="currentPage === totalPages"
|
||||
@click="goToPage(currentPage + 1)"
|
||||
>
|
||||
<span aria-hidden="true">»</span>
|
||||
</CPaginationItem>
|
||||
</CPagination>
|
||||
</div>
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
</CCol>
|
||||
</CRow>
|
||||
|
||||
<!-- Add / Edit Modal -->
|
||||
<CModal :visible="modal" @close="modal = false" alignment="center">
|
||||
<CModalHeader dismiss @close="modal = false">
|
||||
<CModalTitle>{{ isEdit ? 'Edit Ticket Type' : 'Tambah Ticket Type' }}</CModalTitle>
|
||||
</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" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<CFormLabel>Nama</CFormLabel>
|
||||
<CFormInput v-model="form.name" placeholder="Contoh: Bug" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<CFormCheck label="Need Approval" v-model="form.need_approval" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<CFormCheck label="Require Photo" v-model="form.require_photo" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<CFormCheck label="Require Material" v-model="form.require_material" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<CFormCheck label="Need Customer" v-model="form.need_customer" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<CFormCheck label="Need Incident Type" v-model="form.need_incident_type" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<CFormLabel>SLA (Minutes)</CFormLabel>
|
||||
<CFormInput v-model.number="form.sla_minutes" type="number" min="0" />
|
||||
</div>
|
||||
</CForm>
|
||||
</CModalBody>
|
||||
<CModalFooter>
|
||||
<CButton color="secondary" @click="modal = false" :disabled="saving">Batal</CButton>
|
||||
<CButton color="primary" @click="saveItem" :disabled="saving">
|
||||
<span v-if="saving">Menyimpan...</span>
|
||||
<span v-else>Simpan</span>
|
||||
</CButton>
|
||||
</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">
|
||||
<CModalTitle>Detail Ticket Type</CModalTitle>
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
<CTable bordered small v-if="detailItem">
|
||||
<CTableBody>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell style="width: 40%">Code</CTableHeaderCell>
|
||||
<CTableDataCell
|
||||
><code>{{ detailItem.code }}</code></CTableDataCell
|
||||
>
|
||||
</CTableRow>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell>Nama</CTableHeaderCell>
|
||||
<CTableDataCell>{{ detailItem.name }}</CTableDataCell>
|
||||
</CTableRow>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell>Need Approval</CTableHeaderCell>
|
||||
<CTableDataCell>
|
||||
<CBadge :color="detailItem.need_approval ? 'warning' : 'secondary'">
|
||||
{{ detailItem.need_approval ? 'Ya' : 'Tidak' }}
|
||||
</CBadge>
|
||||
</CTableDataCell>
|
||||
</CTableRow>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell>Require Photo</CTableHeaderCell>
|
||||
<CTableDataCell>
|
||||
<CBadge :color="detailItem.require_photo ? 'warning' : 'secondary'">
|
||||
{{ detailItem.require_photo ? 'Ya' : 'Tidak' }}
|
||||
</CBadge>
|
||||
</CTableDataCell>
|
||||
</CTableRow>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell>Require Material</CTableHeaderCell>
|
||||
<CTableDataCell>
|
||||
<CBadge :color="detailItem.require_material ? 'warning' : 'secondary'">
|
||||
{{ detailItem.require_material ? 'Ya' : 'Tidak' }}
|
||||
</CBadge>
|
||||
</CTableDataCell>
|
||||
</CTableRow>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell>Need Customer</CTableHeaderCell>
|
||||
<CTableDataCell>
|
||||
<CBadge :color="detailItem.need_customer ? 'warning' : 'secondary'">
|
||||
{{ detailItem.need_customer ? 'Ya' : 'Tidak' }}
|
||||
</CBadge>
|
||||
</CTableDataCell>
|
||||
</CTableRow>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell>Need Incident Type</CTableHeaderCell>
|
||||
<CTableDataCell>
|
||||
<CBadge :color="detailItem.need_incident_type ? 'warning' : 'secondary'">
|
||||
{{ detailItem.need_incident_type ? 'Ya' : 'Tidak' }}
|
||||
</CBadge>
|
||||
</CTableDataCell>
|
||||
</CTableRow>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell>Created At</CTableHeaderCell>
|
||||
<CTableDataCell>{{ formatTanggal(detailItem.created_at) }}</CTableDataCell>
|
||||
</CTableRow>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell>Updated At</CTableHeaderCell>
|
||||
<CTableDataCell>{{ formatTanggal(detailItem.updated_at) }}</CTableDataCell>
|
||||
</CTableRow>
|
||||
<CTableRow>
|
||||
<CTableHeaderCell>SLA (Minutes)</CTableHeaderCell>
|
||||
<CTableDataCell>{{ detailItem.sla_minutes }}</CTableDataCell>
|
||||
</CTableRow>
|
||||
</CTableBody>
|
||||
</CTable>
|
||||
</CModalBody>
|
||||
<CModalFooter>
|
||||
<CButton color="secondary" @click="detailModal = false">Tutup</CButton>
|
||||
</CModalFooter>
|
||||
</CModal>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user