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
+43 -1
View File
@@ -32,6 +32,48 @@ export default [
text: 'NEW', text: 'NEW',
}, },
}, },
{
component: 'CNavTitle',
name: 'Ticketing',
},
{
component: 'CNavGroup',
name: 'Ticketing',
to: '/ticket',
icon: 'cil-layers',
items: [
{
component: 'CNavItem',
name: 'Ticket',
to: '/tickets/ticket',
icon: 'cil-notes',
},
{
component: 'CNavItem',
name: 'Ticket Type',
to: '/tickets/ticket-type',
icon: 'cil-list',
},
{
component: 'CNavItem',
name: 'Ticket Materials',
to: '/tickets/ticket-materials',
icon: 'cil-file',
},
{
component: 'CNavItem',
name: 'Approved',
to: '/tickets/approved',
icon: 'cil-check',
},
{
component: 'CNavItem',
name: 'Rejected',
to: '/tickets/rejected',
icon: 'cil-ban',
},
],
},
{ {
component: 'CNavTitle', component: 'CNavTitle',
name: 'Theme', name: 'Theme',
@@ -176,7 +218,7 @@ export default [
color: 'danger', color: 'danger',
text: 'PRO', text: 'PRO',
}, },
} },
], ],
}, },
{ {
Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

+21 -2
View File
@@ -1,7 +1,8 @@
<script setup> <script setup>
import { RouterLink } from 'vue-router' import { RouterLink } from 'vue-router'
import { logo } from '@/assets/brand/logo' // import { logo } from '@/assets/brand/logo'
import logo from '@/assets/images/Logo_big.png'
import { sygnet } from '@/assets/brand/sygnet' import { sygnet } from '@/assets/brand/sygnet'
import { AppSidebarNav } from '@/components/AppSidebarNav.js' import { AppSidebarNav } from '@/components/AppSidebarNav.js'
import { useSidebarStore } from '@/stores/sidebar.js' import { useSidebarStore } from '@/stores/sidebar.js'
@@ -20,9 +21,27 @@ const sidebar = useSidebarStore()
> >
<CSidebarHeader class="border-bottom"> <CSidebarHeader class="border-bottom">
<RouterLink custom to="/" v-slot="{ href, navigate }"> <RouterLink custom to="/" v-slot="{ href, navigate }">
<CSidebarBrand v-bind="$attrs" as="a" :href="href" @click="navigate"> <!-- <CSidebarBrand v-bind="$attrs" as="a" :href="href" @click="navigate">
<CIcon custom-class-name="sidebar-brand-full" :icon="logo" :height="32" /> <CIcon custom-class-name="sidebar-brand-full" :icon="logo" :height="32" />
<CIcon custom-class-name="sidebar-brand-narrow" :icon="sygnet" :height="32" /> <CIcon custom-class-name="sidebar-brand-narrow" :icon="sygnet" :height="32" />
</CSidebarBrand> -->
<CSidebarBrand
v-bind="$attrs"
as="a"
:href="href"
@click="navigate"
class="d-flex align-items-center text-decoration-none"
>
<!-- Logo -->
<img :src="logo" alt="Logo" style="height: 60px; width: auto" />
<!-- Teks -->
<div class="ms-2 text-start">
<div class="fw-bold" style="font-size: 30px; line-height: 1; color: #ff8c00">
ManjaPro
</div>
<small class="fw-bold" style="font-size: 11px"> PT. Rhadika Data Nusantara </small>
</div>
</CSidebarBrand> </CSidebarBrand>
</RouterLink> </RouterLink>
<CCloseButton class="d-lg-none" dark @click="sidebar.toggleVisible()" /> <CCloseButton class="d-lg-none" dark @click="sidebar.toggleVisible()" />
+37
View File
@@ -49,6 +49,43 @@ const routes = [
/* webpackChunkName: "dashboard" */ '@/views/dashboard/Dashboard.vue' /* webpackChunkName: "dashboard" */ '@/views/dashboard/Dashboard.vue'
), ),
}, },
{
path: '/tickets',
name: 'Tickets',
component: {
render() {
return h(resolveComponent('router-view'))
},
},
redirect: '/tickets/ticket',
children: [
{
path: '/tickets/ticket',
name: 'Ticket',
component: () => import('@/views/tickets/Ticket.vue'),
},
{
path: '/tickets/ticket-type',
name: 'Ticket Type',
component: () => import('@/views/tickets/TicketType.vue'),
},
{
path: '/tickets/ticket-materials',
name: 'Ticket Materials',
component: () => import('@/views/tickets/TicketMaterials.vue'),
},
{
path: '/tickets/approved',
name: 'Approved',
component: () => import('@/views/tickets/Approved.vue'),
},
{
path: '/tickets/rejected',
name: 'Rejected',
component: () => import('@/views/tickets/Rejected.vue'),
},
],
},
{ {
path: '/theme', path: '/theme',
name: 'Theme', name: 'Theme',
+105
View File
@@ -0,0 +1,105 @@
import { ref } from 'vue'
import { defineStore } from 'pinia'
export const useTicketStore = defineStore('ticket', () => {
const tickets = ref([
{
ticket_no: 'TKT-001',
tenant_id: 'tenant-01',
customer_id: 'CUST-1001',
deskripsi: 'Modem Merah',
kode_tiket: 'GGN',
status: 'Open',
prioritas: 'Low',
sla_minutes: '480',
ditugaskan_untuk: 'Joko',
created_by: 'admin@company.com',
approved_by: 'manager@company.com',
approved_at: '2026-06-20 09:00',
rejected_by: '-',
rejected_at: '-',
rejection_reason: '-',
assigned_at: '2026-06-20 09:30',
resolved_at: '-',
closed_at: '-',
created_at: '2026-06-20 08:00',
updated_at: '2026-06-20 09:30',
},
{
ticket_no: 'TKT-002',
tenant_id: 'tenant-01',
customer_id: 'CUST-1002',
deskripsi: 'Ganti Perangkat',
kode_tiket: 'ONU',
status: 'Open',
prioritas: 'Medium',
sla_minutes: '240',
ditugaskan_untuk: 'Bowo',
created_by: 'user@company.com',
approved_by: '-',
approved_at: '-',
rejected_by: 'supervisor@company.com',
rejected_at: '2026-06-22 10:00',
rejection_reason: 'Duplicate request',
assigned_at: '-',
resolved_at: '-',
closed_at: '-',
created_at: '2026-06-22 09:00',
updated_at: '2026-06-22 10:00',
},
{
ticket_no: 'TKT-003',
tenant_id: 'tenant-02',
customer_id: 'CUST-1003',
deskripsi: 'Perawatan Perangkat',
kode_tiket: 'MNT',
status: 'Closed',
prioritas: 'High',
sla_minutes: '720',
ditugaskan_untuk: 'Bahlil',
created_by: 'client@domain.com',
approved_by: 'manager@company.com',
approved_at: '2026-06-23 08:00',
rejected_by: '-',
rejected_at: '-',
rejection_reason: '-',
assigned_at: '2026-06-23 08:30',
resolved_at: '2026-06-24 15:00',
closed_at: '2026-06-24 16:00',
created_at: '2026-06-23 07:00',
updated_at: '2026-06-24 16:00',
},
])
const approvedTickets = ref([])
const rejectedTickets = ref([])
function addTicket(ticket) {
tickets.value.push({ ...ticket })
}
function updateTicket(index, ticket) {
tickets.value[index] = { ...ticket }
}
function deleteTicket(index) {
tickets.value.splice(index, 1)
}
function approveTicket(index, approver) {
const now = new Date().toISOString().slice(0, 16).replace('T', ' ')
tickets.value[index].approved_by = approver
tickets.value[index].approved_at = now
tickets.value[index].status = 'Approved'
}
function rejectTicket(index, rejector, reason) {
const now = new Date().toISOString().slice(0, 16).replace('T', ' ')
tickets.value[index].rejected_by = rejector
tickets.value[index].rejected_at = now
tickets.value[index].rejection_reason = reason
tickets.value[index].status = 'Rejected'
}
return { tickets, addTicket, updateTicket, deleteTicket, approveTicket, rejectTicket }
})
+69 -5
View File
@@ -1,8 +1,8 @@
@use "@coreui/coreui/scss/coreui" as * with ( @use '@coreui/coreui/scss/coreui' as * with (
$enable-deprecation-messages: false $enable-deprecation-messages: false
); );
@use "@coreui/chartjs/scss/coreui-chartjs"; @use '@coreui/chartjs/scss/coreui-chartjs';
@use "vendors/simplebar"; @use 'vendors/simplebar';
body { body {
background-color: var(--cui-tertiary-bg); background-color: var(--cui-tertiary-bg);
@@ -12,7 +12,7 @@ body {
width: 100%; width: 100%;
padding-inline: var(--cui-sidebar-occupy-start, 0) var(--cui-sidebar-occupy-end, 0); padding-inline: var(--cui-sidebar-occupy-start, 0) var(--cui-sidebar-occupy-end, 0);
will-change: auto; will-change: auto;
@include transition(padding .15s); @include transition(padding 0.15s);
} }
.header > .container-fluid, .header > .container-fluid,
@@ -48,6 +48,37 @@ body {
} }
} }
.sidebar-nav .nav-link {
&.active {
color: #ff8c00;
background: rgba(255, 140, 0, 0.1);
.nav-icon {
color: #ff8c00;
}
}
&:hover {
color: #ff8c00;
background: rgba(255, 140, 0, 0.08);
.nav-icon {
color: #ff8c00;
}
}
}
.header-nav .nav-link {
&.active {
color: #ff8c00;
}
&:hover,
&:focus {
color: #ff8c00;
}
}
.header > .container-fluid + .container-fluid { .header > .container-fluid + .container-fluid {
min-height: 3rem; min-height: 3rem;
} }
@@ -57,11 +88,44 @@ body {
} }
@include color-mode(dark) { @include color-mode(dark) {
--cui-body-bg: #0d0e12;
--cui-body-bg-rgb: 13, 14, 18;
--cui-secondary-bg: #121318;
--cui-secondary-bg-rgb: 18, 19, 24;
--cui-tertiary-bg: #16171d;
--cui-tertiary-bg-rgb: 22, 23, 29;
--cui-sidebar-bg: #0d0e12;
--cui-sidebar-nav-link-active-bg: rgba(242, 133, 0, 0.1);
--cui-card-bg: #16171d;
--cui-card-cap-bg: #16171d;
--cui-card-border-color: #22242d;
--cui-border-color: #22242d;
--cui-border-color-translucent: rgba(34, 36, 45, 0.8);
--cui-dark-bg-subtle: #0d0e12;
--cui-primary: #f28500;
--cui-primary-rgb: 242, 133, 0;
--cui-link-color: #f28500;
--cui-link-color-rgb: 242, 133, 0;
--cui-table-bg: #16171d;
--cui-table-color: rgba(255, 255, 255, 0.87);
--cui-input-bg: #16171d;
--cui-input-color: rgba(255, 255, 255, 0.87);
--cui-input-border-color: #22242d;
--cui-list-group-bg: #16171d;
--cui-list-group-border-color: #22242d;
body { body {
background-color: var(--cui-dark-bg-subtle); background-color: var(--cui-body-bg);
} }
.footer { .footer {
--cui-footer-bg: var(--cui-body-bg); --cui-footer-bg: var(--cui-body-bg);
} }
} }
.sidebar-dark {
--cui-body-bg: #0d0e12;
--cui-secondary-bg: #121318;
--cui-tertiary-bg: #16171d;
--cui-border-color: #22242d;
}
+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>