This commit is contained in:
2026-06-24 14:52:49 +07:00
parent 974ba0e3c3
commit a06d0badfc
12 changed files with 973 additions and 8 deletions
+55
View File
@@ -0,0 +1,55 @@
<script setup>
import { computed } from 'vue'
import { useTicketStore } from '@/stores/ticket.js'
const store = useTicketStore()
const approvedTickets = computed(() =>
store.tickets.filter((t) => t.approved_by && t.approved_by !== '-')
)
</script>
<template>
<CRow>
<CCol :xs="12">
<CCard class="mb-4">
<CCardHeader>
<strong>Approved Tickets</strong>
</CCardHeader>
<CCardBody>
<div class="table-responsive">
<CTable striped hover small>
<CTableHead>
<CTableRow>
<CTableHeaderCell scope="col">No</CTableHeaderCell>
<CTableHeaderCell scope="col">Ticket No</CTableHeaderCell>
<CTableHeaderCell scope="col">Kode Tiket</CTableHeaderCell>
<CTableHeaderCell scope="col">Deskripsi</CTableHeaderCell>
<CTableHeaderCell scope="col">Approved By</CTableHeaderCell>
<CTableHeaderCell scope="col">Approved At</CTableHeaderCell>
<CTableHeaderCell scope="col">Status</CTableHeaderCell>
</CTableRow>
</CTableHead>
<CTableBody>
<CTableRow v-for="(item, index) in approvedTickets" :key="item.ticket_no">
<CTableHeaderCell scope="row">{{ index + 1 }}</CTableHeaderCell>
<CTableDataCell>{{ item.ticket_no }}</CTableDataCell>
<CTableDataCell>{{ item.kode_tiket }}</CTableDataCell>
<CTableDataCell>{{ item.deskripsi }}</CTableDataCell>
<CTableDataCell>{{ item.approved_by }}</CTableDataCell>
<CTableDataCell>{{ item.approved_at }}</CTableDataCell>
<CTableDataCell>
<CBadge color="success">Approved</CBadge>
</CTableDataCell>
</CTableRow>
<CTableRow v-if="approvedTickets.length === 0">
<CTableDataCell colspan="7" class="text-center">Tidak ada data approved tickets</CTableDataCell>
</CTableRow>
</CTableBody>
</CTable>
</div>
</CCardBody>
</CCard>
</CCol>
</CRow>
</template>
+53
View File
@@ -0,0 +1,53 @@
<script setup>
import { computed } from 'vue'
import { useTicketStore } from '@/stores/ticket.js'
const store = useTicketStore()
const rejectedTickets = computed(() =>
store.tickets.filter((t) => t.rejected_by && t.rejected_by !== '-')
)
</script>
<template>
<CRow>
<CCol :xs="12">
<CCard class="mb-4">
<CCardHeader>
<strong>Rejected Tickets</strong>
</CCardHeader>
<CCardBody>
<div class="table-responsive">
<CTable striped hover small>
<CTableHead>
<CTableRow>
<CTableHeaderCell scope="col">No</CTableHeaderCell>
<CTableHeaderCell scope="col">Ticket No</CTableHeaderCell>
<CTableHeaderCell scope="col">Kode Tiket</CTableHeaderCell>
<CTableHeaderCell scope="col">Deskripsi</CTableHeaderCell>
<CTableHeaderCell scope="col">Rejected By</CTableHeaderCell>
<CTableHeaderCell scope="col">Rejected At</CTableHeaderCell>
<CTableHeaderCell scope="col">Alasan</CTableHeaderCell>
</CTableRow>
</CTableHead>
<CTableBody>
<CTableRow v-for="(item, index) in rejectedTickets" :key="item.ticket_no">
<CTableHeaderCell scope="row">{{ index + 1 }}</CTableHeaderCell>
<CTableDataCell>{{ item.ticket_no }}</CTableDataCell>
<CTableDataCell>{{ item.kode_tiket }}</CTableDataCell>
<CTableDataCell>{{ item.deskripsi }}</CTableDataCell>
<CTableDataCell>{{ item.rejected_by }}</CTableDataCell>
<CTableDataCell>{{ item.rejected_at }}</CTableDataCell>
<CTableDataCell>{{ item.rejection_reason }}</CTableDataCell>
</CTableRow>
<CTableRow v-if="rejectedTickets.length === 0">
<CTableDataCell colspan="7" class="text-center">Tidak ada data rejected tickets</CTableDataCell>
</CTableRow>
</CTableBody>
</CTable>
</div>
</CCardBody>
</CCard>
</CCol>
</CRow>
</template>
+479
View File
@@ -0,0 +1,479 @@
<script setup>
import { ref, reactive } from 'vue'
import { useTicketStore } from '@/stores/ticket.js'
const store = useTicketStore()
const ticketModal = ref(false)
const detailModal = ref(false)
const approveModal = ref(false)
const rejectModal = ref(false)
const editingIndex = ref(-1)
const isEdit = ref(false)
const form = reactive({
ticket_no: '',
tenant_id: '',
customer_id: '',
deskripsi: '',
kode_tiket: '',
status: 'Open',
prioritas: 'Low',
sla_minutes: '',
ditugaskan_untuk: '',
created_by: '',
approved_by: '',
approved_at: '',
rejected_by: '',
rejected_at: '',
rejection_reason: '',
assigned_at: '',
resolved_at: '',
closed_at: '',
created_at: '',
updated_at: '',
})
const emptyForm = () => ({
ticket_no: '',
tenant_id: '',
customer_id: '',
deskripsi: '',
kode_tiket: '',
status: 'Open',
prioritas: 'Low',
sla_minutes: '',
ditugaskan_untuk: '',
created_by: '',
approved_by: '',
approved_at: '',
rejected_by: '',
rejected_at: '',
rejection_reason: '',
assigned_at: '',
resolved_at: '',
closed_at: '',
created_at: '',
updated_at: '',
})
const detailItem = ref(null)
const approveIndex = ref(-1)
const rejectIndex = ref(-1)
const approverName = ref('')
const rejectorName = ref('')
const rejectReason = ref('')
function openAddModal() {
isEdit.value = false
editingIndex.value = -1
Object.assign(form, emptyForm())
ticketModal.value = true
}
function openEditModal(index) {
isEdit.value = true
editingIndex.value = index
Object.assign(form, store.tickets[index])
ticketModal.value = true
}
function openDetail(item) {
detailItem.value = item
detailModal.value = true
}
function saveTicket() {
if (isEdit.value && editingIndex.value >= 0) {
store.updateTicket(editingIndex.value, form)
} else {
store.addTicket(form)
}
ticketModal.value = false
}
function deleteTicket(index) {
store.deleteTicket(index)
}
function openApproveModal(index) {
approveIndex.value = index
approverName.value = ''
approveModal.value = true
}
function confirmApprove() {
if (approverName.value.trim()) {
store.approveTicket(approveIndex.value, approverName.value.trim())
approveModal.value = false
}
}
function openRejectModal(index) {
rejectIndex.value = index
rejectorName.value = ''
rejectReason.value = ''
rejectModal.value = true
}
function confirmReject() {
if (rejectorName.value.trim() && rejectReason.value.trim()) {
store.rejectTicket(rejectIndex.value, rejectorName.value.trim(), rejectReason.value.trim())
rejectModal.value = false
}
}
function isPending(item) {
return (
(item.approved_by === '-' || !item.approved_by) &&
(item.rejected_by === '-' || !item.rejected_by)
)
}
</script>
<template>
<CRow>
<CCol :xs="12">
<CCard class="mb-4">
<CCardHeader>
<strong>Ticket</strong>
<CButton color="primary" size="sm" class="float-end" @click="openAddModal">
+ Tambah Ticket
</CButton>
</CCardHeader>
<CCardBody>
<div class="table-responsive">
<CTable striped hover small>
<CTableHead>
<CTableRow>
<CTableHeaderCell scope="col">No</CTableHeaderCell>
<CTableHeaderCell scope="col" class="text-center">Ticket No</CTableHeaderCell>
<CTableHeaderCell scope="col" class="text-center w-25"
>Kode Tiket</CTableHeaderCell
>
<CTableHeaderCell scope="col">Deskripsi</CTableHeaderCell>
<CTableHeaderCell scope="col">Status</CTableHeaderCell>
<CTableHeaderCell scope="col">Prioritas</CTableHeaderCell>
<CTableHeaderCell scope="col" class="text-center"
>Ditugaskan Untuk</CTableHeaderCell
>
<CTableHeaderCell scope="col" class="text-center">Aksi</CTableHeaderCell>
</CTableRow>
</CTableHead>
<CTableBody>
<CTableRow v-for="(item, index) in store.tickets" :key="item.ticket_no">
<CTableHeaderCell scope="row">{{ index + 1 }}</CTableHeaderCell>
<CTableDataCell class="text-center">{{ item.ticket_no }}</CTableDataCell>
<CTableDataCell class="text-center w-25">{{ item.kode_tiket }}</CTableDataCell>
<CTableDataCell>{{ item.deskripsi }}</CTableDataCell>
<CTableDataCell>
<CBadge
:color="
item.status === 'Closed'
? 'success'
: item.status === 'Approved'
? 'info'
: item.status === 'Rejected'
? 'dark'
: 'primary'
"
>
{{ item.status }}
</CBadge>
</CTableDataCell>
<CTableDataCell>
<CBadge
:color="
item.prioritas === 'High'
? 'danger'
: item.prioritas === 'Medium'
? 'warning'
: 'secondary'
"
>
{{ item.prioritas }}
</CBadge>
</CTableDataCell>
<CTableDataCell class="text-center">{{ item.ditugaskan_untuk }}</CTableDataCell>
<CTableDataCell class="text-center">
<CButton color="info" size="sm" class="me-1" @click="openDetail(item)"
>Detail</CButton
>
<CButton
v-if="isPending(item)"
color="success"
size="sm"
class="me-1"
@click="openApproveModal(index)"
>Setujui</CButton
>
<CButton
v-if="isPending(item)"
color="warning"
size="sm"
class="me-1"
@click="openRejectModal(index)"
>Tolak</CButton
>
<CButton color="warning" size="sm" class="me-1" @click="openEditModal(index)"
>Edit</CButton
>
<CButton color="danger" size="sm" @click="deleteTicket(index)">Hapus</CButton>
</CTableDataCell>
</CTableRow>
</CTableBody>
</CTable>
</div>
</CCardBody>
</CCard>
</CCol>
</CRow>
<!-- Form Modal (Add / Edit) -->
<CModal :visible="ticketModal" @close="ticketModal = false" size="lg" alignment="center">
<CModalHeader dismiss @close="ticketModal = false">
<CModalTitle>{{ isEdit ? 'Edit Ticket' : 'Tambah Ticket' }}</CModalTitle>
</CModalHeader>
<CModalBody>
<CForm>
<CRow class="mb-3">
<CCol :md="6">
<CFormLabel>Ticket No</CFormLabel>
<CFormInput v-model="form.ticket_no" />
</CCol>
<CCol :md="6">
<CFormLabel>Tenant ID</CFormLabel>
<CFormInput v-model="form.tenant_id" />
</CCol>
</CRow>
<CRow class="mb-3">
<CCol :md="6">
<CFormLabel>Customer ID</CFormLabel>
<CFormInput v-model="form.customer_id" />
</CCol>
<CCol :md="6">
<CFormLabel>Kode Tiket</CFormLabel>
<CFormInput v-model="form.kode_tiket" />
</CCol>
</CRow>
<CRow class="mb-3">
<CCol :md="12">
<CFormLabel>Deskripsi</CFormLabel>
<CFormTextarea v-model="form.deskripsi" rows="2"></CFormTextarea>
</CCol>
</CRow>
<CRow class="mb-3">
<CCol :md="4">
<CFormLabel>Status</CFormLabel>
<CFormSelect v-model="form.status">
<option value="Open">Open</option>
<option value="In Progress">In Progress</option>
<option value="Closed">Closed</option>
</CFormSelect>
</CCol>
<CCol :md="4">
<CFormLabel>Prioritas</CFormLabel>
<CFormSelect v-model="form.prioritas">
<option value="Low">Low</option>
<option value="Medium">Medium</option>
<option value="High">High</option>
<option value="Critical">Critical</option>
</CFormSelect>
</CCol>
<CCol :md="4">
<CFormLabel>SLA (menit)</CFormLabel>
<CFormInput v-model="form.sla_minutes" type="number" />
</CCol>
</CRow>
<CRow class="mb-3">
<CCol :md="6">
<CFormLabel>Ditugaskan Untuk</CFormLabel>
<CFormInput v-model="form.ditugaskan_untuk" />
</CCol>
<CCol :md="6">
<CFormLabel>Created By</CFormLabel>
<CFormInput v-model="form.created_by" />
</CCol>
</CRow>
</CForm>
</CModalBody>
<CModalFooter>
<CButton color="secondary" @click="ticketModal = false">Batal</CButton>
<CButton color="primary" @click="saveTicket">Simpan</CButton>
</CModalFooter>
</CModal>
<!-- Approve Modal -->
<CModal :visible="approveModal" @close="approveModal = false" alignment="center">
<CModalHeader dismiss @close="approveModal = false">
<CModalTitle>Setujui Ticket</CModalTitle>
</CModalHeader>
<CModalBody>
<CForm>
<div class="mb-3">
<CFormLabel>Nama Leader / SPV</CFormLabel>
<CFormInput v-model="approverName" placeholder="Masukkan nama" />
</div>
</CForm>
</CModalBody>
<CModalFooter>
<CButton color="secondary" @click="approveModal = false">Batal</CButton>
<CButton color="success" @click="confirmApprove" :disabled="!approverName.trim()"
>Setujui</CButton
>
</CModalFooter>
</CModal>
<!-- Reject Modal -->
<CModal :visible="rejectModal" @close="rejectModal = false" alignment="center">
<CModalHeader dismiss @close="rejectModal = false">
<CModalTitle>Tolak Ticket</CModalTitle>
</CModalHeader>
<CModalBody>
<CForm>
<div class="mb-3">
<CFormLabel>Nama Leader / SPV</CFormLabel>
<CFormInput v-model="rejectorName" placeholder="Masukkan nama" />
</div>
<div class="mb-3">
<CFormLabel>Alasan Penolakan</CFormLabel>
<CFormTextarea
v-model="rejectReason"
rows="3"
placeholder="Masukkan alasan"
></CFormTextarea>
</div>
</CForm>
</CModalBody>
<CModalFooter>
<CButton color="secondary" @click="rejectModal = false">Batal</CButton>
<CButton
color="warning"
@click="confirmReject"
:disabled="!rejectorName.trim() || !rejectReason.trim()"
>Tolak</CButton
>
</CModalFooter>
</CModal>
<!-- Detail Modal -->
<CModal :visible="detailModal" @close="detailModal = false" size="lg" alignment="center">
<CModalHeader dismiss @close="detailModal = false">
<CModalTitle>Detail Ticket</CModalTitle>
</CModalHeader>
<CModalBody>
<CTable bordered small v-if="detailItem">
<CTableBody>
<CTableRow>
<CTableHeaderCell style="width: 30%">Ticket No</CTableHeaderCell>
<CTableDataCell>{{ detailItem.ticket_no }}</CTableDataCell>
</CTableRow>
<CTableRow>
<CTableHeaderCell>Tenant ID</CTableHeaderCell>
<CTableDataCell>{{ detailItem.tenant_id }}</CTableDataCell>
</CTableRow>
<CTableRow>
<CTableHeaderCell>Customer ID</CTableHeaderCell>
<CTableDataCell>{{ detailItem.customer_id }}</CTableDataCell>
</CTableRow>
<CTableRow>
<CTableHeaderCell>Kode Tiket</CTableHeaderCell>
<CTableDataCell>{{ detailItem.kode_tiket }}</CTableDataCell>
</CTableRow>
<CTableRow>
<CTableHeaderCell>Deskripsi</CTableHeaderCell>
<CTableDataCell>{{ detailItem.deskripsi }}</CTableDataCell>
</CTableRow>
<CTableRow>
<CTableHeaderCell>Status</CTableHeaderCell>
<CTableDataCell>
<CBadge
:color="
detailItem.status === 'Closed'
? 'success'
: detailItem.status === 'Approved'
? 'info'
: detailItem.status === 'Rejected'
? 'dark'
: 'primary'
"
>
{{ detailItem.status }}
</CBadge>
</CTableDataCell>
</CTableRow>
<CTableRow>
<CTableHeaderCell>Prioritas</CTableHeaderCell>
<CTableDataCell>
<CBadge
:color="
detailItem.prioritas === 'High'
? 'danger'
: detailItem.prioritas === 'Medium'
? 'warning'
: 'secondary'
"
>
{{ detailItem.prioritas }}
</CBadge>
</CTableDataCell>
</CTableRow>
<CTableRow>
<CTableHeaderCell>SLA (menit)</CTableHeaderCell>
<CTableDataCell>{{ detailItem.sla_minutes }}</CTableDataCell>
</CTableRow>
<CTableRow>
<CTableHeaderCell>Ditugaskan Untuk</CTableHeaderCell>
<CTableDataCell>{{ detailItem.ditugaskan_untuk }}</CTableDataCell>
</CTableRow>
<CTableRow>
<CTableHeaderCell>Created By</CTableHeaderCell>
<CTableDataCell>{{ detailItem.created_by }}</CTableDataCell>
</CTableRow>
<CTableRow>
<CTableHeaderCell>Approved By</CTableHeaderCell>
<CTableDataCell>{{ detailItem.approved_by || '-' }}</CTableDataCell>
</CTableRow>
<CTableRow>
<CTableHeaderCell>Approved At</CTableHeaderCell>
<CTableDataCell>{{ detailItem.approved_at || '-' }}</CTableDataCell>
</CTableRow>
<CTableRow>
<CTableHeaderCell>Rejected By</CTableHeaderCell>
<CTableDataCell>{{ detailItem.rejected_by || '-' }}</CTableDataCell>
</CTableRow>
<CTableRow>
<CTableHeaderCell>Rejected At</CTableHeaderCell>
<CTableDataCell>{{ detailItem.rejected_at || '-' }}</CTableDataCell>
</CTableRow>
<CTableRow>
<CTableHeaderCell>Rejection Reason</CTableHeaderCell>
<CTableDataCell>{{ detailItem.rejection_reason || '-' }}</CTableDataCell>
</CTableRow>
<CTableRow>
<CTableHeaderCell>Assigned At</CTableHeaderCell>
<CTableDataCell>{{ detailItem.assigned_at || '-' }}</CTableDataCell>
</CTableRow>
<CTableRow>
<CTableHeaderCell>Resolved At</CTableHeaderCell>
<CTableDataCell>{{ detailItem.resolved_at || '-' }}</CTableDataCell>
</CTableRow>
<CTableRow>
<CTableHeaderCell>Closed At</CTableHeaderCell>
<CTableDataCell>{{ detailItem.closed_at || '-' }}</CTableDataCell>
</CTableRow>
<CTableRow>
<CTableHeaderCell>Created At</CTableHeaderCell>
<CTableDataCell>{{ detailItem.created_at || '-' }}</CTableDataCell>
</CTableRow>
<CTableRow>
<CTableHeaderCell>Updated At</CTableHeaderCell>
<CTableDataCell>{{ detailItem.updated_at || '-' }}</CTableDataCell>
</CTableRow>
</CTableBody>
</CTable>
</CModalBody>
<CModalFooter>
<CButton color="secondary" @click="detailModal = false">Tutup</CButton>
</CModalFooter>
</CModal>
</template>
+57
View File
@@ -0,0 +1,57 @@
<template>
<CRow>
<CCol :xs="12">
<CCard class="mb-4">
<CCardHeader> <strong>Ticket Materials</strong> </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>
</CCardBody>
</CCard>
</CCol>
</CRow>
</template>
+54
View File
@@ -0,0 +1,54 @@
<template>
<CRow>
<CCol :xs="12">
<CCard class="mb-4">
<CCardHeader>
<strong>Ticket Type</strong>
</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>
</CCardBody>
</CCard>
</CCol>
</CRow>
</template>