UI
This commit is contained in:
+43
-1
@@ -32,6 +32,48 @@ export default [
|
||||
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',
|
||||
name: 'Theme',
|
||||
@@ -176,7 +218,7 @@ export default [
|
||||
color: 'danger',
|
||||
text: 'PRO',
|
||||
},
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 129 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.0 MiB |
@@ -1,7 +1,8 @@
|
||||
<script setup>
|
||||
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 { AppSidebarNav } from '@/components/AppSidebarNav.js'
|
||||
import { useSidebarStore } from '@/stores/sidebar.js'
|
||||
@@ -20,9 +21,27 @@ const sidebar = useSidebarStore()
|
||||
>
|
||||
<CSidebarHeader class="border-bottom">
|
||||
<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-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>
|
||||
</RouterLink>
|
||||
<CCloseButton class="d-lg-none" dark @click="sidebar.toggleVisible()" />
|
||||
|
||||
@@ -49,6 +49,43 @@ const routes = [
|
||||
/* 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',
|
||||
name: 'Theme',
|
||||
|
||||
@@ -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
@@ -1,8 +1,8 @@
|
||||
@use "@coreui/coreui/scss/coreui" as * with (
|
||||
@use '@coreui/coreui/scss/coreui' as * with (
|
||||
$enable-deprecation-messages: false
|
||||
);
|
||||
@use "@coreui/chartjs/scss/coreui-chartjs";
|
||||
@use "vendors/simplebar";
|
||||
@use '@coreui/chartjs/scss/coreui-chartjs';
|
||||
@use 'vendors/simplebar';
|
||||
|
||||
body {
|
||||
background-color: var(--cui-tertiary-bg);
|
||||
@@ -12,7 +12,7 @@ body {
|
||||
width: 100%;
|
||||
padding-inline: var(--cui-sidebar-occupy-start, 0) var(--cui-sidebar-occupy-end, 0);
|
||||
will-change: auto;
|
||||
@include transition(padding .15s);
|
||||
@include transition(padding 0.15s);
|
||||
}
|
||||
|
||||
.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 {
|
||||
min-height: 3rem;
|
||||
}
|
||||
@@ -57,11 +88,44 @@ body {
|
||||
}
|
||||
|
||||
@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 {
|
||||
background-color: var(--cui-dark-bg-subtle);
|
||||
background-color: var(--cui-body-bg);
|
||||
}
|
||||
|
||||
.footer {
|
||||
--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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user