Ticket material
NPM Installation / build (16.x, ubuntu-latest) (push) Has been cancelled
NPM Installation / build (16.x, windows-latest) (push) Has been cancelled
NPM Installation / build (17.x, ubuntu-latest) (push) Has been cancelled
NPM Installation / build (17.x, windows-latest) (push) Has been cancelled
NPM Installation / build (18.x, ubuntu-latest) (push) Has been cancelled
NPM Installation / build (18.x, windows-latest) (push) Has been cancelled
NPM Installation / build (16.x, ubuntu-latest) (push) Has been cancelled
NPM Installation / build (16.x, windows-latest) (push) Has been cancelled
NPM Installation / build (17.x, ubuntu-latest) (push) Has been cancelled
NPM Installation / build (17.x, windows-latest) (push) Has been cancelled
NPM Installation / build (18.x, ubuntu-latest) (push) Has been cancelled
NPM Installation / build (18.x, windows-latest) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
# CodeGraph data files — local to each machine, not for committing.
|
||||||
|
# Ignore everything in .codegraph/ except this file itself, so transient
|
||||||
|
# files (the database, daemon.pid, sockets, logs) never show up in git.
|
||||||
|
*
|
||||||
|
!.gitignore
|
||||||
+34
-30
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<!--
|
<!--
|
||||||
* CoreUI Free Vue.js Admin Template
|
* CoreUI Free Vue.js Admin Template
|
||||||
* @version v5.5.0
|
* @version v5.5.0
|
||||||
@@ -8,41 +8,45 @@
|
|||||||
-->
|
-->
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0,shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width,initial-scale=1.0,shrink-to-fit=no" />
|
||||||
<meta name="description" content="CoreUI Vue.js Admin Template">
|
<meta name="description" content="ManjaPro 2 - UI/UX" />
|
||||||
<meta name="author" content="creativeLabs Łukasz Holeczek">
|
<meta name="author" content="creativeLabs Łukasz Holeczek" />
|
||||||
<title>CoreUI Vue.js Admin Template</title>
|
<title>ManjaPro 2</title>
|
||||||
<!-- favicons for all devices -->
|
<!-- favicons for all devices -->
|
||||||
<link rel="apple-touch-icon" sizes="57x57" href="/apple-icon-57x57.png">
|
<link rel="apple-touch-icon" sizes="57x57" href="/apple-icon-57x57.png" />
|
||||||
<link rel="apple-touch-icon" sizes="60x60" href="/apple-icon-60x60.png">
|
<link rel="apple-touch-icon" sizes="60x60" href="/apple-icon-60x60.png" />
|
||||||
<link rel="apple-touch-icon" sizes="72x72" href="/apple-icon-72x72.png">
|
<link rel="apple-touch-icon" sizes="72x72" href="/apple-icon-72x72.png" />
|
||||||
<link rel="apple-touch-icon" sizes="76x76" href="/apple-icon-76x76.png">
|
<link rel="apple-touch-icon" sizes="76x76" href="/apple-icon-76x76.png" />
|
||||||
<link rel="apple-touch-icon" sizes="114x114" href="/apple-icon-114x114.png">
|
<link rel="apple-touch-icon" sizes="114x114" href="/apple-icon-114x114.png" />
|
||||||
<link rel="apple-touch-icon" sizes="120x120" href="/apple-icon-120x120.png">
|
<link rel="apple-touch-icon" sizes="120x120" href="/apple-icon-120x120.png" />
|
||||||
<link rel="apple-touch-icon" sizes="144x144" href="/apple-icon-144x144.png">
|
<link rel="apple-touch-icon" sizes="144x144" href="/apple-icon-144x144.png" />
|
||||||
<link rel="apple-touch-icon" sizes="152x152" href="/apple-icon-152x152.png">
|
<link rel="apple-touch-icon" sizes="152x152" href="/apple-icon-152x152.png" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png" />
|
||||||
<link rel="icon" type="image/png" sizes="192x192" href="/android-icon-192x192.png">
|
<link rel="icon" type="image/png" sizes="192x192" href="/android-icon-192x192.png" />
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||||
<link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png">
|
<link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png" />
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||||
<link rel="manifest" href="/manifest.json">
|
<link rel="manifest" href="/manifest.json" />
|
||||||
<meta name="msapplication-TileColor" content="#ffffff">
|
<meta name="msapplication-TileColor" content="#ffffff" />
|
||||||
<meta name="msapplication-TileImage" content="/ms-icon-144x144.png">
|
<meta name="msapplication-TileImage" content="/ms-icon-144x144.png" />
|
||||||
<meta name="theme-color" content="#ffffff">
|
<meta name="theme-color" content="#ffffff" />
|
||||||
<script>
|
<script>
|
||||||
const userMode = localStorage.getItem('coreui-free-vue-admin-template-theme');
|
const userMode = localStorage.getItem('coreui-free-vue-admin-template-theme')
|
||||||
const systemDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
const systemDarkMode =
|
||||||
if (userMode === 'dark' || (userMode !== 'light' && systemDarkMode)) {
|
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
document.documentElement.dataset.coreuiTheme = 'dark';
|
if (userMode === 'dark' || (userMode !== 'light' && systemDarkMode)) {
|
||||||
}
|
document.documentElement.dataset.coreuiTheme = 'dark'
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>
|
<noscript>
|
||||||
<strong>We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
<strong
|
||||||
|
>We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it
|
||||||
|
to continue.</strong
|
||||||
|
>
|
||||||
</noscript>
|
</noscript>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
|||||||
+26
@@ -975,6 +975,22 @@
|
|||||||
"@popperjs/core": "^2.11.8"
|
"@popperjs/core": "^2.11.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bootstrap-icons": {
|
||||||
|
"version": "1.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.13.1.tgz",
|
||||||
|
"integrity": "sha512-ijombt4v6bv5CLeXvRWKy7CuM3TRTuPEuGaGKvTV5cz65rQSY8RQ2JcHt6b90cBBAC7s8fsf2EkQDldzCoXUjw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/twbs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/bootstrap"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"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",
|
||||||
@@ -2770,6 +2786,16 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sweetalert2": {
|
||||||
|
"version": "11.26.25",
|
||||||
|
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.26.25.tgz",
|
||||||
|
"integrity": "sha512-+hunCOJdJ6FLj04T9YSLvvZXRjsvIkTeTKP2e4VF8CaBias961BTnWiSFAy7F/CM5eq3QK2Rraoc5Gzftslvkg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/limonte"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tinyglobby": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.17",
|
"version": "0.2.17",
|
||||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import { t as __exportAll } from "./chunk-D7D4PA-g.js";
|
import { n as __exportAll } from "./chunk-EAsCxrDo.js";
|
||||||
//#region node_modules/@coreui/icons/dist/esm/brand/cib-500px-5.js
|
//#region node_modules/@coreui/icons/dist/esm/brand/cib-500px-5.js
|
||||||
var cib500px5 = ["32 32", "<path d='M8.456 21.519c-.406-.887-.431-1.144.462-1.444 1.6-.5.5.575 2.7 3.075h.019v-5.869c.075-3.137 2.75-5.762 6.106-5.762 3.369 0 6.106 2.719 6.106 6.05 0 3.962-3.8 7.075-8.031 5.831-.656-.262-.131-1.981.531-1.787 3.313 0 5.587-.631 5.587-4.025 0-3.813-4.819-5.6-7.306-2.787-1.469 1.65-1.1 2.631-1.1 9.85 3.169 1.938 7.394 1.375 10.025-1.256a8.2 8.2 0 0 0 2.406-5.813c0-2.2-.862-4.262-2.425-5.831-1.55-1.55-3.613-2.406-5.831-2.406s-4.3.863-5.844 2.406c-.019.019-1 1.031-1.325 1.494l-.031.038c-.206.294-.394.569-1.256.381-.431-.106-.894-.363-.894-.738V1.251c0-.313.244-.656.656-.656h15.081c.519 0 .519.725.519.944 0 .244 0 .944-.519.944h-13.95v8.306h.019c6.512-6.863 17.675-2.25 17.675 6.806 0 11.131-15.3 13.769-19.381 3.925zm3.957-16.3c-.031.262.287 1.531.912 1.288C21.125 3.538 26 9.032 26.413 9.032c.3 0 1.425-.956.894-1.425-5.825-5.563-14.656-3.563-14.894-2.387zm14.15 20.7c-6.875 6.869-18.688 3.8-20.75-6.512 0-.762-1.9-.462-1.806.206 1.5 10.837 15.375 16.056 23.85 7.581.431-.488-.787-1.775-1.294-1.275M15.35 19.163c0 .25.269.456.344.531.188.188.381.275.531.275.237 0 .162.012 1.394-1.219 1.225 1.206 1.194 1.219 1.394 1.219.338 0 1.156-.65.669-1.137l-1.081-1.081 1.137-1.137c.394-.425-.631-1.363-1.012-.981l-1.119 1.119c-1.162-1.175-1.15-1.219-1.344-1.219-.313 0-1.125.731-.775 1.081l1.137 1.137c-1.131 1.119-1.275 1.2-1.275 1.413z'/>"];
|
var cib500px5 = ["32 32", "<path d='M8.456 21.519c-.406-.887-.431-1.144.462-1.444 1.6-.5.5.575 2.7 3.075h.019v-5.869c.075-3.137 2.75-5.762 6.106-5.762 3.369 0 6.106 2.719 6.106 6.05 0 3.962-3.8 7.075-8.031 5.831-.656-.262-.131-1.981.531-1.787 3.313 0 5.587-.631 5.587-4.025 0-3.813-4.819-5.6-7.306-2.787-1.469 1.65-1.1 2.631-1.1 9.85 3.169 1.938 7.394 1.375 10.025-1.256a8.2 8.2 0 0 0 2.406-5.813c0-2.2-.862-4.262-2.425-5.831-1.55-1.55-3.613-2.406-5.831-2.406s-4.3.863-5.844 2.406c-.019.019-1 1.031-1.325 1.494l-.031.038c-.206.294-.394.569-1.256.381-.431-.106-.894-.363-.894-.738V1.251c0-.313.244-.656.656-.656h15.081c.519 0 .519.725.519.944 0 .244 0 .944-.519.944h-13.95v8.306h.019c6.512-6.863 17.675-2.25 17.675 6.806 0 11.131-15.3 13.769-19.381 3.925zm3.957-16.3c-.031.262.287 1.531.912 1.288C21.125 3.538 26 9.032 26.413 9.032c.3 0 1.425-.956.894-1.425-5.825-5.563-14.656-3.563-14.894-2.387zm14.15 20.7c-6.875 6.869-18.688 3.8-20.75-6.512 0-.762-1.9-.462-1.806.206 1.5 10.837 15.375 16.056 23.85 7.581.431-.488-.787-1.775-1.294-1.275M15.35 19.163c0 .25.269.456.344.531.188.188.381.275.531.275.237 0 .162.012 1.394-1.219 1.225 1.206 1.194 1.219 1.394 1.219.338 0 1.156-.65.669-1.137l-1.081-1.081 1.137-1.137c.394-.425-.631-1.363-1.012-.981l-1.119 1.119c-1.162-1.175-1.15-1.219-1.344-1.219-.313 0-1.125.731-.775 1.081l1.137 1.137c-1.131 1.119-1.275 1.2-1.275 1.413z'/>"];
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
import { t as __exportAll } from "./chunk-D7D4PA-g.js";
|
import { n as __exportAll } from "./chunk-EAsCxrDo.js";
|
||||||
import { $n as toRefs, At as onBeforeMount, C as vShow, Cn as withDirectives, Ft as onMounted, I as Teleport, Kn as ref, Ot as nextTick, U as computed, Ut as provide, V as cloneVNode, Yt as resolveComponent, cn as useId, gn as watch, jt as onBeforeUnmount, n as Transition, nt as defineComponent, pt as h, ut as getCurrentInstance, xt as inject, zt as onUnmounted } from "./vue.runtime.esm-bundler-BbnC8aGa.js";
|
import { $n as toRefs, At as onBeforeMount, C as vShow, Cn as withDirectives, Ft as onMounted, I as Teleport, Kn as ref, Ot as nextTick, U as computed, Ut as provide, V as cloneVNode, Yt as resolveComponent, cn as useId, gn as watch, jt as onBeforeUnmount, n as Transition, nt as defineComponent, pt as h, ut as getCurrentInstance, xt as inject, zt as onUnmounted } from "./vue.runtime.esm-bundler-BbnC8aGa.js";
|
||||||
//#region node_modules/@coreui/vue/dist/esm/components/accordion/CAccordion.js
|
//#region node_modules/@coreui/vue/dist/esm/components/accordion/CAccordion.js
|
||||||
var CAccordion = defineComponent({
|
var CAccordion = defineComponent({
|
||||||
|
|||||||
+21
-15
@@ -1,73 +1,79 @@
|
|||||||
{
|
{
|
||||||
"hash": "4d35c1d8",
|
"hash": "2e340ed7",
|
||||||
"configHash": "10ab3426",
|
"configHash": "10ab3426",
|
||||||
"lockfileHash": "02073072",
|
"lockfileHash": "70e9b55b",
|
||||||
"browserHash": "4f5afd98",
|
"browserHash": "3bb1ff32",
|
||||||
"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": "01b1000b",
|
"fileHash": "4d6686d2",
|
||||||
"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": "11116c1c",
|
"fileHash": "907f9c3e",
|
||||||
"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": "a7904746",
|
"fileHash": "6e72ff9c",
|
||||||
"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": "ff7ea82c",
|
"fileHash": "50ed4496",
|
||||||
"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": "f875f908",
|
"fileHash": "3ebf09b5",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"axios": {
|
"axios": {
|
||||||
"src": "../../axios/index.js",
|
"src": "../../axios/index.js",
|
||||||
"file": "axios.js",
|
"file": "axios.js",
|
||||||
"fileHash": "aab15b87",
|
"fileHash": "184e9400",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"pinia": {
|
"pinia": {
|
||||||
"src": "../../pinia/dist/pinia.mjs",
|
"src": "../../pinia/dist/pinia.mjs",
|
||||||
"file": "pinia.js",
|
"file": "pinia.js",
|
||||||
"fileHash": "620e0ade",
|
"fileHash": "46bbb8ba",
|
||||||
"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": "dc00c9a8",
|
"fileHash": "0567ff81",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
|
"sweetalert2": {
|
||||||
|
"src": "../../sweetalert2/dist/sweetalert2.all.js",
|
||||||
|
"file": "sweetalert2.js",
|
||||||
|
"fileHash": "aaaec58d",
|
||||||
|
"needsInterop": true
|
||||||
|
},
|
||||||
"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": "71557f71",
|
"fileHash": "7ea651fe",
|
||||||
"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": "a24d774e",
|
"fileHash": "50e5209b",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chunks": {
|
"chunks": {
|
||||||
"chunk-D7D4PA-g": {
|
"chunk-EAsCxrDo": {
|
||||||
"file": "chunk-D7D4PA-g.js",
|
"file": "chunk-EAsCxrDo.js",
|
||||||
"isDynamicEntry": false
|
"isDynamicEntry": false
|
||||||
},
|
},
|
||||||
"dist-CY5vkPpf": {
|
"dist-CY5vkPpf": {
|
||||||
|
|||||||
-13
@@ -1,13 +0,0 @@
|
|||||||
//#region \0rolldown/runtime.js
|
|
||||||
var __defProp = Object.defineProperty;
|
|
||||||
var __exportAll = (all, no_symbols) => {
|
|
||||||
let target = {};
|
|
||||||
for (var name in all) __defProp(target, name, {
|
|
||||||
get: all[name],
|
|
||||||
enumerable: true
|
|
||||||
});
|
|
||||||
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
||||||
return target;
|
|
||||||
};
|
|
||||||
//#endregion
|
|
||||||
export { __exportAll as t };
|
|
||||||
Generated
+28
@@ -19,9 +19,11 @@
|
|||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"axios": "^1.18.1",
|
"axios": "^1.18.1",
|
||||||
"bootstrap": "^5.3.8",
|
"bootstrap": "^5.3.8",
|
||||||
|
"bootstrap-icons": "^1.13.1",
|
||||||
"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",
|
||||||
|
"sweetalert2": "^11.26.25",
|
||||||
"vue": "^3.5.31",
|
"vue": "^3.5.31",
|
||||||
"vue-router": "^5.0.4"
|
"vue-router": "^5.0.4"
|
||||||
},
|
},
|
||||||
@@ -1568,6 +1570,22 @@
|
|||||||
"@popperjs/core": "^2.11.8"
|
"@popperjs/core": "^2.11.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bootstrap-icons": {
|
||||||
|
"version": "1.13.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.13.1.tgz",
|
||||||
|
"integrity": "sha512-ijombt4v6bv5CLeXvRWKy7CuM3TRTuPEuGaGKvTV5cz65rQSY8RQ2JcHt6b90cBBAC7s8fsf2EkQDldzCoXUjw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/twbs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/bootstrap"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"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",
|
||||||
@@ -3589,6 +3607,16 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sweetalert2": {
|
||||||
|
"version": "11.26.25",
|
||||||
|
"resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.26.25.tgz",
|
||||||
|
"integrity": "sha512-+hunCOJdJ6FLj04T9YSLvvZXRjsvIkTeTKP2e4VF8CaBias961BTnWiSFAy7F/CM5eq3QK2Rraoc5Gzftslvkg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/limonte"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tinyglobby": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.17",
|
"version": "0.2.17",
|
||||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
||||||
|
|||||||
@@ -28,9 +28,11 @@
|
|||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"axios": "^1.18.1",
|
"axios": "^1.18.1",
|
||||||
"bootstrap": "^5.3.8",
|
"bootstrap": "^5.3.8",
|
||||||
|
"bootstrap-icons": "^1.13.1",
|
||||||
"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",
|
||||||
|
"sweetalert2": "^11.26.25",
|
||||||
"vue": "^3.5.31",
|
"vue": "^3.5.31",
|
||||||
"vue-router": "^5.0.4"
|
"vue-router": "^5.0.4"
|
||||||
},
|
},
|
||||||
|
|||||||
+2
-1
@@ -21,6 +21,7 @@
|
|||||||
*
|
*
|
||||||
* @type {Array<Object>}
|
* @type {Array<Object>}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
component: 'CNavItem',
|
component: 'CNavItem',
|
||||||
@@ -88,7 +89,7 @@ export default [
|
|||||||
component: 'CNavItem',
|
component: 'CNavItem',
|
||||||
name: 'Colors',
|
name: 'Colors',
|
||||||
to: '/theme/colors',
|
to: '/theme/colors',
|
||||||
icon: 'cil-drop',
|
icon: 'cil-square',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
component: 'CNavItem',
|
component: 'CNavItem',
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import { getToken } from '@/utils/session'
|
||||||
|
|
||||||
|
const api = axios.create({
|
||||||
|
baseURL: 'https://api.radiq.my.id/api',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
api.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
const token = getToken()
|
||||||
|
if (token) {
|
||||||
|
config.headers.Authorization = `Bearer ${token}`
|
||||||
|
}
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
(error) => Promise.reject(error),
|
||||||
|
)
|
||||||
|
|
||||||
|
export default api
|
||||||
@@ -80,6 +80,13 @@ import {
|
|||||||
cilUserFemale,
|
cilUserFemale,
|
||||||
cilUserFollow,
|
cilUserFollow,
|
||||||
cilXCircle,
|
cilXCircle,
|
||||||
|
cilSquare, // new add
|
||||||
|
cilBarcode,
|
||||||
|
cilBookmark,
|
||||||
|
cilAperture,
|
||||||
|
cilCamera,
|
||||||
|
cilDescription,
|
||||||
|
cilCog,
|
||||||
} from '@coreui/icons'
|
} from '@coreui/icons'
|
||||||
|
|
||||||
export const iconsSet = Object.assign(
|
export const iconsSet = Object.assign(
|
||||||
@@ -139,6 +146,13 @@ export const iconsSet = Object.assign(
|
|||||||
cilUserFemale,
|
cilUserFemale,
|
||||||
cilUserFollow,
|
cilUserFollow,
|
||||||
cilXCircle,
|
cilXCircle,
|
||||||
|
cilSquare, // new add
|
||||||
|
cilBarcode,
|
||||||
|
cilBookmark,
|
||||||
|
cilAperture,
|
||||||
|
cilCamera,
|
||||||
|
cilDescription,
|
||||||
|
cilCog,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
cifUs,
|
cifUs,
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<CFooter class="px-4">
|
<CFooter class="px-4">
|
||||||
<div>
|
<div>
|
||||||
<a href="https://coreui.io" target="_blank">CoreUI</a>
|
<a href="https://manjapro.net/" target="_blank">ManjaPro</a>
|
||||||
<span class="ms-1"
|
<span class="ms-1">© {{ new Date().getFullYear() }} bestapps</span>
|
||||||
>© {{ new Date().getFullYear() }} creativeLabs.</span
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="ms-auto">
|
<div class="ms-auto">
|
||||||
<span class="me-1" target="_blank">Powered by</span>
|
<span class="me-1" target="_blank">Powered by</span>
|
||||||
<a href="https://coreui.io/vue">CoreUI for Vue</a>
|
<a href="https://rjn-net.id/">PT. LJN Cikembar</a>
|
||||||
</div>
|
</div>
|
||||||
</CFooter>
|
</CFooter>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -96,11 +96,12 @@ const AppSidebarNav = defineComponent({
|
|||||||
})
|
})
|
||||||
: h('span', { class: 'nav-icon' }, h('span', { class: 'nav-icon-bullet' })),
|
: h('span', { class: 'nav-icon' }, h('span', { class: 'nav-icon-bullet' })),
|
||||||
item.name,
|
item.name,
|
||||||
item.external && h(resolveComponent('CIcon'), {
|
item.external &&
|
||||||
class: 'ms-2',
|
h(resolveComponent('CIcon'), {
|
||||||
name: 'cil-external-link',
|
class: 'ms-2',
|
||||||
size: 'sm'
|
name: 'cil-external-link',
|
||||||
}),
|
size: 'sm',
|
||||||
|
}),
|
||||||
item.badge &&
|
item.badge &&
|
||||||
h(
|
h(
|
||||||
CBadge,
|
CBadge,
|
||||||
|
|||||||
@@ -0,0 +1,247 @@
|
|||||||
|
<template>
|
||||||
|
<CModal :visible="visible" alignment="center" @close="handleClose" size="lg">
|
||||||
|
<CModalHeader dismiss>
|
||||||
|
<CModalTitle>Assign Material</CModalTitle>
|
||||||
|
</CModalHeader>
|
||||||
|
|
||||||
|
<CModalBody>
|
||||||
|
<CAlert v-if="validationError" color="danger" class="py-2 mb-3">
|
||||||
|
{{ validationError }}
|
||||||
|
</CAlert>
|
||||||
|
|
||||||
|
<CCard class="mb-4">
|
||||||
|
<CCardHeader>
|
||||||
|
<strong>Scan QR Code / Barcode</strong>
|
||||||
|
</CCardHeader>
|
||||||
|
<CCardBody>
|
||||||
|
<div class="mb-3">
|
||||||
|
<CFormLabel>Barcode</CFormLabel>
|
||||||
|
<CFormInput v-model="form.barcode_id" placeholder="Scan atau ketik barcode" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3 d-flex gap-2">
|
||||||
|
<CButton color="primary" @click="startScanner" :disabled="scannerRunning">
|
||||||
|
Buka Kamera
|
||||||
|
</CButton>
|
||||||
|
<CButton color="secondary" @click="stopScanner" :disabled="!scannerRunning">
|
||||||
|
Tutup Kamera
|
||||||
|
</CButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="scannerRunning" id="scanner-container" class="scanner-preview mb-3"></div>
|
||||||
|
|
||||||
|
<div class="text-body-secondary small">Status : {{ scannerStatus }}</div>
|
||||||
|
</CCardBody>
|
||||||
|
</CCard>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<CFormLabel>User ID</CFormLabel>
|
||||||
|
<CFormInput v-model="form.user_id" placeholder="Masukan User ID" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<CFormLabel>Nama Material</CFormLabel>
|
||||||
|
<CFormInput v-model="form.material_name" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<CFormLabel>Type</CFormLabel>
|
||||||
|
<CFormSelect v-model="form.type">
|
||||||
|
<option value="serialized">Serialized</option>
|
||||||
|
<option value="consumable">Consumable</option>
|
||||||
|
</CFormSelect>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3" v-if="form.type === 'serialized'">
|
||||||
|
<CFormLabel>Serial Number</CFormLabel>
|
||||||
|
<CFormInput v-model="form.serial_number" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CRow>
|
||||||
|
<CCol>
|
||||||
|
<CFormLabel>Qty</CFormLabel>
|
||||||
|
<CFormInput type="number" v-model="form.qty" />
|
||||||
|
</CCol>
|
||||||
|
<CCol>
|
||||||
|
<CFormLabel>Unit</CFormLabel>
|
||||||
|
<CFormInput v-model="form.unit" />
|
||||||
|
</CCol>
|
||||||
|
</CRow>
|
||||||
|
</CModalBody>
|
||||||
|
|
||||||
|
<CModalFooter>
|
||||||
|
<CButton color="secondary" @click="handleClose" :disabled="saving">Batal</CButton>
|
||||||
|
<CButton color="primary" @click="handleSave" :disabled="saving">
|
||||||
|
<span v-if="saving">Menyimpan...</span>
|
||||||
|
<span v-else>Simpan</span>
|
||||||
|
</CButton>
|
||||||
|
</CModalFooter>
|
||||||
|
</CModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, nextTick, watch, onMounted, onBeforeUnmount } from 'vue'
|
||||||
|
import Swal from 'sweetalert2'
|
||||||
|
import { showSuccessr, showError } from '@/utils/swal'
|
||||||
|
|
||||||
|
const props = defineProps({ visible: Boolean, form: Object, saving: Boolean })
|
||||||
|
const emit = defineEmits(['close', 'save'])
|
||||||
|
|
||||||
|
const scannerStatus = ref('Siap Scan')
|
||||||
|
const scannerRunning = ref(false)
|
||||||
|
const html5QrCode = ref(null)
|
||||||
|
|
||||||
|
let scriptLoaded = false
|
||||||
|
|
||||||
|
function loadScannerLibrary() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (window.Html5Qrcode) {
|
||||||
|
scriptLoaded = true
|
||||||
|
resolve()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (scriptLoaded) {
|
||||||
|
resolve()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const script = document.createElement('script')
|
||||||
|
script.src = 'https://unpkg.com/html5-qrcode'
|
||||||
|
script.async = true
|
||||||
|
script.onload = () => {
|
||||||
|
scriptLoaded = true
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
script.onerror = () => reject(new Error('Gagal memuat library scanner'))
|
||||||
|
document.head.appendChild(script)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startScanner() {
|
||||||
|
try {
|
||||||
|
await loadScannerLibrary()
|
||||||
|
scannerStatus.value = 'Meminta akses kamera...'
|
||||||
|
scannerRunning.value = true
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
html5QrCode.value = new Html5Qrcode('scanner-container')
|
||||||
|
|
||||||
|
await html5QrCode.value.start(
|
||||||
|
{ facingMode: 'environment' },
|
||||||
|
{ fps: 10, qrbox: { width: 250, height: 250 } },
|
||||||
|
onScanSuccess,
|
||||||
|
)
|
||||||
|
scannerStatus.value = 'Kamera menyala, arahkan ke QR Code'
|
||||||
|
} catch (err) {
|
||||||
|
scannerRunning.value = false
|
||||||
|
const msg = err.toString()
|
||||||
|
if (msg.includes('NotAllowedError')) {
|
||||||
|
scannerStatus.value = 'Izin kamera ditolak'
|
||||||
|
} else if (msg.includes('NotFoundError')) {
|
||||||
|
scannerStatus.value = 'Kamera tidak ditemukan'
|
||||||
|
} else {
|
||||||
|
scannerStatus.value = 'Gagal membuka kamera'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onScanSuccess(decodedText) {
|
||||||
|
props.form.barcode_id = decodedText
|
||||||
|
scannerStatus.value = 'QR Code berhasil dipindai.'
|
||||||
|
validationError.value = ''
|
||||||
|
stopScanner()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stopScanner() {
|
||||||
|
try {
|
||||||
|
if (html5QrCode.value) {
|
||||||
|
await html5QrCode.value.stop()
|
||||||
|
await html5QrCode.value.clear()
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Abaikan error saat menghentikan scanner
|
||||||
|
} finally {
|
||||||
|
html5QrCode.value = null
|
||||||
|
scannerRunning.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const validationError = ref('')
|
||||||
|
|
||||||
|
const defaultForm = {
|
||||||
|
user_id: null,
|
||||||
|
barcode_id: '',
|
||||||
|
material_name: '',
|
||||||
|
type: 'serialized',
|
||||||
|
serial_number: '',
|
||||||
|
qty: 1,
|
||||||
|
unit: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
Object.assign(props.form, defaultForm)
|
||||||
|
scannerStatus.value = 'Siap Scan'
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSave() {
|
||||||
|
if (!props.form.barcode_id) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Gagal',
|
||||||
|
text: 'Barcode harus diisi',
|
||||||
|
confirmButtonText: 'Tutup',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!props.form.user_id) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Gagal',
|
||||||
|
text: 'User ID harus diisi',
|
||||||
|
confirmButtonText: 'Tutup',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
emit('save')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resetState() {
|
||||||
|
resetForm()
|
||||||
|
await stopScanner()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleClose() {
|
||||||
|
await resetState()
|
||||||
|
emit('close')
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
(val) => {
|
||||||
|
if (!val) {
|
||||||
|
resetForm()
|
||||||
|
stopScanner()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadScannerLibrary()
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
stopScanner()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.scanner-preview {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.scanner-preview :deep(video) {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<template>
|
||||||
|
<CModal :visible="visible" alignment="center" size="lg" @close="$emit('close')">
|
||||||
|
<CModalHeader dismiss>
|
||||||
|
<CModalTitle>Detail Material</CModalTitle>
|
||||||
|
</CModalHeader>
|
||||||
|
|
||||||
|
<CModalBody>
|
||||||
|
<CTable>
|
||||||
|
<CTableBody>
|
||||||
|
<CTableRow>
|
||||||
|
<CTableHeaderCell style="width: 35%">Nama Material</CTableHeaderCell>
|
||||||
|
<CTableDataCell>{{ item.material_name }}</CTableDataCell>
|
||||||
|
</CTableRow>
|
||||||
|
|
||||||
|
<CTableRow>
|
||||||
|
<CTableHeaderCell>Type</CTableHeaderCell>
|
||||||
|
<CTableDataCell>
|
||||||
|
<CBadge :color="item.type === 'serialized' ? 'success' : 'warning'">
|
||||||
|
{{ item.type }}
|
||||||
|
</CBadge>
|
||||||
|
</CTableDataCell>
|
||||||
|
</CTableRow>
|
||||||
|
|
||||||
|
<!-- serialized: barcode & serial number -->
|
||||||
|
<template v-if="item.type === 'serialized'">
|
||||||
|
<CTableRow>
|
||||||
|
<CTableHeaderCell>Barcode</CTableHeaderCell>
|
||||||
|
<CTableDataCell>{{ item.barcode_id }}</CTableDataCell>
|
||||||
|
</CTableRow>
|
||||||
|
<CTableRow>
|
||||||
|
<CTableHeaderCell>Serial Number</CTableHeaderCell>
|
||||||
|
<CTableDataCell>{{ item.serial_number || '-' }}</CTableDataCell>
|
||||||
|
</CTableRow>
|
||||||
|
<CTableRow>
|
||||||
|
<CTableHeaderCell>Quantity</CTableHeaderCell>
|
||||||
|
<CTableDataCell>1</CTableDataCell>
|
||||||
|
</CTableRow>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- consumable: received, used, remaining -->
|
||||||
|
<template v-if="item.type === 'consumable'">
|
||||||
|
<CTableRow>
|
||||||
|
<CTableHeaderCell>Received</CTableHeaderCell>
|
||||||
|
<CTableDataCell>{{ item.qty?.received || '-' }}</CTableDataCell>
|
||||||
|
</CTableRow>
|
||||||
|
<CTableRow>
|
||||||
|
<CTableHeaderCell>Used</CTableHeaderCell>
|
||||||
|
<CTableDataCell>{{ item.qty?.used || '-' }}</CTableDataCell>
|
||||||
|
</CTableRow>
|
||||||
|
<CTableRow>
|
||||||
|
<CTableHeaderCell>Remaining</CTableHeaderCell>
|
||||||
|
<CTableDataCell>{{ item.qty?.remaining || '-' }}</CTableDataCell>
|
||||||
|
</CTableRow>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<CTableRow v-if="item.type === 'consumable'">
|
||||||
|
<CTableHeaderCell>Unit</CTableHeaderCell>
|
||||||
|
<CTableDataCell>{{ item.qty?.unit || item.unit || '-' }}</CTableDataCell>
|
||||||
|
</CTableRow>
|
||||||
|
|
||||||
|
<CTableRow>
|
||||||
|
<CTableHeaderCell>Status</CTableHeaderCell>
|
||||||
|
<CTableDataCell>
|
||||||
|
<CBadge
|
||||||
|
:color="
|
||||||
|
item.status === 'Assigned'
|
||||||
|
? 'success'
|
||||||
|
: item.status === 'Returned'
|
||||||
|
? 'secondary'
|
||||||
|
: 'info'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ item.status }}
|
||||||
|
</CBadge>
|
||||||
|
</CTableDataCell>
|
||||||
|
</CTableRow>
|
||||||
|
</CTableBody>
|
||||||
|
</CTable>
|
||||||
|
</CModalBody>
|
||||||
|
|
||||||
|
<CModalFooter>
|
||||||
|
<CButton color="secondary" @click="$emit('close')">Tutup</CButton>
|
||||||
|
</CModalFooter>
|
||||||
|
</CModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
visible: Boolean,
|
||||||
|
item: Object,
|
||||||
|
})
|
||||||
|
defineEmits(['close'])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.table) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
:deep(.table td),
|
||||||
|
:deep(.table th) {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="histories.length" class="timeline">
|
||||||
|
<div v-for="(item, index) in histories" :key="index" class="timeline-item">
|
||||||
|
<div class="timeline-icon">
|
||||||
|
<CIcon :icon="getHistoryIcon(item.action)" />
|
||||||
|
</div>
|
||||||
|
<div class="timeline-content">
|
||||||
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<CBadge :color="getHistoryColor(item.action)">
|
||||||
|
{{ item.action }}
|
||||||
|
</CBadge>
|
||||||
|
<small class="text-body-secondary">{{ item.created_at }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2">{{ item.description }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="text-center py-5">
|
||||||
|
<h6>Belum Ada Riwayat Material</h6>
|
||||||
|
<p class="text-body-secondary">History muncul setelah assign/transfer/return.</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { cilUserPlus, cilSwapHorizontal, cilArrowCircleLeft } from '@coreui/icons'
|
||||||
|
|
||||||
|
defineProps({ histories: Array })
|
||||||
|
|
||||||
|
function getHistoryColor(action) {
|
||||||
|
switch (action?.toLowerCase()) {
|
||||||
|
case 'assigned':
|
||||||
|
return 'success'
|
||||||
|
case 'transfer':
|
||||||
|
return 'info'
|
||||||
|
case 'returned':
|
||||||
|
return 'secondary'
|
||||||
|
default:
|
||||||
|
return 'warning'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHistoryIcon(action) {
|
||||||
|
switch (action?.toLowerCase()) {
|
||||||
|
case 'assigned':
|
||||||
|
return cilUserPlus
|
||||||
|
case 'transfer':
|
||||||
|
return cilSwapHorizontal
|
||||||
|
case 'returned':
|
||||||
|
return cilArrowCircleLeft
|
||||||
|
default:
|
||||||
|
return cilUserPlus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.timeline {
|
||||||
|
border-left: 2px solid #e9ecef;
|
||||||
|
margin-left: 10px;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
.timeline-item {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.timeline-icon {
|
||||||
|
position: absolute;
|
||||||
|
left: -32px;
|
||||||
|
top: 0;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #fff;
|
||||||
|
border: 2px solid #adb5bd;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #6c757d;
|
||||||
|
}
|
||||||
|
.timeline-content {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="loading" class="text-center py-5">
|
||||||
|
<CSpinner color="primary" class="mb-3" />
|
||||||
|
<p class="text-body-secondary">Memuat data material...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="data.length === 0" class="text-center py-5">
|
||||||
|
<h6 class="text-body-secondary">Tidak ada data material</h6>
|
||||||
|
<p class="text-body-secondary small">Belum ada material yang terdaftar.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<CTable>
|
||||||
|
<CTableHead>
|
||||||
|
<CTableRow>
|
||||||
|
<CTableHeaderCell scope="col" style="width: 50px">No</CTableHeaderCell>
|
||||||
|
<CTableHeaderCell>Barcode</CTableHeaderCell>
|
||||||
|
<CTableHeaderCell>Material</CTableHeaderCell>
|
||||||
|
<CTableHeaderCell>Type</CTableHeaderCell>
|
||||||
|
<CTableHeaderCell class="text-center">Aksi</CTableHeaderCell>
|
||||||
|
</CTableRow>
|
||||||
|
</CTableHead>
|
||||||
|
|
||||||
|
<CTableBody>
|
||||||
|
<CTableRow v-for="(item, index) in data" :key="item.barcode_id">
|
||||||
|
<CTableHeaderCell scope="row">{{
|
||||||
|
(currentPage - 1) * perPage + index + 1
|
||||||
|
}}</CTableHeaderCell>
|
||||||
|
<CTableDataCell>{{ item.barcode_id }}</CTableDataCell>
|
||||||
|
<CTableDataCell>{{ item.material_name }}</CTableDataCell>
|
||||||
|
<CTableDataCell>
|
||||||
|
<CBadge :color="item.type === 'serialized' ? 'success' : 'primary'">
|
||||||
|
{{ item.type }}
|
||||||
|
</CBadge>
|
||||||
|
</CTableDataCell>
|
||||||
|
<CTableDataCell class="text-center">
|
||||||
|
<CButton size="sm" color="info" class="me-1" @click="$emit('detail', item)"
|
||||||
|
>Detail</CButton
|
||||||
|
>
|
||||||
|
<CButton size="sm" color="warning" class="me-1" @click="$emit('transfer', item)"
|
||||||
|
>Transfer</CButton
|
||||||
|
>
|
||||||
|
<CButton size="sm" color="danger" @click="$emit('return', item)">Return</CButton>
|
||||||
|
</CTableDataCell>
|
||||||
|
</CTableRow>
|
||||||
|
</CTableBody>
|
||||||
|
</CTable>
|
||||||
|
|
||||||
|
<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
|
||||||
|
style="width: auto"
|
||||||
|
:value="perPage"
|
||||||
|
@change="$emit('update:perPage', Number($event.target.value))"
|
||||||
|
>
|
||||||
|
<option :value="15">15</option>
|
||||||
|
<option :value="25">25</option>
|
||||||
|
<option :value="50">50</option>
|
||||||
|
<option :value="100">100</option>
|
||||||
|
</CFormSelect>
|
||||||
|
<span> dari {{ totalItems }} data </span>
|
||||||
|
</div>
|
||||||
|
<CPagination class="mb-0" v-if="totalPages > 1">
|
||||||
|
<CPaginationItem :disabled="currentPage == 1" @click="$emit('page', currentPage - 1)">
|
||||||
|
Previous
|
||||||
|
</CPaginationItem>
|
||||||
|
|
||||||
|
<CPaginationItem
|
||||||
|
v-for="page in totalPages"
|
||||||
|
:key="page"
|
||||||
|
:active="page == currentPage"
|
||||||
|
@click="$emit('page', page)"
|
||||||
|
>
|
||||||
|
{{ page }}
|
||||||
|
</CPaginationItem>
|
||||||
|
|
||||||
|
<CPaginationItem
|
||||||
|
:disabled="currentPage == totalPages"
|
||||||
|
@click="$emit('page', currentPage + 1)"
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</CPaginationItem>
|
||||||
|
</CPagination>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
data: Array,
|
||||||
|
totalPages: Number,
|
||||||
|
currentPage: Number,
|
||||||
|
perPage: Number,
|
||||||
|
totalItems: Number,
|
||||||
|
loading: Boolean,
|
||||||
|
})
|
||||||
|
defineEmits(['detail', 'transfer', 'return', 'page', 'update:perPage'])
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<template>
|
||||||
|
<CModal :visible="visible" alignment="center" @close="$emit('close')">
|
||||||
|
<CModalHeader dismiss>
|
||||||
|
<CModalTitle>Return Material</CModalTitle>
|
||||||
|
</CModalHeader>
|
||||||
|
|
||||||
|
<CModalBody>
|
||||||
|
<div class="mb-3">
|
||||||
|
<CFormLabel>Barcode</CFormLabel>
|
||||||
|
<CFormInput v-model="form.barcode_id" readonly />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<CFormLabel>User ID</CFormLabel>
|
||||||
|
<CFormInput v-model="form.user_id" readonly />
|
||||||
|
</div>
|
||||||
|
</CModalBody>
|
||||||
|
|
||||||
|
<CModalFooter>
|
||||||
|
<CButton color="secondary" @click="$emit('close')" :disabled="saving">Batal</CButton>
|
||||||
|
<CButton color="danger" @click="$emit('save')" :disabled="saving">
|
||||||
|
<span v-if="saving">Memproses...</span>
|
||||||
|
<span v-else>Return</span>
|
||||||
|
</CButton>
|
||||||
|
</CModalFooter>
|
||||||
|
</CModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
visible: Boolean,
|
||||||
|
form: Object,
|
||||||
|
saving: Boolean,
|
||||||
|
})
|
||||||
|
defineEmits(['close', 'save'])
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<template>
|
||||||
|
<CModal :visible="visible" alignment="center" @close="$emit('close')">
|
||||||
|
<CModalHeader dismiss>
|
||||||
|
<CModalTitle>Transfer Material</CModalTitle>
|
||||||
|
</CModalHeader>
|
||||||
|
|
||||||
|
<CModalBody>
|
||||||
|
<div class="mb-3">
|
||||||
|
<CFormLabel>Barcode</CFormLabel>
|
||||||
|
<CFormInput v-model="form.barcode_id" readonly />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<CFormLabel>User ID Asal</CFormLabel>
|
||||||
|
<CFormInput v-model="form.from_user_id" readonly />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<CFormLabel>User ID Tujuan</CFormLabel>
|
||||||
|
<CFormInput v-model="form.to_user_id" placeholder="Masukkan User ID Tujuan" />
|
||||||
|
</div>
|
||||||
|
</CModalBody>
|
||||||
|
|
||||||
|
<CModalFooter>
|
||||||
|
<CButton color="secondary" @click="$emit('close')" :disabled="saving">Batal</CButton>
|
||||||
|
<CButton color="warning" @click="$emit('save')" :disabled="saving">
|
||||||
|
<span v-if="saving">Transfer...</span>
|
||||||
|
<span v-else>Transfer</span>
|
||||||
|
</CButton>
|
||||||
|
</CModalFooter>
|
||||||
|
</CModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
visible: Boolean,
|
||||||
|
form: Object,
|
||||||
|
saving: Boolean,
|
||||||
|
})
|
||||||
|
defineEmits(['close', 'save'])
|
||||||
|
</script>
|
||||||
+11
@@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
|
import { setToken, hasToken } from '@/utils/session'
|
||||||
|
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
@@ -31,6 +32,9 @@ import DocsComponents from '@/components/DocsComponents'
|
|||||||
import DocsExample from '@/components/DocsExample'
|
import DocsExample from '@/components/DocsExample'
|
||||||
import DocsIcons from '@/components/DocsIcons'
|
import DocsIcons from '@/components/DocsIcons'
|
||||||
|
|
||||||
|
import { CButton } from '@coreui/vue'
|
||||||
|
import { CBadge } from '@coreui/vue'
|
||||||
|
|
||||||
// Create Vue application instance
|
// Create Vue application instance
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
@@ -39,6 +43,8 @@ app.use(createPinia()) // State management
|
|||||||
app.use(router) // Router for SPA navigation
|
app.use(router) // Router for SPA navigation
|
||||||
app.use(CoreuiVue) // CoreUI component library
|
app.use(CoreuiVue) // CoreUI component library
|
||||||
|
|
||||||
|
app.component('CButton', CButton)
|
||||||
|
|
||||||
// Provide icons globally
|
// Provide icons globally
|
||||||
app.provide('icons', icons)
|
app.provide('icons', icons)
|
||||||
|
|
||||||
@@ -48,5 +54,10 @@ app.component('DocsComponents', DocsComponents)
|
|||||||
app.component('DocsExample', DocsExample)
|
app.component('DocsExample', DocsExample)
|
||||||
app.component('DocsIcons', DocsIcons)
|
app.component('DocsIcons', DocsIcons)
|
||||||
|
|
||||||
|
// TODO: Hapus setelah fitur Login tersedia — token sementara untuk development
|
||||||
|
if (!hasToken()) {
|
||||||
|
setToken('UU7HVn7ECHe7MvqMTeTeEpoFdBA2wc0NHQX29WyG91fc6982')
|
||||||
|
}
|
||||||
|
|
||||||
// Mount application to DOM
|
// Mount application to DOM
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import api from '@/api/axios'
|
||||||
|
|
||||||
|
export const materialService = {
|
||||||
|
fetchAll(params = {}) {
|
||||||
|
return api.get('/materials', { params })
|
||||||
|
},
|
||||||
|
fetchByUser(userId) {
|
||||||
|
return api.get(`/materials/user/${userId}`)
|
||||||
|
},
|
||||||
|
fetchHistory(barcodeId) {
|
||||||
|
return api.get(`/materials/history/${barcodeId}`)
|
||||||
|
},
|
||||||
|
assign(payload) {
|
||||||
|
return api.post('/materials/assign', payload)
|
||||||
|
},
|
||||||
|
transfer(payload) {
|
||||||
|
return api.post('/materials/transfer', payload)
|
||||||
|
},
|
||||||
|
returnMaterial(payload) {
|
||||||
|
return api.post('/materials/return', payload)
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { materialService } from '@/services/materialService'
|
||||||
|
|
||||||
|
export const useMaterialStore = defineStore('material', {
|
||||||
|
state: () => ({
|
||||||
|
materials: [],
|
||||||
|
histories: [],
|
||||||
|
loading: false,
|
||||||
|
error: '',
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
async fetchAll(params = {}) {
|
||||||
|
this.loading = true
|
||||||
|
this.error = ''
|
||||||
|
try {
|
||||||
|
const res = await materialService.fetchAll({ per_page: 1000, ...params })
|
||||||
|
const resData = res.data
|
||||||
|
|
||||||
|
if (Array.isArray(resData)) {
|
||||||
|
this.materials = resData
|
||||||
|
} else if (resData && Array.isArray(resData.data)) {
|
||||||
|
this.materials = resData.data
|
||||||
|
} else {
|
||||||
|
console.warn('fetchAll: unexpected response', resData)
|
||||||
|
this.materials = []
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.error = err.response?.data?.message || 'Gagal mengambil data material.'
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async fetchByUser(userId) {
|
||||||
|
if (!userId) return
|
||||||
|
this.loading = true
|
||||||
|
this.error = ''
|
||||||
|
try {
|
||||||
|
const res = await materialService.fetchByUser(userId)
|
||||||
|
this.materials = res.data.data || []
|
||||||
|
} catch (err) {
|
||||||
|
this.error = err.response?.data?.message || 'Gagal mengambil data material.'
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchHistory(barcodeId) {
|
||||||
|
if (!barcodeId) return
|
||||||
|
this.loading = true
|
||||||
|
this.error = ''
|
||||||
|
try {
|
||||||
|
const res = await materialService.fetchHistory(barcodeId)
|
||||||
|
this.histories = res.data.data || []
|
||||||
|
} catch (err) {
|
||||||
|
this.error = err.response?.data?.message || 'Gagal mengambil history material.'
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
+74
-1
@@ -1,5 +1,9 @@
|
|||||||
@use '@coreui/coreui/scss/coreui' as * with (
|
@use '@coreui/coreui/scss/coreui' as * with (
|
||||||
$enable-deprecation-messages: false
|
$enable-deprecation-messages: false,
|
||||||
|
$primary: #f28500,
|
||||||
|
$primary-text-emphasis: #9e5500,
|
||||||
|
$primary-bg-subtle: #fde6cc,
|
||||||
|
$primary-border-subtle: #f9b364
|
||||||
);
|
);
|
||||||
@use '@coreui/chartjs/scss/coreui-chartjs';
|
@use '@coreui/chartjs/scss/coreui-chartjs';
|
||||||
@use 'vendors/simplebar';
|
@use 'vendors/simplebar';
|
||||||
@@ -8,6 +12,13 @@ body {
|
|||||||
background-color: var(--cui-tertiary-bg);
|
background-color: var(--cui-tertiary-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--cui-primary: #f28500;
|
||||||
|
--cui-primary-rgb: 242, 133, 0;
|
||||||
|
--cui-link-color: #f28500;
|
||||||
|
--cui-link-color-rgb: 242, 133, 0;
|
||||||
|
}
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
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);
|
||||||
@@ -129,3 +140,65 @@ body {
|
|||||||
--cui-tertiary-bg: #16171d;
|
--cui-tertiary-bg: #16171d;
|
||||||
--cui-border-color: #22242d;
|
--cui-border-color: #22242d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// font bold untuk toast edit
|
||||||
|
.swal-title-bold {
|
||||||
|
font-weight: 700 !important; /* Bold */
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
//materials
|
||||||
|
.timeline {
|
||||||
|
position: relative;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 14px;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 2px;
|
||||||
|
background: #d8dbe0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-icon {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 2px solid #d8dbe0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-content {
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 15px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
transition: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table tbody tr {
|
||||||
|
transition: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table tbody tr:hover {
|
||||||
|
transform: scale(1.01);
|
||||||
|
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
const TOKEN_KEY = 'UU7HVn7ECHe7MvqMTeTeEpoFdBA2wc0NHQX29WyG91fc6982'
|
||||||
|
|
||||||
|
export function setToken(token) {
|
||||||
|
sessionStorage.setItem(TOKEN_KEY, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getToken() {
|
||||||
|
return sessionStorage.getItem(TOKEN_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeToken() {
|
||||||
|
sessionStorage.removeItem(TOKEN_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasToken() {
|
||||||
|
return !!sessionStorage.getItem(TOKEN_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearSession() {
|
||||||
|
sessionStorage.clear()
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import Swal from 'sweetalert2'
|
||||||
|
|
||||||
|
export function showConfirm(title = 'Konfirmasi', text = 'Apakah Anda yakin?') {
|
||||||
|
return Swal.fire({
|
||||||
|
title,
|
||||||
|
text,
|
||||||
|
icon: 'question',
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonColor: '#dc3545',
|
||||||
|
confirmButtonText: 'Ya, hapus!',
|
||||||
|
cancelButtonText: 'Batal',
|
||||||
|
theme: 'auto',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function showSuccess(message = 'Data berhasil disimpan') {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Berhasil',
|
||||||
|
text: message,
|
||||||
|
showConfirmButton: false,
|
||||||
|
timer: 2000,
|
||||||
|
timerProgressBar: true,
|
||||||
|
theme: 'auto',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function showSuccessr() {
|
||||||
|
Swal.fire({
|
||||||
|
position: 'top-end',
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Data Berhasil Di Simpan',
|
||||||
|
showConfirmButton: false,
|
||||||
|
timer: 1500,
|
||||||
|
theme: 'auto',
|
||||||
|
toast: true,
|
||||||
|
width: '350px',
|
||||||
|
padding: '10px 18px',
|
||||||
|
customClass: {
|
||||||
|
title: 'swal-title-bold',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function showError() {
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'error',
|
||||||
|
title: 'Gagal',
|
||||||
|
text: 'Terjadi Kesalahan',
|
||||||
|
confirmButtonText: 'Tutup',
|
||||||
|
theme: 'auto',
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -142,14 +142,11 @@ async function saveTicket() {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (isEdit.value && editingIndex.value >= 0) {
|
if (isEdit.value && editingIndex.value >= 0) {
|
||||||
const res = await fetch(
|
const res = await fetch(`https://api.radiq.my.id/api/tickets/${form.ticket_no}`, {
|
||||||
`https://api.radiq.my.id/api/tickets/${form.ticket_no}`,
|
method: 'PUT',
|
||||||
{
|
headers: { Authorization: token, 'Content-Type': 'application/json' },
|
||||||
method: 'PUT',
|
body: JSON.stringify(body),
|
||||||
headers: { Authorization: token, 'Content-Type': 'application/json' },
|
})
|
||||||
body: JSON.stringify(body),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if (res.ok) store.updateTicket(editingIndex.value, form)
|
if (res.ok) store.updateTicket(editingIndex.value, form)
|
||||||
} else {
|
} else {
|
||||||
const res = await fetch('https://api.radiq.my.id/api/tickets', {
|
const res = await fetch('https://api.radiq.my.id/api/tickets', {
|
||||||
@@ -227,7 +224,7 @@ function isPending(item) {
|
|||||||
<CCard class="mb-4">
|
<CCard class="mb-4">
|
||||||
<CCardHeader>
|
<CCardHeader>
|
||||||
<strong>Ticket</strong>
|
<strong>Ticket</strong>
|
||||||
<CButton color="primary" size="sm" class="float-end" @click="openAddModal">
|
<CButton color="primary" class="float-end" @click="openAddModal">
|
||||||
+ Tambah Ticket
|
+ Tambah Ticket
|
||||||
</CButton>
|
</CButton>
|
||||||
</CCardHeader>
|
</CCardHeader>
|
||||||
|
|||||||
@@ -1,32 +1,21 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import axios from 'axios'
|
|
||||||
import { ref, reactive, computed, onMounted } from 'vue'
|
import { ref, reactive, computed, onMounted } from 'vue'
|
||||||
import { useTicketIncidentStore } from '@/stores/ticketIncident.js'
|
import { useTicketIncidentStore } from '@/stores/ticketIncident.js'
|
||||||
import { formatTanggal } from '@/utils/tglindo.js'
|
import { formatTanggal } from '@/utils/tglindo.js'
|
||||||
|
import { showConfirm, showSuccess, showSuccessr, showError } from '@/utils/swal.js'
|
||||||
|
import { CButton } from '@coreui/vue'
|
||||||
|
import api from '@/api/axios'
|
||||||
|
|
||||||
const store = useTicketIncidentStore()
|
const store = useTicketIncidentStore()
|
||||||
const loading = ref(false)
|
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() {
|
async function fetchTicketIncidentTypes() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const response = await api.get('')
|
const response = await api.get('/ticket-incident-types')
|
||||||
store.setItems(response.data.data || [])
|
store.setItems(response.data.data || [])
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to fetch:', err)
|
showError('Gagal memuat data: ' + err.message)
|
||||||
errorMsg.value = 'Gagal memuat data: ' + err.message
|
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
@@ -35,15 +24,11 @@ async function fetchTicketIncidentTypes() {
|
|||||||
onMounted(fetchTicketIncidentTypes)
|
onMounted(fetchTicketIncidentTypes)
|
||||||
|
|
||||||
const modal = ref(false)
|
const modal = ref(false)
|
||||||
const deleteModal = ref(false)
|
|
||||||
const detailModal = ref(false)
|
const detailModal = ref(false)
|
||||||
const editingIndex = ref(-1)
|
const editingIndex = ref(-1)
|
||||||
const isEdit = ref(false)
|
const isEdit = ref(false)
|
||||||
const deleteIndex = ref(-1)
|
const deleteIndex = ref(-1)
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
const errorMsg = ref('')
|
|
||||||
const successMsg = ref('')
|
|
||||||
let successTimer = null
|
|
||||||
|
|
||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
const perPage = ref(10)
|
const perPage = ref(10)
|
||||||
@@ -64,14 +49,6 @@ function changePerPage(val) {
|
|||||||
currentPage.value = 1
|
currentPage.value = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
function showSuccess(msg) {
|
|
||||||
successMsg.value = msg
|
|
||||||
if (successTimer) clearTimeout(successTimer)
|
|
||||||
successTimer = setTimeout(() => {
|
|
||||||
successMsg.value = ''
|
|
||||||
}, 3000)
|
|
||||||
}
|
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
name: '',
|
name: '',
|
||||||
is_active: '1',
|
is_active: '1',
|
||||||
@@ -107,29 +84,33 @@ function openDetail(item) {
|
|||||||
getTicketIncidentType(item.id)
|
getTicketIncidentType(item.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
function openDeleteModal(item) {
|
async function openDeleteModal(item) {
|
||||||
const idx = store.items.findIndex((t) => t.id === item.id)
|
const idx = store.items.findIndex((t) => t.id === item.id)
|
||||||
if (idx === -1) return
|
if (idx === -1) return
|
||||||
|
const result = await showConfirm(
|
||||||
|
'Konfirmasi Hapus',
|
||||||
|
'Apakah Anda yakin ingin menghapus ticket incident ini?',
|
||||||
|
)
|
||||||
|
if (!result.isConfirmed) return
|
||||||
deleteIndex.value = idx
|
deleteIndex.value = idx
|
||||||
deleteModal.value = true
|
await deleteTicketIncidentType(item.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createTicketIncidentType() {
|
async function createTicketIncidentType() {
|
||||||
errorMsg.value = ''
|
|
||||||
saving.value = true
|
saving.value = true
|
||||||
const body = {
|
const body = {
|
||||||
name: form.name,
|
name: form.name,
|
||||||
is_active: form.is_active === '1',
|
is_active: form.is_active === '1',
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const response = await api.post('', body)
|
const response = await api.post('/ticket-incident-types', body)
|
||||||
store.addItem(response.data.data)
|
store.addItem(response.data.data)
|
||||||
modal.value = false
|
modal.value = false
|
||||||
currentPage.value = totalPages.value
|
currentPage.value = totalPages.value
|
||||||
showSuccess('Data berhasil disimpan')
|
showSuccess('Data berhasil disimpan')
|
||||||
fetchTicketIncidentTypes() // Refresh data after creation
|
fetchTicketIncidentTypes()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errorMsg.value = err.response?.data?.message || 'Gagal menyimpan data: ' + err.message
|
showError(err.response?.data?.message || 'Gagal menyimpan data: ' + err.message)
|
||||||
} finally {
|
} finally {
|
||||||
saving.value = false
|
saving.value = false
|
||||||
}
|
}
|
||||||
@@ -137,30 +118,28 @@ async function createTicketIncidentType() {
|
|||||||
|
|
||||||
async function getTicketIncidentType(id) {
|
async function getTicketIncidentType(id) {
|
||||||
try {
|
try {
|
||||||
const response = await api.get(`/${id}`)
|
const response = await api.get(`/ticket-incident-types/${id}`)
|
||||||
detailItem.value = response.data.data
|
detailItem.value = response.data.data
|
||||||
detailModal.value = true
|
detailModal.value = true
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to fetch detail:', err)
|
showError('Gagal memuat detail data: ' + err.message)
|
||||||
errorMsg.value = 'Gagal memuat detail data: ' + err.message
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateTicketIncidentType(id) {
|
async function updateTicketIncidentType(id) {
|
||||||
errorMsg.value = ''
|
|
||||||
saving.value = true
|
saving.value = true
|
||||||
const body = {
|
const body = {
|
||||||
name: form.name,
|
name: form.name,
|
||||||
is_active: form.is_active === '1',
|
is_active: form.is_active === '1',
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const response = await api.put(`/${id}`, body)
|
const response = await api.put(`/ticket-incident-types/${id}`, body)
|
||||||
store.updateItem(editingIndex.value, response.data.data)
|
store.updateItem(editingIndex.value, response.data.data)
|
||||||
modal.value = false
|
modal.value = false
|
||||||
showSuccess('Data berhasil diupdate')
|
showSuccessr('Data Berhasil Di Simpan')
|
||||||
fetchTicketIncidentTypes() // Refresh data after update
|
fetchTicketIncidentTypes()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errorMsg.value = err.response?.data?.message || 'Gagal mengupdate data: ' + err.message
|
showError(err.response?.data?.message || 'Gagal mengupdate data: ' + err.message)
|
||||||
} finally {
|
} finally {
|
||||||
saving.value = false
|
saving.value = false
|
||||||
}
|
}
|
||||||
@@ -168,17 +147,15 @@ async function updateTicketIncidentType(id) {
|
|||||||
|
|
||||||
async function deleteTicketIncidentType(id) {
|
async function deleteTicketIncidentType(id) {
|
||||||
try {
|
try {
|
||||||
await api.delete(`/${id}`)
|
await api.delete(`/ticket-incident-types/${id}`)
|
||||||
store.deleteItem(deleteIndex.value)
|
store.deleteItem(deleteIndex.value)
|
||||||
if (paginatedItems.length === 0 && currentPage.value > 1) currentPage.value--
|
if (paginatedItems.length === 0 && currentPage.value > 1) currentPage.value--
|
||||||
showSuccess('Data berhasil dihapus')
|
showSuccess('Data berhasil dihapus')
|
||||||
fetchTicketIncidentTypes() // Refresh data after deletion
|
fetchTicketIncidentTypes()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Delete failed:', err)
|
showError('Gagal menghapus data: ' + err.message)
|
||||||
errorMsg.value = 'Gagal menghapus data: ' + err.message
|
|
||||||
} finally {
|
} finally {
|
||||||
deleteIndex.value = -1
|
deleteIndex.value = -1
|
||||||
deleteModal.value = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,12 +167,6 @@ async function saveItem() {
|
|||||||
await createTicketIncidentType()
|
await createTicketIncidentType()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function confirmDelete() {
|
|
||||||
if (deleteIndex.value < 0) return
|
|
||||||
const item = store.items[deleteIndex.value]
|
|
||||||
await deleteTicketIncidentType(item.id)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -204,15 +175,12 @@ async function confirmDelete() {
|
|||||||
<CCard class="mb-4">
|
<CCard class="mb-4">
|
||||||
<CCardHeader>
|
<CCardHeader>
|
||||||
<strong>Ticket Incident</strong>
|
<strong>Ticket Incident</strong>
|
||||||
<CButton color="warning" size="sm" class="float-end" @click="openAddModal">
|
<CButton color="primary" class="float-end" @click="openAddModal">
|
||||||
+ Tambah Ticket Incident
|
+ Tambah Ticket Incident
|
||||||
</CButton>
|
</CButton>
|
||||||
</CCardHeader>
|
</CCardHeader>
|
||||||
<CCardBody>
|
<CCardBody>
|
||||||
<CAlert color="success" v-if="successMsg" dismissible @close="successMsg = ''">
|
<div class="">
|
||||||
{{ successMsg }}
|
|
||||||
</CAlert>
|
|
||||||
<div class="table-responsive">
|
|
||||||
<CTable striped hover small>
|
<CTable striped hover small>
|
||||||
<CTableHead>
|
<CTableHead>
|
||||||
<CTableRow>
|
<CTableRow>
|
||||||
@@ -265,19 +233,20 @@ async function confirmDelete() {
|
|||||||
@change="changePerPage(Number($event.target.value))"
|
@change="changePerPage(Number($event.target.value))"
|
||||||
>
|
>
|
||||||
<option :value="10">10</option>
|
<option :value="10">10</option>
|
||||||
|
<option :value="15">15</option>
|
||||||
<option :value="25">25</option>
|
<option :value="25">25</option>
|
||||||
<option :value="50">50</option>
|
<option :value="50">50</option>
|
||||||
<option :value="100">100</option>
|
<option :value="100">100</option>
|
||||||
</CFormSelect>
|
</CFormSelect>
|
||||||
<span>dari {{ store.items.length }} data</span>
|
<span>dari {{ store.items.length }} data</span>
|
||||||
</div>
|
</div>
|
||||||
<CPagination v-if="totalPages > 1" size="sm" aria-label="pagination">
|
<CPagination v-if="totalPages > 1" aria-label="pagination">
|
||||||
<CPaginationItem
|
<CPaginationItem
|
||||||
aria-label="Previous"
|
aria-label="Previous"
|
||||||
:disabled="currentPage === 1"
|
:disabled="currentPage === 1"
|
||||||
@click="goToPage(currentPage - 1)"
|
@click="goToPage(currentPage - 1)"
|
||||||
>
|
>
|
||||||
<span aria-hidden="true">«</span>
|
Previous
|
||||||
</CPaginationItem>
|
</CPaginationItem>
|
||||||
<CPaginationItem
|
<CPaginationItem
|
||||||
v-for="page in totalPages"
|
v-for="page in totalPages"
|
||||||
@@ -292,7 +261,7 @@ async function confirmDelete() {
|
|||||||
:disabled="currentPage === totalPages"
|
:disabled="currentPage === totalPages"
|
||||||
@click="goToPage(currentPage + 1)"
|
@click="goToPage(currentPage + 1)"
|
||||||
>
|
>
|
||||||
<span aria-hidden="true">»</span>
|
Next
|
||||||
</CPaginationItem>
|
</CPaginationItem>
|
||||||
</CPagination>
|
</CPagination>
|
||||||
</div>
|
</div>
|
||||||
@@ -308,9 +277,6 @@ async function confirmDelete() {
|
|||||||
</CModalHeader>
|
</CModalHeader>
|
||||||
<CModalBody>
|
<CModalBody>
|
||||||
<CForm>
|
<CForm>
|
||||||
<CAlert color="danger" v-if="errorMsg" dismissible @close="errorMsg = ''">
|
|
||||||
{{ errorMsg }}
|
|
||||||
</CAlert>
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<CFormLabel>Name</CFormLabel>
|
<CFormLabel>Name</CFormLabel>
|
||||||
<CFormInput v-model="form.name" placeholder="Contoh: Kabel putus" />
|
<CFormInput v-model="form.name" placeholder="Contoh: Kabel putus" />
|
||||||
@@ -333,20 +299,6 @@ async function confirmDelete() {
|
|||||||
</CModalFooter>
|
</CModalFooter>
|
||||||
</CModal>
|
</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 -->
|
<!-- Detail Modal -->
|
||||||
<CModal :visible="detailModal" @close="detailModal = false" alignment="center">
|
<CModal :visible="detailModal" @close="detailModal = false" alignment="center">
|
||||||
<CModalHeader dismiss @close="detailModal = false">
|
<CModalHeader dismiss @close="detailModal = false">
|
||||||
|
|||||||
@@ -1,57 +1,431 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, reactive, computed, watch, onMounted } from 'vue'
|
||||||
|
import Swal from 'sweetalert2'
|
||||||
|
import { useMaterialStore } from '@/stores/materialStore'
|
||||||
|
import { materialService } from '@/services/materialService'
|
||||||
|
|
||||||
|
import MaterialTable from '@/components/materials/MaterialTable.vue'
|
||||||
|
import MaterialHistory from '@/components/materials/MaterialHistory.vue'
|
||||||
|
import AssignMaterialModal from '@/components/materials/AssignMaterialModal.vue'
|
||||||
|
import TransferMaterialModal from '@/components/materials/TransferMaterialModal.vue'
|
||||||
|
import ReturnMaterialModal from '@/components/materials/ReturnMaterialModal.vue'
|
||||||
|
import MaterialDetailModal from '@/components/materials/MaterialDetailModal.vue'
|
||||||
|
|
||||||
|
const store = useMaterialStore()
|
||||||
|
|
||||||
|
const currentTab = ref('user')
|
||||||
|
const searchBarcode = ref('')
|
||||||
|
const searchMaterial = ref('')
|
||||||
|
const selectedUser = ref(null)
|
||||||
|
const selectedBarcode = ref('')
|
||||||
|
|
||||||
|
const currentPage = ref(1)
|
||||||
|
const perPage = ref(15)
|
||||||
|
|
||||||
|
const filterType = ref('')
|
||||||
|
|
||||||
|
const assignModal = ref(false)
|
||||||
|
const transferModal = ref(false)
|
||||||
|
const returnModal = ref(false)
|
||||||
|
const detailModal = ref(false)
|
||||||
|
|
||||||
|
const saving = ref(false)
|
||||||
|
|
||||||
|
const assignForm = reactive({
|
||||||
|
user_id: null,
|
||||||
|
barcode_id: '',
|
||||||
|
material_name: '',
|
||||||
|
type: 'serialized',
|
||||||
|
serial_number: '',
|
||||||
|
qty: 1,
|
||||||
|
unit: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const transferForm = reactive({
|
||||||
|
barcode_id: '',
|
||||||
|
from_user_id: null,
|
||||||
|
to_user_id: null,
|
||||||
|
})
|
||||||
|
|
||||||
|
const returnForm = reactive({
|
||||||
|
barcode_id: '',
|
||||||
|
user_id: null,
|
||||||
|
})
|
||||||
|
|
||||||
|
const detailItem = ref({})
|
||||||
|
|
||||||
|
const filteredMaterials = computed(() => {
|
||||||
|
return store.materials.filter((item) => {
|
||||||
|
const barcodeMatch =
|
||||||
|
!searchBarcode.value ||
|
||||||
|
item.barcode_id?.toLowerCase().includes(searchBarcode.value.toLowerCase())
|
||||||
|
|
||||||
|
const materialMatch =
|
||||||
|
!searchMaterial.value ||
|
||||||
|
item.material_name?.toLowerCase().includes(searchMaterial.value.toLowerCase())
|
||||||
|
|
||||||
|
const userMatch = !selectedUser.value || item.user_id == selectedUser.value
|
||||||
|
|
||||||
|
const typeMatch = !filterType.value || item.type === filterType.value
|
||||||
|
|
||||||
|
return barcodeMatch && materialMatch && userMatch && typeMatch
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const isFilteringActive = computed(() => {
|
||||||
|
return (
|
||||||
|
searchBarcode.value !== '' ||
|
||||||
|
searchMaterial.value !== '' ||
|
||||||
|
selectedUser.value !== null ||
|
||||||
|
filterType.value !== ''
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const currentMaterialsSource = computed(() => {
|
||||||
|
return isFilteringActive.value ? filteredMaterials.value : store.materials
|
||||||
|
})
|
||||||
|
|
||||||
|
const totalPages = computed(() => Math.ceil(currentMaterialsSource.value.length / perPage.value))
|
||||||
|
|
||||||
|
const paginatedMaterials = computed(() => {
|
||||||
|
const start = (currentPage.value - 1) * perPage.value
|
||||||
|
return currentMaterialsSource.value.slice(start, start + perPage.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
const assignedMaterial = computed(
|
||||||
|
() => store.materials.filter((i) => i.status === 'Assigned').length,
|
||||||
|
)
|
||||||
|
const returnedMaterial = computed(
|
||||||
|
() => store.materials.filter((i) => i.status === 'Returned').length,
|
||||||
|
)
|
||||||
|
const serializedMaterial = computed(
|
||||||
|
() => store.materials.filter((i) => i.type === 'serialized').length,
|
||||||
|
)
|
||||||
|
const consumableMaterial = computed(
|
||||||
|
() => store.materials.filter((i) => i.type === 'consumable').length,
|
||||||
|
)
|
||||||
|
|
||||||
|
const totalMaterial = computed(() => store.materials.length)
|
||||||
|
|
||||||
|
watch(searchBarcode, () => {
|
||||||
|
currentPage.value = 1
|
||||||
|
})
|
||||||
|
watch(searchMaterial, () => {
|
||||||
|
currentPage.value = 1
|
||||||
|
})
|
||||||
|
watch(filterType, () => {
|
||||||
|
currentPage.value = 1
|
||||||
|
})
|
||||||
|
|
||||||
|
watch([selectedUser], async ([newSelectedUser]) => {
|
||||||
|
currentPage.value = 1
|
||||||
|
if (newSelectedUser) {
|
||||||
|
await store.fetchByUser(newSelectedUser)
|
||||||
|
} else {
|
||||||
|
await store.fetchAll()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(selectedBarcode, async (val) => {
|
||||||
|
if (val) await store.fetchHistory(val)
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
store.fetchAll()
|
||||||
|
})
|
||||||
|
|
||||||
|
function openTransferModal(item) {
|
||||||
|
transferForm.barcode_id = item.barcode_id
|
||||||
|
transferForm.from_user_id = item.user_id
|
||||||
|
transferForm.to_user_id = null
|
||||||
|
transferModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function openReturnModal(item) {
|
||||||
|
returnForm.barcode_id = item.barcode_id
|
||||||
|
returnForm.user_id = item.user_id
|
||||||
|
returnModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function openDetail(item) {
|
||||||
|
detailItem.value = { ...item }
|
||||||
|
detailModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function assignMaterial() {
|
||||||
|
saving.value = true
|
||||||
|
try {
|
||||||
|
await materialService.assign({
|
||||||
|
...assignForm,
|
||||||
|
qty: Number(assignForm.qty),
|
||||||
|
})
|
||||||
|
assignModal.value = false
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Berhasil',
|
||||||
|
text: 'Material berhasil di-assign',
|
||||||
|
timer: 1500,
|
||||||
|
showConfirmButton: false,
|
||||||
|
})
|
||||||
|
const userId = selectedUser.value || assignForm.user_id
|
||||||
|
if (userId) {
|
||||||
|
selectedUser.value = userId
|
||||||
|
await store.fetchByUser(userId)
|
||||||
|
} else {
|
||||||
|
await store.fetchAll()
|
||||||
|
}
|
||||||
|
assignForm.barcode_id = ''
|
||||||
|
assignForm.user_id = null
|
||||||
|
assignForm.material_name = ''
|
||||||
|
assignForm.type = 'serialized'
|
||||||
|
assignForm.serial_number = ''
|
||||||
|
assignForm.qty = 1
|
||||||
|
assignForm.unit = ''
|
||||||
|
} catch (err) {
|
||||||
|
Swal.fire({ icon: 'error', title: 'Gagal', text: err.response?.data?.message || err.message })
|
||||||
|
} finally {
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function transferMaterial() {
|
||||||
|
const confirm = await Swal.fire({
|
||||||
|
title: 'Transfer Material?',
|
||||||
|
text: 'Pastikan user tujuan sudah benar.',
|
||||||
|
icon: 'question',
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonText: 'Transfer',
|
||||||
|
cancelButtonText: 'Batal',
|
||||||
|
})
|
||||||
|
if (!confirm.isConfirmed) return
|
||||||
|
|
||||||
|
saving.value = true
|
||||||
|
try {
|
||||||
|
await materialService.transfer({ ...transferForm })
|
||||||
|
transferModal.value = false
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Berhasil',
|
||||||
|
text: 'Material berhasil dipindahkan',
|
||||||
|
timer: 1500,
|
||||||
|
showConfirmButton: false,
|
||||||
|
})
|
||||||
|
if (selectedUser.value) {
|
||||||
|
await store.fetchByUser(selectedUser.value)
|
||||||
|
} else {
|
||||||
|
await store.fetchAll()
|
||||||
|
}
|
||||||
|
if (selectedBarcode.value) await store.fetchHistory(selectedBarcode.value)
|
||||||
|
} catch (err) {
|
||||||
|
Swal.fire({ icon: 'error', title: 'Gagal', text: err.response?.data?.message || err.message })
|
||||||
|
} finally {
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function returnMaterial() {
|
||||||
|
const confirm = await Swal.fire({
|
||||||
|
title: 'Return Material?',
|
||||||
|
text: 'Material akan dikembalikan.',
|
||||||
|
icon: 'warning',
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonText: 'Ya',
|
||||||
|
})
|
||||||
|
if (!confirm.isConfirmed) return
|
||||||
|
|
||||||
|
saving.value = true
|
||||||
|
try {
|
||||||
|
await materialService.returnMaterial({ ...returnForm })
|
||||||
|
returnModal.value = false
|
||||||
|
Swal.fire({
|
||||||
|
icon: 'success',
|
||||||
|
title: 'Berhasil',
|
||||||
|
text: 'Material berhasil dikembalikan',
|
||||||
|
timer: 1500,
|
||||||
|
showConfirmButton: false,
|
||||||
|
})
|
||||||
|
if (selectedUser.value) {
|
||||||
|
await store.fetchByUser(selectedUser.value)
|
||||||
|
} else {
|
||||||
|
await store.fetchAll()
|
||||||
|
}
|
||||||
|
if (selectedBarcode.value) await store.fetchHistory(selectedBarcode.value)
|
||||||
|
} catch (err) {
|
||||||
|
Swal.fire({ icon: 'error', title: 'Gagal', text: err.response?.data?.message || err.message })
|
||||||
|
} finally {
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<CRow>
|
<CRow>
|
||||||
<CCol :xs="12">
|
<CCol :xs="12">
|
||||||
<CCard class="mb-4">
|
<CCard class="mb-4">
|
||||||
<CCardHeader> <strong>Ticket Materials</strong> </CCardHeader>
|
<CCardHeader class="d-flex justify-content-between align-items-center">
|
||||||
|
<div>
|
||||||
|
<strong>Material Management</strong>
|
||||||
|
<div class="small text-body-secondary">
|
||||||
|
Assign, Transfer, Return dan History Material
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<CButton color="primary" @click="assignModal = true">+ Tambah Material</CButton>
|
||||||
|
</CCardHeader>
|
||||||
|
|
||||||
<CCardBody>
|
<CCardBody>
|
||||||
<CTable striped hover>
|
<CRow class="mb-4">
|
||||||
<CTableHead>
|
<CCol md
|
||||||
<CTableRow>
|
><CCard class="border-start border-primary border-4"
|
||||||
<CTableHeaderCell scope="col">#</CTableHeaderCell>
|
><CCardBody
|
||||||
<CTableHeaderCell scope="col">Material Name</CTableHeaderCell>
|
><div>Total Material</div>
|
||||||
<CTableHeaderCell scope="col">Ticket</CTableHeaderCell>
|
<h3>{{ totalMaterial }}</h3></CCardBody
|
||||||
<CTableHeaderCell scope="col">Quantity</CTableHeaderCell>
|
></CCard
|
||||||
<CTableHeaderCell scope="col">Unit</CTableHeaderCell>
|
></CCol
|
||||||
<CTableHeaderCell scope="col">Cost</CTableHeaderCell>
|
>
|
||||||
</CTableRow>
|
<CCol md
|
||||||
</CTableHead>
|
><CCard class="border-start border-success border-4"
|
||||||
<CTableBody>
|
><CCardBody
|
||||||
<CTableRow>
|
><div>Assigned</div>
|
||||||
<CTableHeaderCell scope="row">1</CTableHeaderCell>
|
<h3>{{ assignedMaterial }}</h3></CCardBody
|
||||||
<CTableDataCell>Cable CAT6</CTableDataCell>
|
></CCard
|
||||||
<CTableDataCell>Network setup</CTableDataCell>
|
></CCol
|
||||||
<CTableDataCell>100</CTableDataCell>
|
>
|
||||||
<CTableDataCell>Meters</CTableDataCell>
|
<CCol md
|
||||||
<CTableDataCell>$150.00</CTableDataCell>
|
><CCard class="border-start border-secondary border-4"
|
||||||
</CTableRow>
|
><CCardBody
|
||||||
<CTableRow>
|
><div>Returned</div>
|
||||||
<CTableHeaderCell scope="row">2</CTableHeaderCell>
|
<h3>{{ returnedMaterial }}</h3></CCardBody
|
||||||
<CTableDataCell>RJ45 Connector</CTableDataCell>
|
></CCard
|
||||||
<CTableDataCell>Network setup</CTableDataCell>
|
></CCol
|
||||||
<CTableDataCell>50</CTableDataCell>
|
>
|
||||||
<CTableDataCell>Pcs</CTableDataCell>
|
<CCol md
|
||||||
<CTableDataCell>$25.00</CTableDataCell>
|
><CCard class="border-start border-info border-4"
|
||||||
</CTableRow>
|
><CCardBody
|
||||||
<CTableRow>
|
><div>Serialized</div>
|
||||||
<CTableHeaderCell scope="row">3</CTableHeaderCell>
|
<h3>{{ serializedMaterial }}</h3></CCardBody
|
||||||
<CTableDataCell>SSD 512GB</CTableDataCell>
|
></CCard
|
||||||
<CTableDataCell>Hardware upgrade</CTableDataCell>
|
></CCol
|
||||||
<CTableDataCell>2</CTableDataCell>
|
>
|
||||||
<CTableDataCell>Pcs</CTableDataCell>
|
<CCol md
|
||||||
<CTableDataCell>$200.00</CTableDataCell>
|
><CCard class="border-start border-warning border-4"
|
||||||
</CTableRow>
|
><CCardBody
|
||||||
<CTableRow>
|
><div>Consumable</div>
|
||||||
<CTableHeaderCell scope="row">4</CTableHeaderCell>
|
<h3>{{ consumableMaterial }}</h3></CCardBody
|
||||||
<CTableDataCell>RAM 16GB</CTableDataCell>
|
></CCard
|
||||||
<CTableDataCell>Hardware upgrade</CTableDataCell>
|
></CCol
|
||||||
<CTableDataCell>2</CTableDataCell>
|
>
|
||||||
<CTableDataCell>Pcs</CTableDataCell>
|
</CRow>
|
||||||
<CTableDataCell>$180.00</CTableDataCell>
|
|
||||||
</CTableRow>
|
<CTabs :active-item-key="currentTab" @change="(key) => (currentTab = key)">
|
||||||
</CTableBody>
|
<CTabList variant="tabs">
|
||||||
</CTable>
|
<CTab item-key="user">Material User</CTab>
|
||||||
|
<CTab item-key="history">History</CTab>
|
||||||
|
</CTabList>
|
||||||
|
|
||||||
|
<CTabPanel item-key="user" class="mt-4">
|
||||||
|
<CRow class="mb-3">
|
||||||
|
<CCol md="3"
|
||||||
|
><CFormLabel>Barcode</CFormLabel><CFormInput v-model="searchBarcode"
|
||||||
|
/></CCol>
|
||||||
|
<CCol md="3"
|
||||||
|
><CFormLabel>Material</CFormLabel><CFormInput v-model="searchMaterial"
|
||||||
|
/></CCol>
|
||||||
|
<CCol md="2">
|
||||||
|
<CFormLabel>Type</CFormLabel>
|
||||||
|
<CFormSelect v-model="filterType">
|
||||||
|
<option value="">Semua Type</option>
|
||||||
|
<option value="serialized">Serialized</option>
|
||||||
|
<option value="consumable">Consumable</option>
|
||||||
|
</CFormSelect>
|
||||||
|
</CCol>
|
||||||
|
<CCol md="2">
|
||||||
|
<CFormLabel>User ID</CFormLabel>
|
||||||
|
<CFormInput v-model="selectedUser" placeholder="Filter by User ID" />
|
||||||
|
</CCol>
|
||||||
|
<CCol md="2" class="d-flex align-items-end">
|
||||||
|
<CButton
|
||||||
|
color="secondary"
|
||||||
|
@click="
|
||||||
|
((searchBarcode = ''),
|
||||||
|
(searchMaterial = ''),
|
||||||
|
(selectedUser = null),
|
||||||
|
(filterType = ''))
|
||||||
|
"
|
||||||
|
>Reset</CButton
|
||||||
|
>
|
||||||
|
</CCol>
|
||||||
|
</CRow>
|
||||||
|
|
||||||
|
<MaterialTable
|
||||||
|
:data="paginatedMaterials"
|
||||||
|
:totalPages="totalPages"
|
||||||
|
:currentPage="currentPage"
|
||||||
|
:perPage="perPage"
|
||||||
|
:totalItems="currentMaterialsSource.length"
|
||||||
|
:loading="store.loading"
|
||||||
|
@update:perPage="
|
||||||
|
(val) => {
|
||||||
|
perPage = val
|
||||||
|
currentPage = 1
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@page="(p) => (currentPage = p)"
|
||||||
|
@detail="openDetail"
|
||||||
|
@transfer="openTransferModal"
|
||||||
|
@return="openReturnModal"
|
||||||
|
/>
|
||||||
|
</CTabPanel>
|
||||||
|
|
||||||
|
<CTabPanel item-key="history" class="mt-4">
|
||||||
|
<CRow class="mb-3">
|
||||||
|
<CCol md="4">
|
||||||
|
<CFormLabel>Barcode</CFormLabel>
|
||||||
|
<CFormInput v-model="selectedBarcode" placeholder="Masukkan Barcode" />
|
||||||
|
</CCol>
|
||||||
|
<CCol md="2" class="d-flex align-items-end">
|
||||||
|
<CButton color="primary" @click="store.fetchHistory(selectedBarcode)"
|
||||||
|
>Lihat History</CButton
|
||||||
|
>
|
||||||
|
</CCol>
|
||||||
|
</CRow>
|
||||||
|
|
||||||
|
<MaterialHistory :histories="store.histories" />
|
||||||
|
</CTabPanel>
|
||||||
|
</CTabs>
|
||||||
</CCardBody>
|
</CCardBody>
|
||||||
</CCard>
|
</CCard>
|
||||||
</CCol>
|
</CCol>
|
||||||
</CRow>
|
</CRow>
|
||||||
|
|
||||||
|
<AssignMaterialModal
|
||||||
|
:visible="assignModal"
|
||||||
|
:form="assignForm"
|
||||||
|
:saving="saving"
|
||||||
|
@close="assignModal = false"
|
||||||
|
@save="assignMaterial"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TransferMaterialModal
|
||||||
|
:visible="transferModal"
|
||||||
|
:form="transferForm"
|
||||||
|
:saving="saving"
|
||||||
|
@close="transferModal = false"
|
||||||
|
@save="transferMaterial"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ReturnMaterialModal
|
||||||
|
:visible="returnModal"
|
||||||
|
:form="returnForm"
|
||||||
|
:saving="saving"
|
||||||
|
@close="returnModal = false"
|
||||||
|
@save="returnMaterial"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<MaterialDetailModal
|
||||||
|
:visible="detailModal"
|
||||||
|
:item="detailItem"
|
||||||
|
@close="detailModal = false"
|
||||||
|
@transfer="openTransferModal"
|
||||||
|
@return="openReturnModal"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,27 +1,22 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import axios from 'axios'
|
import { ref, reactive, computed, onMounted } from 'vue'
|
||||||
import { ref, reactive, computed, watch, onMounted } from 'vue'
|
|
||||||
import { useTicketTypeStore } from '@/stores/ticketType.js'
|
import { useTicketTypeStore } from '@/stores/ticketType.js'
|
||||||
import { formatTanggal } from '@/utils/tglindo.js'
|
import { formatTanggal } from '@/utils/tglindo.js'
|
||||||
|
import { showConfirm, showSuccess, showSuccessr, showError } from '@/utils/swal.js'
|
||||||
|
import { CButton } from '@coreui/vue'
|
||||||
|
import { CBadge } from '@coreui/vue'
|
||||||
|
import api from '@/api/axios'
|
||||||
|
|
||||||
const store = useTicketTypeStore()
|
const store = useTicketTypeStore()
|
||||||
const loading = ref(false)
|
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() {
|
async function fetchAllData() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const res = await api.get('/ticket-types?per_page=1000')
|
const res = await api.get('/ticket-types?per_page=1000')
|
||||||
store.setTicketTypes(res.data.data || [])
|
store.setTicketTypes(res.data.data || [])
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to fetch data:', err)
|
showError('Gagal memuat data: ' + err.message)
|
||||||
errorMsg.value = 'Gagal memuat data: ' + err.message
|
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
@@ -32,15 +27,11 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const modal = ref(false)
|
const modal = ref(false)
|
||||||
const deleteModal = ref(false)
|
|
||||||
const detailModal = ref(false)
|
const detailModal = ref(false)
|
||||||
const editingIndex = ref(-1)
|
const editingIndex = ref(-1)
|
||||||
const isEdit = ref(false)
|
const isEdit = ref(false)
|
||||||
const deleteIndex = ref(-1)
|
const deleteIndex = ref(-1)
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
const errorMsg = ref('')
|
|
||||||
const successMsg = ref('')
|
|
||||||
let successTimer = null
|
|
||||||
|
|
||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
const perPage = ref(10)
|
const perPage = ref(10)
|
||||||
@@ -61,14 +52,6 @@ function changePerPage(val) {
|
|||||||
currentPage.value = 1
|
currentPage.value = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
function showSuccess(msg) {
|
|
||||||
successMsg.value = msg
|
|
||||||
if (successTimer) clearTimeout(successTimer)
|
|
||||||
successTimer = setTimeout(() => {
|
|
||||||
successMsg.value = ''
|
|
||||||
}, 3000)
|
|
||||||
}
|
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
code: '',
|
code: '',
|
||||||
name: '',
|
name: '',
|
||||||
@@ -99,8 +82,7 @@ async function getTicketType(id) {
|
|||||||
detailItem.value = res.data.data
|
detailItem.value = res.data.data
|
||||||
detailModal.value = true
|
detailModal.value = true
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to fetch detail:', err)
|
showError('Gagal memuat detail: ' + err.message)
|
||||||
errorMsg.value = 'Gagal memuat detail: ' + err.message
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,15 +115,19 @@ function openEditModal(item) {
|
|||||||
modal.value = true
|
modal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function openDeleteModal(item) {
|
async function openDeleteModal(item) {
|
||||||
const idx = store.ticketTypes.findIndex((t) => t.code === item.code)
|
const idx = store.ticketTypes.findIndex((t) => t.code === item.code)
|
||||||
if (idx === -1) return
|
if (idx === -1) return
|
||||||
|
const result = await showConfirm(
|
||||||
|
'Konfirmasi Hapus',
|
||||||
|
'Apakah Anda yakin ingin menghapus ticket type ini?',
|
||||||
|
)
|
||||||
|
if (!result.isConfirmed) return
|
||||||
deleteIndex.value = idx
|
deleteIndex.value = idx
|
||||||
deleteModal.value = true
|
await deleteTicketType(item.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createTicketType() {
|
async function createTicketType() {
|
||||||
errorMsg.value = ''
|
|
||||||
saving.value = true
|
saving.value = true
|
||||||
const body = {
|
const body = {
|
||||||
code: form.code,
|
code: form.code,
|
||||||
@@ -161,14 +147,13 @@ async function createTicketType() {
|
|||||||
showSuccess('Data berhasil disimpan')
|
showSuccess('Data berhasil disimpan')
|
||||||
fetchAllData()
|
fetchAllData()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errorMsg.value = err.response?.data?.message || 'Gagal menyimpan data: ' + err.message
|
showError(err.response?.data?.message || 'Gagal menyimpan data: ' + err.message)
|
||||||
} finally {
|
} finally {
|
||||||
saving.value = false
|
saving.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateTicketType(id) {
|
async function updateTicketType(id) {
|
||||||
errorMsg.value = ''
|
|
||||||
saving.value = true
|
saving.value = true
|
||||||
const body = {
|
const body = {
|
||||||
code: form.code,
|
code: form.code,
|
||||||
@@ -184,10 +169,10 @@ async function updateTicketType(id) {
|
|||||||
await api.put(`/ticket-types/${id}`, body)
|
await api.put(`/ticket-types/${id}`, body)
|
||||||
store.updateTicketType(editingIndex.value, { ...body })
|
store.updateTicketType(editingIndex.value, { ...body })
|
||||||
modal.value = false
|
modal.value = false
|
||||||
showSuccess('Data berhasil diupdate')
|
showSuccessr('Data berhasil diupdate')
|
||||||
fetchAllData()
|
fetchAllData()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
errorMsg.value = err.response?.data?.message || 'Gagal mengupdate data: ' + err.message
|
showError(err.response?.data?.message || 'Gagal mengupdate data: ' + err.message)
|
||||||
} finally {
|
} finally {
|
||||||
saving.value = false
|
saving.value = false
|
||||||
}
|
}
|
||||||
@@ -201,11 +186,9 @@ async function deleteTicketType(id) {
|
|||||||
showSuccess('Data berhasil dihapus')
|
showSuccess('Data berhasil dihapus')
|
||||||
fetchAllData()
|
fetchAllData()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Delete failed:', err)
|
showError('Gagal menghapus data: ' + err.message)
|
||||||
errorMsg.value = 'Gagal menghapus data: ' + err.message
|
|
||||||
} finally {
|
} finally {
|
||||||
deleteIndex.value = -1
|
deleteIndex.value = -1
|
||||||
deleteModal.value = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,12 +200,6 @@ async function saveItem() {
|
|||||||
await createTicketType()
|
await createTicketType()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function confirmDelete() {
|
|
||||||
if (deleteIndex.value < 0) return
|
|
||||||
const item = store.ticketTypes[deleteIndex.value]
|
|
||||||
await deleteTicketType(item.id)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -231,15 +208,12 @@ async function confirmDelete() {
|
|||||||
<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">
|
<CButton color="primary" class="float-end" @click="openAddModal">
|
||||||
+ Tambah Ticket Type
|
+ Tambah Ticket Type
|
||||||
</CButton>
|
</CButton>
|
||||||
</CCardHeader>
|
</CCardHeader>
|
||||||
<CCardBody>
|
<CCardBody>
|
||||||
<CAlert color="success" v-if="successMsg" dismissible @close="successMsg = ''">
|
<div class="">
|
||||||
{{ successMsg }}
|
|
||||||
</CAlert>
|
|
||||||
<div class="table-responsive">
|
|
||||||
<CTable striped hover small>
|
<CTable striped hover small>
|
||||||
<CTableHead>
|
<CTableHead>
|
||||||
<CTableRow>
|
<CTableRow>
|
||||||
@@ -256,7 +230,7 @@ async function confirmDelete() {
|
|||||||
(currentPage - 1) * perPage + index + 1
|
(currentPage - 1) * perPage + index + 1
|
||||||
}}</CTableHeaderCell>
|
}}</CTableHeaderCell>
|
||||||
<CTableDataCell
|
<CTableDataCell
|
||||||
><code>{{ item.code }}</code></CTableDataCell
|
><CBadge color="primary">{{ item.code }}</CBadge></CTableDataCell
|
||||||
>
|
>
|
||||||
<CTableDataCell>{{ item.name }}</CTableDataCell>
|
<CTableDataCell>{{ item.name }}</CTableDataCell>
|
||||||
<CTableDataCell class="text-center">{{ item.sla_minutes }}</CTableDataCell>
|
<CTableDataCell class="text-center">{{ item.sla_minutes }}</CTableDataCell>
|
||||||
@@ -291,13 +265,13 @@ async function confirmDelete() {
|
|||||||
</CFormSelect>
|
</CFormSelect>
|
||||||
<span>dari {{ store.ticketTypes.length }} data</span>
|
<span>dari {{ store.ticketTypes.length }} data</span>
|
||||||
</div>
|
</div>
|
||||||
<CPagination v-if="totalPages > 1" size="sm" aria-label="pagination">
|
<CPagination v-if="totalPages > 1" aria-label="pagination">
|
||||||
<CPaginationItem
|
<CPaginationItem
|
||||||
aria-label="Previous"
|
aria-label="Previous"
|
||||||
:disabled="currentPage === 1"
|
:disabled="currentPage === 1"
|
||||||
@click="goToPage(currentPage - 1)"
|
@click="goToPage(currentPage - 1)"
|
||||||
>
|
>
|
||||||
<span aria-hidden="true">«</span>
|
Previous
|
||||||
</CPaginationItem>
|
</CPaginationItem>
|
||||||
<CPaginationItem
|
<CPaginationItem
|
||||||
v-for="page in totalPages"
|
v-for="page in totalPages"
|
||||||
@@ -312,7 +286,7 @@ async function confirmDelete() {
|
|||||||
:disabled="currentPage === totalPages"
|
:disabled="currentPage === totalPages"
|
||||||
@click="goToPage(currentPage + 1)"
|
@click="goToPage(currentPage + 1)"
|
||||||
>
|
>
|
||||||
<span aria-hidden="true">»</span>
|
Next
|
||||||
</CPaginationItem>
|
</CPaginationItem>
|
||||||
</CPagination>
|
</CPagination>
|
||||||
</div>
|
</div>
|
||||||
@@ -328,9 +302,6 @@ async function confirmDelete() {
|
|||||||
</CModalHeader>
|
</CModalHeader>
|
||||||
<CModalBody>
|
<CModalBody>
|
||||||
<CForm>
|
<CForm>
|
||||||
<CAlert color="danger" v-if="errorMsg" dismissible @close="errorMsg = ''">
|
|
||||||
{{ errorMsg }}
|
|
||||||
</CAlert>
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<CFormLabel>Code</CFormLabel>
|
<CFormLabel>Code</CFormLabel>
|
||||||
<CFormInput v-model="form.code" placeholder="Contoh: BUG" :disabled="isEdit" />
|
<CFormInput v-model="form.code" placeholder="Contoh: BUG" :disabled="isEdit" />
|
||||||
@@ -369,20 +340,6 @@ async function confirmDelete() {
|
|||||||
</CModalFooter>
|
</CModalFooter>
|
||||||
</CModal>
|
</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 -->
|
<!-- Detail Modal -->
|
||||||
<CModal :visible="detailModal" @close="detailModal = false" alignment="center">
|
<CModal :visible="detailModal" @close="detailModal = false" alignment="center">
|
||||||
<CModalHeader dismiss @close="detailModal = false">
|
<CModalHeader dismiss @close="detailModal = false">
|
||||||
@@ -394,7 +351,7 @@ async function confirmDelete() {
|
|||||||
<CTableRow>
|
<CTableRow>
|
||||||
<CTableHeaderCell style="width: 40%">Code</CTableHeaderCell>
|
<CTableHeaderCell style="width: 40%">Code</CTableHeaderCell>
|
||||||
<CTableDataCell
|
<CTableDataCell
|
||||||
><code>{{ detailItem.code }}</code></CTableDataCell
|
><b>{{ detailItem.code }}</b></CTableDataCell
|
||||||
>
|
>
|
||||||
</CTableRow>
|
</CTableRow>
|
||||||
<CTableRow>
|
<CTableRow>
|
||||||
@@ -404,7 +361,7 @@ async function confirmDelete() {
|
|||||||
<CTableRow>
|
<CTableRow>
|
||||||
<CTableHeaderCell>Need Approval</CTableHeaderCell>
|
<CTableHeaderCell>Need Approval</CTableHeaderCell>
|
||||||
<CTableDataCell>
|
<CTableDataCell>
|
||||||
<CBadge :color="detailItem.need_approval ? 'warning' : 'secondary'">
|
<CBadge :color="detailItem.need_approval ? 'success' : 'secondary'">
|
||||||
{{ detailItem.need_approval ? 'Ya' : 'Tidak' }}
|
{{ detailItem.need_approval ? 'Ya' : 'Tidak' }}
|
||||||
</CBadge>
|
</CBadge>
|
||||||
</CTableDataCell>
|
</CTableDataCell>
|
||||||
@@ -412,7 +369,7 @@ async function confirmDelete() {
|
|||||||
<CTableRow>
|
<CTableRow>
|
||||||
<CTableHeaderCell>Require Photo</CTableHeaderCell>
|
<CTableHeaderCell>Require Photo</CTableHeaderCell>
|
||||||
<CTableDataCell>
|
<CTableDataCell>
|
||||||
<CBadge :color="detailItem.require_photo ? 'warning' : 'secondary'">
|
<CBadge :color="detailItem.require_photo ? 'success' : 'secondary'">
|
||||||
{{ detailItem.require_photo ? 'Ya' : 'Tidak' }}
|
{{ detailItem.require_photo ? 'Ya' : 'Tidak' }}
|
||||||
</CBadge>
|
</CBadge>
|
||||||
</CTableDataCell>
|
</CTableDataCell>
|
||||||
@@ -420,7 +377,7 @@ async function confirmDelete() {
|
|||||||
<CTableRow>
|
<CTableRow>
|
||||||
<CTableHeaderCell>Require Material</CTableHeaderCell>
|
<CTableHeaderCell>Require Material</CTableHeaderCell>
|
||||||
<CTableDataCell>
|
<CTableDataCell>
|
||||||
<CBadge :color="detailItem.require_material ? 'warning' : 'secondary'">
|
<CBadge :color="detailItem.require_material ? 'success' : 'secondary'">
|
||||||
{{ detailItem.require_material ? 'Ya' : 'Tidak' }}
|
{{ detailItem.require_material ? 'Ya' : 'Tidak' }}
|
||||||
</CBadge>
|
</CBadge>
|
||||||
</CTableDataCell>
|
</CTableDataCell>
|
||||||
@@ -428,7 +385,7 @@ async function confirmDelete() {
|
|||||||
<CTableRow>
|
<CTableRow>
|
||||||
<CTableHeaderCell>Need Customer</CTableHeaderCell>
|
<CTableHeaderCell>Need Customer</CTableHeaderCell>
|
||||||
<CTableDataCell>
|
<CTableDataCell>
|
||||||
<CBadge :color="detailItem.need_customer ? 'warning' : 'secondary'">
|
<CBadge :color="detailItem.need_customer ? 'success' : 'secondary'">
|
||||||
{{ detailItem.need_customer ? 'Ya' : 'Tidak' }}
|
{{ detailItem.need_customer ? 'Ya' : 'Tidak' }}
|
||||||
</CBadge>
|
</CBadge>
|
||||||
</CTableDataCell>
|
</CTableDataCell>
|
||||||
@@ -436,7 +393,7 @@ async function confirmDelete() {
|
|||||||
<CTableRow>
|
<CTableRow>
|
||||||
<CTableHeaderCell>Need Incident Type</CTableHeaderCell>
|
<CTableHeaderCell>Need Incident Type</CTableHeaderCell>
|
||||||
<CTableDataCell>
|
<CTableDataCell>
|
||||||
<CBadge :color="detailItem.need_incident_type ? 'warning' : 'secondary'">
|
<CBadge :color="detailItem.need_incident_type ? 'success' : 'secondary'">
|
||||||
{{ detailItem.need_incident_type ? 'Ya' : 'Tidak' }}
|
{{ detailItem.need_incident_type ? 'Ya' : 'Tidak' }}
|
||||||
</CBadge>
|
</CBadge>
|
||||||
</CTableDataCell>
|
</CTableDataCell>
|
||||||
|
|||||||
Reference in New Issue
Block a user