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

This commit is contained in:
2026-06-27 09:28:39 +07:00
parent d57332ecf9
commit 63458f397d
11 changed files with 1304 additions and 64 deletions
+19
View File
@@ -956,6 +956,25 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/bootstrap": {
"version": "5.3.8",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz",
"integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"license": "MIT",
"peerDependencies": {
"@popperjs/core": "^2.11.8"
}
},
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.15", "version": "1.1.15",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
+18 -12
View File
@@ -1,61 +1,67 @@
{ {
"hash": "24b8d5d0", "hash": "4d35c1d8",
"configHash": "10ab3426", "configHash": "10ab3426",
"lockfileHash": "8ab86554", "lockfileHash": "02073072",
"browserHash": "177cc826", "browserHash": "4f5afd98",
"optimized": { "optimized": {
"@coreui/icons-vue": { "@coreui/icons-vue": {
"src": "../../@coreui/icons-vue/dist/index.es.js", "src": "../../@coreui/icons-vue/dist/index.es.js",
"file": "@coreui_icons-vue.js", "file": "@coreui_icons-vue.js",
"fileHash": "66cf0d91", "fileHash": "01b1000b",
"needsInterop": false "needsInterop": false
}, },
"@coreui/icons": { "@coreui/icons": {
"src": "../../@coreui/icons/dist/esm/index.js", "src": "../../@coreui/icons/dist/esm/index.js",
"file": "@coreui_icons.js", "file": "@coreui_icons.js",
"fileHash": "0abe2e2c", "fileHash": "11116c1c",
"needsInterop": false "needsInterop": false
}, },
"@coreui/utils": { "@coreui/utils": {
"src": "../../@coreui/utils/dist/esm/index.js", "src": "../../@coreui/utils/dist/esm/index.js",
"file": "@coreui_utils.js", "file": "@coreui_utils.js",
"fileHash": "abb66f41", "fileHash": "a7904746",
"needsInterop": false "needsInterop": false
}, },
"@coreui/vue-chartjs": { "@coreui/vue-chartjs": {
"src": "../../@coreui/vue-chartjs/dist/esm/index.js", "src": "../../@coreui/vue-chartjs/dist/esm/index.js",
"file": "@coreui_vue-chartjs.js", "file": "@coreui_vue-chartjs.js",
"fileHash": "b960fb4f", "fileHash": "ff7ea82c",
"needsInterop": false "needsInterop": false
}, },
"@coreui/vue": { "@coreui/vue": {
"src": "../../@coreui/vue/dist/esm/index.js", "src": "../../@coreui/vue/dist/esm/index.js",
"file": "@coreui_vue.js", "file": "@coreui_vue.js",
"fileHash": "a426af0d", "fileHash": "f875f908",
"needsInterop": false
},
"axios": {
"src": "../../axios/index.js",
"file": "axios.js",
"fileHash": "aab15b87",
"needsInterop": false "needsInterop": false
}, },
"pinia": { "pinia": {
"src": "../../pinia/dist/pinia.mjs", "src": "../../pinia/dist/pinia.mjs",
"file": "pinia.js", "file": "pinia.js",
"fileHash": "bb050b1f", "fileHash": "620e0ade",
"needsInterop": false "needsInterop": false
}, },
"simplebar-vue": { "simplebar-vue": {
"src": "../../simplebar-vue/dist/simplebar-vue.esm.js", "src": "../../simplebar-vue/dist/simplebar-vue.esm.js",
"file": "simplebar-vue.js", "file": "simplebar-vue.js",
"fileHash": "fbc973f2", "fileHash": "dc00c9a8",
"needsInterop": false "needsInterop": false
}, },
"vue-router": { "vue-router": {
"src": "../../vue-router/dist/vue-router.js", "src": "../../vue-router/dist/vue-router.js",
"file": "vue-router.js", "file": "vue-router.js",
"fileHash": "4a4ad759", "fileHash": "71557f71",
"needsInterop": false "needsInterop": false
}, },
"vue": { "vue": {
"src": "../../vue/dist/vue.runtime.esm-bundler.js", "src": "../../vue/dist/vue.runtime.esm-bundler.js",
"file": "vue.js", "file": "vue.js",
"fileHash": "820a5047", "fileHash": "a24d774e",
"needsInterop": false "needsInterop": false
} }
}, },
+332 -5
View File
@@ -10,13 +10,15 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@coreui/chartjs": "^4.2.0", "@coreui/chartjs": "^4.2.0",
"@coreui/coreui": "^5.6.1", "@coreui/coreui": "^5.8.0",
"@coreui/icons": "^3.0.1", "@coreui/icons": "^3.1.0",
"@coreui/icons-vue": "2.2.0", "@coreui/icons-vue": "2.2.0",
"@coreui/utils": "^2.0.2", "@coreui/utils": "^2.0.2",
"@coreui/vue": "^5.8.0", "@coreui/vue": "^5.9.0",
"@coreui/vue-chartjs": "^3.0.0", "@coreui/vue-chartjs": "^3.0.0",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"axios": "^1.18.1",
"bootstrap": "^5.3.8",
"chart.js": "^4.5.1", "chart.js": "^4.5.1",
"pinia": "^3.0.4", "pinia": "^3.0.4",
"simplebar-vue": "^2.4.2", "simplebar-vue": "^2.4.2",
@@ -1371,6 +1373,18 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
} }
}, },
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"license": "MIT",
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/ajv": { "node_modules/ajv": {
"version": "6.15.0", "version": "6.15.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
@@ -1444,6 +1458,12 @@
"url": "https://github.com/sponsors/sxzz" "url": "https://github.com/sponsors/sxzz"
} }
}, },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/autoprefixer": { "node_modules/autoprefixer": {
"version": "10.5.0", "version": "10.5.0",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz",
@@ -1481,6 +1501,18 @@
"postcss": "^8.1.0" "postcss": "^8.1.0"
} }
}, },
"node_modules/axios": {
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz",
"integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.16.0",
"form-data": "^4.0.5",
"https-proxy-agent": "^5.0.1",
"proxy-from-env": "^2.1.0"
}
},
"node_modules/balanced-match": { "node_modules/balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -1517,6 +1549,25 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/bootstrap": {
"version": "5.3.8",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz",
"integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"license": "MIT",
"peerDependencies": {
"@popperjs/core": "^2.11.8"
}
},
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.15", "version": "1.1.15",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
@@ -1562,6 +1613,19 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
} }
}, },
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/callsites": { "node_modules/callsites": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -1657,6 +1721,18 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/concat-map": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -1723,7 +1799,6 @@
"version": "4.4.3", "version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ms": "^2.1.3" "ms": "^2.1.3"
@@ -1744,6 +1819,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/detect-libc": { "node_modules/detect-libc": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -1754,6 +1838,20 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.376", "version": "1.5.376",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.376.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.376.tgz",
@@ -1773,6 +1871,51 @@
"url": "https://github.com/fb55/entities?sponsor=1" "url": "https://github.com/fb55/entities?sponsor=1"
} }
}, },
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
"integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escalade": { "node_modules/escalade": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
@@ -2083,6 +2226,42 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/follow-redirects": {
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/form-data": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
"integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.4",
"mime-types": "^2.1.35"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/fraction.js": { "node_modules/fraction.js": {
"version": "5.3.4", "version": "5.3.4",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
@@ -2111,6 +2290,52 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0" "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
} }
}, },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/glob-parent": { "node_modules/glob-parent": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -2137,6 +2362,18 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-flag": { "node_modules/has-flag": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -2147,6 +2384,45 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/hookable": { "node_modules/hookable": {
"version": "5.5.3", "version": "5.5.3",
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz",
@@ -2169,6 +2445,19 @@
], ],
"license": "MIT" "license": "MIT"
}, },
"node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"license": "MIT",
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/ignore": { "node_modules/ignore": {
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -2685,6 +2974,36 @@
"url": "https://github.com/sponsors/sxzz" "url": "https://github.com/sponsors/sxzz"
} }
}, },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.1.5", "version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
@@ -2737,7 +3056,6 @@
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/muggle-string": { "node_modules/muggle-string": {
@@ -3005,6 +3323,15 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/proxy-from-env": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
"integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/punycode": { "node_modules/punycode": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+5 -3
View File
@@ -19,13 +19,15 @@
}, },
"dependencies": { "dependencies": {
"@coreui/chartjs": "^4.2.0", "@coreui/chartjs": "^4.2.0",
"@coreui/coreui": "^5.6.1", "@coreui/coreui": "^5.8.0",
"@coreui/icons": "^3.0.1", "@coreui/icons": "^3.1.0",
"@coreui/icons-vue": "2.2.0", "@coreui/icons-vue": "2.2.0",
"@coreui/utils": "^2.0.2", "@coreui/utils": "^2.0.2",
"@coreui/vue": "^5.8.0", "@coreui/vue": "^5.9.0",
"@coreui/vue-chartjs": "^3.0.0", "@coreui/vue-chartjs": "^3.0.0",
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"axios": "^1.18.1",
"bootstrap": "^5.3.8",
"chart.js": "^4.5.1", "chart.js": "^4.5.1",
"pinia": "^3.0.4", "pinia": "^3.0.4",
"simplebar-vue": "^2.4.2", "simplebar-vue": "^2.4.2",
+6
View File
@@ -60,6 +60,12 @@ export default [
to: '/tickets/ticket-materials', to: '/tickets/ticket-materials',
icon: 'cil-file', icon: 'cil-file',
}, },
{
component: 'CNavItem',
name: 'Ticket Incident',
to: '/tickets/ticket-incident',
icon: 'cil-Puzzle',
},
{ {
component: 'CNavItem', component: 'CNavItem',
name: 'Approved', name: 'Approved',
+11 -3
View File
@@ -45,9 +45,7 @@ const routes = [
// this generates a separate chunk (about.[hash].js) for this route // this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited. // which is lazy-loaded when the route is visited.
component: () => component: () =>
import( import(/* webpackChunkName: "dashboard" */ '@/views/dashboard/Dashboard.vue'),
/* webpackChunkName: "dashboard" */ '@/views/dashboard/Dashboard.vue'
),
}, },
{ {
path: '/tickets', path: '/tickets',
@@ -74,6 +72,11 @@ const routes = [
name: 'Ticket Materials', name: 'Ticket Materials',
component: () => import('@/views/tickets/TicketMaterials.vue'), component: () => import('@/views/tickets/TicketMaterials.vue'),
}, },
{
path: '/tickets/ticket-incident',
name: 'Ticket Incident',
component: () => import('@/views/tickets/TicketIncident.vue'),
},
{ {
path: '/tickets/approved', path: '/tickets/approved',
name: 'Approved', name: 'Approved',
@@ -84,6 +87,11 @@ const routes = [
name: 'Rejected', name: 'Rejected',
component: () => import('@/views/tickets/Rejected.vue'), component: () => import('@/views/tickets/Rejected.vue'),
}, },
{
path: '/tickets/ticket-incident',
name: 'Ticket Incident',
component: () => import('@/views/tickets/TicketIncident.vue'),
},
], ],
}, },
{ {
+24
View File
@@ -0,0 +1,24 @@
import { ref } from 'vue'
import { defineStore } from 'pinia'
export const useTicketIncidentStore = defineStore('ticketIncident', () => {
const items = ref([])
function setItems(data) {
items.value = data
}
function addItem(item) {
items.value.push({ ...item })
}
function updateItem(index, item) {
items.value[index] = { ...item }
}
function deleteItem(index) {
items.value.splice(index, 1)
}
return { items, setItems, addItem, updateItem, deleteItem }
})
+24
View File
@@ -0,0 +1,24 @@
import { ref } from 'vue'
import { defineStore } from 'pinia'
export const useTicketTypeStore = defineStore('ticketType', () => {
const ticketTypes = ref([])
function setTicketTypes(data) {
ticketTypes.value = data
}
function addTicketType(item) {
ticketTypes.value.push({ ...item })
}
function updateTicketType(index, item) {
ticketTypes.value[index] = { ...item }
}
function deleteTicketType(index) {
ticketTypes.value.splice(index, 1)
}
return { ticketTypes, setTicketTypes, addTicketType, updateTicketType, deleteTicketType }
})
+26
View File
@@ -0,0 +1,26 @@
export function formatTanggal(dateStr) {
if (!dateStr) return '-'
const d = new Date(dateStr)
if (isNaN(d.getTime())) return dateStr
const months = [
'Januari',
'Februari',
'Maret',
'April',
'Mei',
'Juni',
'Juli',
'Agustus',
'September',
'Oktober',
'November',
'Desember',
]
const day = d.getDate()
const month = months[d.getMonth()]
const year = d.getFullYear()
const hours = String(d.getHours()).padStart(2, '0')
const minutes = String(d.getMinutes()).padStart(2, '0')
const seconds = String(d.getSeconds()).padStart(2, '0')
return `${day} ${month} ${year} (${hours}:${minutes}:${seconds})`
}
+389
View File
@@ -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">&laquo;</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">&raquo;</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>
+442 -33
View File
@@ -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> <template>
<CRow> <CRow>
<CCol :xs="12"> <CCol :xs="12">
<CCard class="mb-4"> <CCard class="mb-4">
<CCardHeader> <CCardHeader>
<strong>Ticket Type</strong> <strong>Ticket Type</strong>
<CButton color="warning" size="sm" class="float-end" @click="openAddModal">
+ Tambah Ticket Type
</CButton>
</CCardHeader> </CCardHeader>
<CCardBody> <CCardBody>
<CTable striped hover> <CAlert color="success" v-if="successMsg" dismissible @close="successMsg = ''">
{{ successMsg }}
</CAlert>
<div class="table-responsive">
<CTable striped hover small>
<CTableHead> <CTableHead>
<CTableRow> <CTableRow>
<CTableHeaderCell scope="col">#</CTableHeaderCell> <CTableHeaderCell scope="col">No</CTableHeaderCell>
<CTableHeaderCell scope="col">Name</CTableHeaderCell> <CTableHeaderCell scope="col">Code</CTableHeaderCell>
<CTableHeaderCell scope="col">Description</CTableHeaderCell> <CTableHeaderCell scope="col">Nama</CTableHeaderCell>
<CTableHeaderCell scope="col">Color</CTableHeaderCell> <CTableHeaderCell scope="col" class="text-center">SLA (Minutes)</CTableHeaderCell>
<CTableHeaderCell scope="col">Status</CTableHeaderCell> <CTableHeaderCell scope="col" class="text-center">Aksi</CTableHeaderCell>
</CTableRow> </CTableRow>
</CTableHead> </CTableHead>
<CTableBody> <CTableBody>
<CTableRow> <CTableRow v-for="(item, index) in paginatedItems" :key="item.id">
<CTableHeaderCell scope="row">1</CTableHeaderCell> <CTableHeaderCell scope="row">{{
<CTableDataCell>Bug</CTableDataCell> (currentPage - 1) * perPage + index + 1
<CTableDataCell>Error or issue in the system</CTableDataCell> }}</CTableHeaderCell>
<CTableDataCell>Red</CTableDataCell> <CTableDataCell
<CTableDataCell>Active</CTableDataCell> ><code>{{ item.code }}</code></CTableDataCell
</CTableRow> >
<CTableRow> <CTableDataCell>{{ item.name }}</CTableDataCell>
<CTableHeaderCell scope="row">2</CTableHeaderCell> <CTableDataCell class="text-center">{{ item.sla_minutes }}</CTableDataCell>
<CTableDataCell>Enhancement</CTableDataCell> <CTableDataCell class="text-center">
<CTableDataCell>Request for new feature</CTableDataCell> <CButton color="info" size="sm" class="me-1" @click="openDetail(item)">
<CTableDataCell>Blue</CTableDataCell> Detail
<CTableDataCell>Active</CTableDataCell> </CButton>
</CTableRow> <CButton color="warning" size="sm" class="me-1" @click="openEditModal(item)">
<CTableRow> Edit
<CTableHeaderCell scope="row">3</CTableHeaderCell> </CButton>
<CTableDataCell>Support</CTableDataCell> <CButton color="danger" size="sm" @click="openDeleteModal(item)">
<CTableDataCell>General support request</CTableDataCell> Hapus
<CTableDataCell>Green</CTableDataCell> </CButton>
<CTableDataCell>Active</CTableDataCell> </CTableDataCell>
</CTableRow>
<CTableRow>
<CTableHeaderCell scope="row">4</CTableHeaderCell>
<CTableDataCell>Question</CTableDataCell>
<CTableDataCell>General inquiry</CTableDataCell>
<CTableDataCell>Yellow</CTableDataCell>
<CTableDataCell>Inactive</CTableDataCell>
</CTableRow> </CTableRow>
</CTableBody> </CTableBody>
</CTable> </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">&laquo;</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">&raquo;</span>
</CPaginationItem>
</CPagination>
</div>
</CCardBody> </CCardBody>
</CCard> </CCard>
</CCol> </CCol>
</CRow> </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> </template>