split front end to separate repo

This commit is contained in:
Paul Wilde 2023-01-19 12:44:49 +00:00
commit 5c72899183
110 changed files with 9629 additions and 0 deletions

23
.gitignore vendored Normal file
View file

@ -0,0 +1,23 @@
.DS_Store
node_modules
./dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

24
README.md Normal file
View file

@ -0,0 +1,24 @@
# cmc_fe
## Project setup
```
yarn install
```
### Compiles and hot-reloads for development
```
yarn serve
```
### Compiles and minifies for production
```
yarn build
```
### Lints and fixes files
```
yarn lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

5
babel.config.js Normal file
View file

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

1
dist/css/165.83e5555a.css vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/css/17.ae3c234a.css vendored Normal file
View file

@ -0,0 +1 @@
.scroller[data-v-3c9948e5]{height:500px}.customer[data-v-3c9948e5]{height:32%;padding:0 12px;display:flex;align-items:center}

1
dist/css/287.b8b74994.css vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/css/337.1da97b3f.css vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/css/553.c3657471.css vendored Normal file
View file

@ -0,0 +1 @@
.v-switch .v-label{-webkit-padding-start:10px;padding-inline-start:10px}.v-switch__loader{display:flex}.v-switch__thumb,.v-switch__track{background-color:currentColor;transition:none}.v-selection-control--error:not(.v-selection-control--disabled) .v-switch__thumb,.v-selection-control--error:not(.v-selection-control--disabled) .v-switch__track{background-color:rgb(var(--v-theme-error))}.v-switch__track{border-radius:8px;height:14px;opacity:.6;width:36px;cursor:pointer}.v-switch--inset .v-switch__track{border-radius:14px;height:28px;width:48px}.v-switch__thumb{align-items:center;border-radius:50%;color:rgb(var(--v-theme-surface));display:flex;height:20px;justify-content:center;width:20px;pointer-events:none;transition:transform .15s cubic-bezier(.4,0,.2,1);box-shadow:0 2px 4px -1px var(--v-shadow-key-umbra-opacity,rgba(0,0,0,.2)),0 4px 5px 0 var(--v-shadow-key-penumbra-opacity,rgba(0,0,0,.14)),0 1px 10px 0 var(--v-shadow-key-penumbra-opacity,rgba(0,0,0,.12))}.v-switch--inset .v-switch__thumb{box-shadow:0 0 0 0 var(--v-shadow-key-umbra-opacity,rgba(0,0,0,.2)),0 0 0 0 var(--v-shadow-key-penumbra-opacity,rgba(0,0,0,.14)),0 0 0 0 var(--v-shadow-key-penumbra-opacity,rgba(0,0,0,.12))}.v-switch--loading .v-selection-control__input>.v-icon,.v-switch:not(.v-switch--loading) .v-icon~.v-switch__thumb{display:none}.v-switch .v-selection-control{min-height:var(--v-input-control-height)}.v-switch .v-selection-control__wrapper{width:auto}.v-switch .v-selection-control__input{border-radius:50%;transition:transform .15s cubic-bezier(.4,0,.2,1);transform:translateX(-10px);position:absolute}.v-switch .v-selection-control--dirty .v-selection-control__input{transform:translateX(10px)}.v-switch.v-switch--indeterminate .v-selection-control__input{transform:scale(.8)}.v-switch.v-switch--indeterminate .v-switch__thumb{transform:scale(.75);box-shadow:none}.v-selection-control{align-items:center;contain:layout;display:flex;flex:1 0;grid-area:control;position:relative;-webkit-user-select:none;-moz-user-select:none;user-select:none}.v-selection-control .v-label{white-space:normal;word-break:break-word;height:100%;width:100%}.v-selection-control--disabled{opacity:var(--v-disabled-opacity);pointer-events:none}.v-selection-control--disabled .v-label,.v-selection-control--error .v-label{opacity:1}.v-selection-control--error:not(.v-selection-control--disabled) .v-label{color:rgb(var(--v-theme-error))}.v-selection-control--inline{display:inline-flex;flex:0 0 auto;min-width:0;max-width:100%}.v-selection-control--inline .v-label{width:auto}.v-selection-control--density-default{--v-selection-control-size:40px}.v-selection-control--density-comfortable{--v-selection-control-size:36px}.v-selection-control--density-compact{--v-selection-control-size:28px}.v-selection-control__wrapper{display:inline-flex}.v-selection-control__input,.v-selection-control__wrapper{width:var(--v-selection-control-size);height:var(--v-selection-control-size);align-items:center;position:relative;justify-content:center;flex:none}.v-selection-control__input{display:flex;border-radius:50%}.v-selection-control__input input{cursor:pointer;position:absolute;left:0;top:0;width:100%;height:100%;opacity:0}.v-selection-control__input:before{content:"";position:absolute;top:0;left:0;width:100%;height:100%;border-radius:100%;background-color:currentColor;opacity:0;pointer-events:none}.v-selection-control__input:hover:before{opacity:calc(var(--v-theme-overlay-multiplier)*.04)}.v-selection-control__input>.v-icon{opacity:var(--v-medium-emphasis-opacity)}.v-selection-control--dirty .v-selection-control__input>.v-icon,.v-selection-control--disabled .v-selection-control__input>.v-icon,.v-selection-control--error .v-selection-control__input>.v-icon{opacity:1}.v-selection-control--error:not(.v-selection-control--disabled) .v-selection-control__input>.v-icon{color:rgb(var(--v-theme-error))}.v-selection-control--focus-visible .v-selection-control__input:before{opacity:calc(var(--v-theme-overlay-multiplier)*.12)}.v-selection-control-group{grid-area:control;display:flex;flex-direction:column}.v-selection-control-group--inline{flex-direction:row;flex-wrap:wrap}.v-sheet{display:block;border-color:rgba(var(--v-border-color),var(--v-border-opacity));border-style:solid;border-width:0;box-shadow:0 0 0 0 var(--v-shadow-key-umbra-opacity,rgba(0,0,0,.2)),0 0 0 0 var(--v-shadow-key-penumbra-opacity,rgba(0,0,0,.14)),0 0 0 0 var(--v-shadow-key-penumbra-opacity,rgba(0,0,0,.12));border-radius:0;background:rgb(var(--v-theme-surface));color:rgba(var(--v-theme-on-background),var(--v-high-emphasis-opacity))}.v-sheet--border{border-width:thin;box-shadow:none}.v-sheet--absolute{position:absolute}.v-sheet--fixed{position:fixed}.v-sheet--rounded{border-radius:4px}.item{height:100px}

1
dist/css/803.be0ee137.css vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/css/83.46f88035.css vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/css/975.9ad07ca2.css vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/css/app.f3159a54.css vendored Normal file
View file

@ -0,0 +1 @@
[v-cloak]{display:none}.lastrefresh{font-size:10px;float:right;text-align:right;margin-bottom:0}.loading{margin:auto;display:block}.title-bar{background:#ffae00;color:#000}.menu-icon:after{background:#000;box-shadow:0 7px 0 #000,0 14px 0 #000}main{margin-top:2rem;padding-top:2rem}.icon,.icon-2{max-height:2rem}.icon-1{max-height:1rem}table td{padding-right:.4em;padding-left:.4em;padding-top:0;padding-bottom:0}table .button{margin-top:.2rem;margin-bottom:.2rem}.off-canvas{background:#ffe}#editModal{height:750px}img.img-small{max-height:16px}table tr.at_risk{background:#ffb6c1}table tr.at_risk:hover{background:pink!important}.scrollable{max-height:90vh;overflow-y:scroll}.scrollable-50{max-height:50vh}.inactive{background:#d3d3d3;color:gray}.scroller{border:1px dotted #333;height:600px}.item{height:138px;overflow-y:hidden;padding:0 12px;margin-bottom:2px;display:flex;align-items:center;border-bottom:1px solid #000}

7
dist/css/chunk-vendors.fab48178.css vendored Normal file

File diff suppressed because one or more lines are too long

BIN
dist/favicon.ico vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
dist/images/cmc-logo.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
dist/images/icons/Completed.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
dist/images/icons/Live.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 B

BIN
dist/images/icons/finish.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
dist/images/icons/loading.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
dist/images/icons/loading.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 791 B

BIN
dist/images/icons/printed.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 B

BIN
dist/images/icons/printedconfirm.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
dist/images/icons/repeat.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 941 B

BIN
dist/images/icons/warning.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 B

1
dist/index.html vendored Normal file
View file

@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>cmc_fe</title><script defer="defer" src="/js/chunk-vendors.391950dc.js"></script><script defer="defer" src="/js/app.a93abe60.js"></script><link href="/css/chunk-vendors.fab48178.css" rel="stylesheet"><link href="/css/app.f3159a54.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but cmc_fe doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>

114
dist/js/121.5997df3e.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/js/121.5997df3e.js.map vendored Normal file

File diff suppressed because one or more lines are too long

2
dist/js/165.211e8e0a.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/js/165.211e8e0a.js.map vendored Normal file

File diff suppressed because one or more lines are too long

2
dist/js/17.e124ae12.js vendored Normal file
View file

@ -0,0 +1,2 @@
"use strict";(self["webpackChunkcmc_fe"]=self["webpackChunkcmc_fe"]||[]).push([[17],{8099:function(){},9017:function(e,t,l){l.r(t),l.d(t,{default:function(){return y}});var n=l(3396),s=l(7139),r=l(6824),o=l(8521),a=l(4162),i=l(165);const u=e=>((0,n.dD)("data-v-3c9948e5"),e=e(),(0,n.Cn)(),e),c=u((()=>(0,n._)("h3",null,"Customer List",-1))),d=u((()=>(0,n._)("hr",null,null,-1)));function f(e,t,l,u,f,g){const m=(0,n.up)("RecycleScroller");return(0,n.wg)(),(0,n.iD)(n.HY,null,[c,(0,n.Wm)(a.t,{"max-width":"500"},{default:(0,n.w5)((()=>[(0,n.Wm)(i.hw,{clearable:"",label:"Search",variant:"outlined",modelValue:f.searchQuery,"onUpdate:modelValue":t[0]||(t[0]=e=>f.searchQuery=e),density:"compact","append-inner-icon":"mdi-magnify"},null,8,["modelValue"])])),_:1}),(0,n.Wm)(m,{class:"scroller",items:g.filteredCustomers,"item-size":50,"key-field":"acc_no"},{default:(0,n.w5)((({item:e})=>[(0,n.Wm)(r.o,{class:"customer"},{default:(0,n.w5)((()=>[(0,n.Wm)(o.D,{cols:"6"},{default:(0,n.w5)((()=>[(0,n.Uk)((0,s.zw)(e.acc_no)+" - "+(0,s.zw)(e.name),1)])),_:2},1024)])),_:2},1024),d])),_:1},8,["items"])],64)}var g=l(6943),m={props:{site_info:{},user_info:{}},data(){return{searchQuery:"",showSearch:!0,open:!1,list:[],limit:5e3,loading:!0,listreceived:!1}},components:{},computed:{filteredCustomers(){let e=this.searchQuery.toLowerCase();this.listreceived||this.getCustomerList();let t=this.list.filter((t=>t.name.toLowerCase().includes(e)||t.acc_no.includes(e)));return t}},methods:{async getCustomerList(){this.loading=!0;let e=this.$api_url+"/customers/list";g.Z.get(e,{params:{limit:this.limit,query:this.searchQuery}}).then((e=>{this.list=e.data,this.listreceived=!0,this.loading=!1})).catch((e=>console.log(e)))}}},p=l(89);const h=(0,p.Z)(m,[["render",f],["__scopeId","data-v-3c9948e5"]]);var y=h},8521:function(e,t,l){l.d(t,{D:function(){return m}});l(7658),l(8099);var n=l(1138),s=l(7139),r=l(3396),o=l(320);const a=["sm","md","lg","xl","xxl"],i=(()=>a.reduce(((e,t)=>(e[t]={type:[Boolean,String,Number],default:!1},e)),{}))(),u=(()=>a.reduce(((e,t)=>(e["offset"+(0,s.kC)(t)]={type:[String,Number],default:null},e)),{}))(),c=(()=>a.reduce(((e,t)=>(e["order"+(0,s.kC)(t)]={type:[String,Number],default:null},e)),{}))(),d={col:Object.keys(i),offset:Object.keys(u),order:Object.keys(c)};function f(e,t,l){let n=e;if(null!=l&&!1!==l){if(t){const l=t.replace(e,"");n+=`-${l}`}return"col"===e&&(n="v-"+n),"col"!==e||""!==l&&!0!==l?(n+=`-${l}`,n.toLowerCase()):n.toLowerCase()}}const g=["auto","start","end","center","baseline","stretch"],m=(0,o.a)({name:"VCol",props:{cols:{type:[Boolean,String,Number],default:!1},...i,offset:{type:[String,Number],default:null},...u,order:{type:[String,Number],default:null},...c,alignSelf:{type:String,default:null,validator:e=>g.includes(e)},...(0,n.Q)()},setup(e,t){let{slots:l}=t;const n=(0,r.Fl)((()=>{const t=[];let l;for(l in d)d[l].forEach((n=>{const s=e[n],r=f(l,n,s);r&&t.push(r)}));const n=t.some((e=>e.startsWith("v-col-")));return t.push({"v-col":!n||!e.cols,[`v-col-${e.cols}`]:e.cols,[`offset-${e.offset}`]:e.offset,[`order-${e.order}`]:e.order,[`align-self-${e.alignSelf}`]:e.alignSelf}),t}));return()=>{var t;return(0,r.h)(e.tag,{class:n.value},null==(t=l.default)?void 0:t.call(l))}}})},6824:function(e,t,l){l.d(t,{o:function(){return _}});l(7658),l(8099);var n=l(1138),s=l(7139),r=l(3396),o=l(320);const a=["sm","md","lg","xl","xxl"],i=["start","end","center"],u=["space-between","space-around","space-evenly"];function c(e,t){return a.reduce(((l,n)=>(l[e+(0,s.kC)(n)]=t(),l)),{})}const d=[...i,"baseline","stretch"],f=e=>d.includes(e),g=c("align",(()=>({type:String,default:null,validator:f}))),m=[...i,...u],p=e=>m.includes(e),h=c("justify",(()=>({type:String,default:null,validator:p}))),y=[...i,...u,"stretch"],v=e=>y.includes(e),C=c("alignContent",(()=>({type:String,default:null,validator:v}))),w={align:Object.keys(g),justify:Object.keys(h),alignContent:Object.keys(C)},b={align:"align",justify:"justify",alignContent:"align-content"};function S(e,t,l){let n=b[e];if(null!=l){if(t){const l=t.replace(e,"");n+=`-${l}`}return n+=`-${l}`,n.toLowerCase()}}const _=(0,o.a)({name:"VRow",props:{dense:Boolean,noGutters:Boolean,align:{type:String,default:null,validator:f},...g,justify:{type:String,default:null,validator:p},...h,alignContent:{type:String,default:null,validator:v},...C,...(0,n.Q)()},setup(e,t){let{slots:l}=t;const n=(0,r.Fl)((()=>{const t=[];let l;for(l in w)w[l].forEach((n=>{const s=e[n],r=S(l,n,s);r&&t.push(r)}));return t.push({"v-row--no-gutters":e.noGutters,"v-row--dense":e.dense,[`align-${e.align}`]:e.align,[`justify-${e.justify}`]:e.justify,[`align-content-${e.alignContent}`]:e.alignContent}),t}));return()=>{var t;return(0,r.h)(e.tag,{class:["v-row",n.value]},null==(t=l.default)?void 0:t.call(l))}}})}}]);
//# sourceMappingURL=17.e124ae12.js.map

1
dist/js/17.e124ae12.js.map vendored Normal file

File diff suppressed because one or more lines are too long

2
dist/js/189.d48eb76c.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/js/189.d48eb76c.js.map vendored Normal file

File diff suppressed because one or more lines are too long

2
dist/js/287.3d3496d6.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/js/287.3d3496d6.js.map vendored Normal file

File diff suppressed because one or more lines are too long

2
dist/js/337.7acf902b.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/js/337.7acf902b.js.map vendored Normal file

File diff suppressed because one or more lines are too long

2
dist/js/463.dd239b65.js vendored Normal file
View file

@ -0,0 +1,2 @@
"use strict";(self["webpackChunkcmc_fe"]=self["webpackChunkcmc_fe"]||[]).push([[463],{4463:function(n,u,c){c.r(u),c.d(u,{default:function(){return b}});var e=c(3396);const t={class:"about"},r=(0,e._)("h1",null,"About",-1),l=(0,e._)("p",null,null,-1),s=[r,l];function a(n,u){return(0,e.wg)(),(0,e.iD)("div",t,s)}var f=c(89);const o={},i=(0,f.Z)(o,[["render",a]]);var b=i}}]);
//# sourceMappingURL=463.dd239b65.js.map

1
dist/js/463.dd239b65.js.map vendored Normal file
View file

@ -0,0 +1 @@
{"version":3,"file":"js/463.dd239b65.js","mappings":"+KACOA,MAAM,S,GACTC,EAAAA,EAAAA,GAAc,UAAV,SAAK,G,GACTA,EAAAA,EAAAA,GAAO,kB,GADPC,EACAC,G,kCAFFC,EAAAA,EAAAA,IAGM,MAHNC,EAGM,E,aCHR,MAAMC,EAAS,CAAC,EAGVC,GAA2B,OAAgBD,EAAQ,CAAC,CAAC,SAASE,KAEpE,O","sources":["webpack://cmc_fe/./src/views/AboutView.vue","webpack://cmc_fe/./src/views/AboutView.vue?d56f"],"sourcesContent":["<template>\n <div class=\"about\">\n <h1>About</h1>\n <p></p>\n </div>\n</template>\n","import { render } from \"./AboutView.vue?vue&type=template&id=7c1ef17b\"\nconst script = {}\n\nimport exportComponent from \"/srv/http/Development/Clients/cmc_app/public/cmc_fe/node_modules/vue-loader/dist/exportHelper.js\"\nconst __exports__ = /*#__PURE__*/exportComponent(script, [['render',render]])\n\nexport default __exports__"],"names":["class","_createElementVNode","_hoisted_2","_hoisted_3","_createElementBlock","_hoisted_1","script","__exports__","render"],"sourceRoot":""}

2
dist/js/553.fb74f1fb.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/js/553.fb74f1fb.js.map vendored Normal file

File diff suppressed because one or more lines are too long

274
dist/js/577.f88c2014.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/js/577.f88c2014.js.map vendored Normal file

File diff suppressed because one or more lines are too long

2
dist/js/803.d5052a11.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/js/803.d5052a11.js.map vendored Normal file

File diff suppressed because one or more lines are too long

2
dist/js/823.8ba5256b.js vendored Normal file
View file

@ -0,0 +1,2 @@
"use strict";(self["webpackChunkcmc_fe"]=self["webpackChunkcmc_fe"]||[]).push([[823],{1823:function(e,o,n){n.r(o),n.d(o,{default:function(){return t}});var c={mounted(){this.endSession(),localStorage.removeItem("access_token"),window.location.href="/"},methods:{endSession(){console.log("Logging out...")}}};const s=c;var t=s}}]);
//# sourceMappingURL=823.8ba5256b.js.map

1
dist/js/823.8ba5256b.js.map vendored Normal file
View file

@ -0,0 +1 @@
{"version":3,"file":"js/823.8ba5256b.js","mappings":"wJACA,OACIA,UACIC,KAAKC,aACLC,aAAaC,WAAW,gBACxBC,OAAOC,SAASC,KAAQ,GAC5B,EACAC,QAAS,CACLN,aACIO,QAAQC,IAAI,iBAEhB,ICRR,MAAMC,EAAc,EAEpB,O","sources":["webpack://cmc_fe/./src/components/LogOut.vue","webpack://cmc_fe/./src/components/LogOut.vue?62d9"],"sourcesContent":["<script>\nexport default {\n mounted() {\n this.endSession()\n localStorage.removeItem('access_token')\n window.location.href = \"/\"\n },\n methods: {\n endSession(){\n console.log(\"Logging out...\")\n // Do something here to remove the token from the database\n }\n\n }\n}\n</script>\n","import script from \"./LogOut.vue?vue&type=script&lang=js\"\nexport * from \"./LogOut.vue?vue&type=script&lang=js\"\n\nconst __exports__ = script;\n\nexport default __exports__"],"names":["mounted","this","endSession","localStorage","removeItem","window","location","href","methods","console","log","__exports__"],"sourceRoot":""}

2
dist/js/83.37aa53f6.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/js/83.37aa53f6.js.map vendored Normal file

File diff suppressed because one or more lines are too long

2
dist/js/app.a93abe60.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/js/app.a93abe60.js.map vendored Normal file

File diff suppressed because one or more lines are too long

14
dist/js/chunk-vendors.391950dc.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/js/chunk-vendors.391950dc.js.map vendored Normal file

File diff suppressed because one or more lines are too long

3
dist/js/html2pdf.bundle.min.js vendored Normal file

File diff suppressed because one or more lines are too long

2
dist/js/webfontloader.95ddae74.js vendored Normal file

File diff suppressed because one or more lines are too long

1
dist/js/webfontloader.95ddae74.js.map vendored Normal file

File diff suppressed because one or more lines are too long

19
jsconfig.json Normal file
View file

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

62
package.json Normal file
View file

@ -0,0 +1,62 @@
{
"name": "cmc_fe",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@mdi/font": "5.9.55",
"@vuepic/vue-datepicker": "^3.6.4",
"axios": "^1.2.2",
"core-js": "^3.8.3",
"moment": "^2.29.4",
"roboto-fontface": "*",
"sass": "^1.57.1",
"sass-loader": "^13.2.0",
"scss": "^0.2.4",
"vue": "^3.2.13",
"vue-datepicker": "^1.3.0",
"vue-meta": "^2.4.0",
"vue-router": "^4.0.3",
"vue-virtual-scroller": "^2.0.0-beta.7",
"vue3-html2pdf": "^1.1.2",
"vue3-print-nb": "^0.1.4",
"vuetify": "^3.0.0-beta.0",
"webfontloader": "^1.0.0"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"eslint": "^7.32.0",
"eslint-plugin-vue": "^8.0.3",
"vue-cli-plugin-vuetify": "~2.5.8",
"webpack-plugin-vuetify": "^2.0.0-alpha.0"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "@babel/eslint-parser"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie 11"
]
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
public/images/cmc-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 791 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 941 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 B

16
public/index.html Normal file
View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
</body>
</html>

3
public/js/html2pdf.bundle.min.js vendored Normal file

File diff suppressed because one or more lines are too long

12
src/App.vue Normal file
View file

@ -0,0 +1,12 @@
<template>
<CMCApp />
</template>
<script>
import CMCApp from './CMCApp.vue'
export default {
components: {
CMCApp
}
}
</script>

99
src/CMCApp.vue Normal file
View file

@ -0,0 +1,99 @@
<template>
<v-app>
<MyNav :user="user" :site_info="site_info" />
<v-main class="ma-4">
<router-view :site_info="site_info" :user_info="user_info"></router-view>
</v-main>
<v-footer>
<sub>{{ site_info.name }} v{{ site_info.version }}</sub>
</v-footer>
</v-app>
</template>
<script>
import MyNav from './components/MyNav.vue'
import axios from 'axios'
export default {
name: 'App',
components: {
MyNav,
},
data() {
return {
site_info: {
name: "Loading...",
features: {}
},
user: {
first_name: "",
last_name: "",
email: "",
token: "",
logged_in: false
},
user_info: {}
}
},
watch: {
'user.logged_in'(val) {
console.log("Login status is " + val)
},
'site_info.name'() {
document.title = this.site_info.name
}
},
methods: {
checkLoginStatus() {
let url = this.$api_url + "/users/check_login"
console.log("Checking login status...")
axios
.post(url)
.then(resp => {
this.user.logged_in = resp.data.logged_in
this.user.first_name = resp.data.user.first_name
if (this.user.logged_in) {
console.log("Logged in.")
if (window.location.pathname == "/login") {
this.$router.push("/")
}
this.getUserInfo()
} else {
console.log("Not logged in.")
this.$router.push("/login")
}
})
.catch(error => {
console.log("Error checking login status..." + error)
this.$router.push("/login")
})
},
async getSiteInfo() {
axios
.get(this.$api_url + "/info")
.then(response => {this.site_info = response.data})
.catch(error => (console.log(error)))
},
async getUserInfo() {
let url = this.$api_url + "/users/info"
console.log(url)
axios.get(url)
.then(resp => {
this.user_info = resp.data
})
.catch(err => {
console.log(err)
})
}
},
created() {
this.getSiteInfo()
if (window.location.pathname != "/login") {
this.checkLoginStatus()
}
}
}
</script>

31
src/CommonMethods.vue Normal file
View file

@ -0,0 +1,31 @@
<script>
import moment from 'moment'
export default {
methods:{
formatDate(d,f) {
return moment(String(d)).format(f)
},
formatNumber(num,chars) {
return parseFloat(num).toFixed(chars)
},
getDateNow() {
let now = new Date()
return now.toLocaleString('en-GB', {
day: '2-digit',
month: 'long',
year: 'numeric'
})
},
getTimeNow() {
let now = new Date()
return now.toLocaleString('en-GB', {
month: 'long',
day: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'})
},
}
}
</script>

84
src/assets/css/app.css Normal file
View file

@ -0,0 +1,84 @@
[v-cloak] {
display:none;
}
.lastrefresh {
font-size:10px;
float:right;
text-align:right;
margin-bottom:0;
}
.loading {
margin:auto;
display:block;
}
.title-bar {
background:#ffae00;
color:#000;
}
.menu-icon::after {
background:#000;
box-shadow:0 7px 0 #000,0 14px 0 #000;
}
main {
margin-top:2rem;
padding-top:2rem;
}
.icon, .icon-2 {
max-height:2rem;
}
.icon-1 {
max-height:1rem;
}
table td {
padding-right:0.4em;
padding-left:0.4em;
padding-top:0;
padding-bottom:0;
}
table .button {
margin-top:0.2rem;
margin-bottom:0.2rem;
}
.off-canvas {
background:#ffffee
}
#editModal {
height:750px;
}
img.img-small {
max-height:16px;
}
table tr.at_risk {
background:lightpink;
}
table tr.at_risk:hover {
background:pink !important;
}
.scrollable {
max-height:90vh;
overflow-y:scroll;
}
.scrollable-50 {
max-height:50vh;
}
.inactive {
background:lightgrey;
color: grey;
}
.scroller {
border: 1px dotted #333;
}
.scroller {
height: 600px;
}
.item {
height: 138px;
overflow-y:hidden;
padding: 0 12px;
margin-bottom:2px;
display: flex;
align-items: center;
border-bottom: 1px solid #000;
}

1
src/assets/css/app.scss Normal file
View file

@ -0,0 +1 @@
@import './app.css'

1
src/assets/css/foundation.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,69 @@
p,address,ul {
font-size:1rem;
margin-bottom:0.8rem;
}
body {
}
.button-container {
margin-left:8mm;
}
.page {
max-width:210mm;
min-width:210mm;
max-height:296mm;
min-height:296mm;
}
.pdf-scope {
padding:8mm;
}
h1,h2,h3,h4,h5,hr {
margin:0;
padding:0;
}
hr {
margin-bottom: 8px;
border-color:black;
}
hr.bold {
border-width:4px;
}
header img {
max-width:250px;
}
header img.smaller {
max-width:200px;
}
ul {
list-style-type: none;
}
ul li {
margin-left: 1rem;
}
.letter {
margin-top:140px;
}
.signature {
min-height:60px;
}
.text-bold {
font-weight: bold;
}
.text-underline {
text-decoration: underline;
}
.text-italic {
font-style: italic;
}
.gap-before {
margin-left:2em;
}
.gap-12 {
margin-left:12em;
}
.red-text {
color:red;
}
.b_line {
border-bottom: 1px solid black;
}

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

6
src/assets/logo.svg Normal file
View file

@ -0,0 +1,6 @@
<svg width="488" height="424" viewBox="0 0 488 424" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M249.126 95.017L151.843 263.694L243.959 423.473L365.966 211.973L487.918 0.473206H303.629L249.126 95.017Z" fill="#1697F6"/>
<path d="M122.007 211.973L128.396 223.096L219.402 65.2635L256.793 0.473206H243.959H0L122.007 211.973Z" fill="#AEDDFF"/>
<path d="M303.629 0.473206C349.743 152.355 243.959 423.473 243.959 423.473L151.843 263.694L303.629 0.473206Z" fill="#1867C0"/>
<path d="M256.793 0.473206C62.5042 0.473206 128.397 223.096 128.397 223.096L256.793 0.473206Z" fill="#7BC6FF"/>
</svg>

After

Width:  |  Height:  |  Size: 598 B

31
src/common.js Normal file
View file

@ -0,0 +1,31 @@
import moment from 'moment';
export default {
getAuthToken() {
let tok = localStorage.getItem('access_token')
console.log(tok)
return tok
},
formatDate(dateVal, format) {
if (dateVal) {
return moment(String(dateVal)).format(format)
}
},
getDateNow() {
let now = new Date()
return now.toLocaleString('en-GB', {
day: '2-digit',
month: 'long',
year: 'numeric'
})
},
getTimeNow() {
let now = new Date()
return now.toLocaleString('en-GB', {
month: 'long',
day: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'})
},
}

16
src/components/LogOut.vue Normal file
View file

@ -0,0 +1,16 @@
<script>
export default {
mounted() {
this.endSession()
localStorage.removeItem('access_token')
window.location.href = "/"
},
methods: {
endSession(){
console.log("Logging out...")
// Do something here to remove the token from the database
}
}
}
</script>

92
src/components/MyNav.vue Normal file
View file

@ -0,0 +1,92 @@
<template>
<v-app-bar
color="yellow">
<v-app-bar-nav-icon v-if="user.logged_in" variant="text" @click.stop="drawer = !drawer"></v-app-bar-nav-icon>
<v-toolbar-title>{{ site_info.name }}</v-toolbar-title>
<v-spacer></v-spacer>
<v-btn variant="text" v-if="user.logged_in">Hi {{ user.first_name }}</v-btn>
</v-app-bar>
<v-navigation-drawer
v-if="user.logged_in"
v-model="drawer"
theme="dark">
<v-list>
<template v-for="item in items" v-bind:key="item.title">
<template v-if="item.children">
<v-list-item :title="item.title">
<v-list>
<v-list-item v-for="citem in item.children"
v-bind:key="citem.title"
:title="citem.title"
:to="citem.value">
</v-list-item>
</v-list>
</v-list-item>
</template>
<template v-else>
<v-list-item :title="item.title" :to="item.value">
</v-list-item>
</template>
</template>
</v-list>
</v-navigation-drawer>
</template>
<script>
export default{
name: "MyNav",
props: {
site_info: {},
user: {
logged_in: false,
first_name: "null"
}
},
watch: {
'user.logged_in'(isLoggedIn) {
if (isLoggedIn) {
this.items = this.get_menu()
} else {
this.items = []
}
}
},
data(){
return {
items: [],
drawer: null
}
},
created() {
if (window.location.pathname != "/login") {
this.items = this.get_menu()
}
},
methods:{
get_menu() {
let items = []
items.push({title: "Dashboard", value:"/about"})
items.push({title: "Customers",
value:"/customers",
children:[{title:"List",
value:"/customers/list"},
{title:"Contracts",
value:"/customers/contracts/list"},
{title:"Complaints",
value:"/customers/complaints/list"},
{title:"Medicated Feeds",
value:"/customers/medicated-feeds/list"}
]
})
items.push({title: "Sales Orders",
value:"/sop",
children:[{title:"Printed",
value:"/sop/printed"}]
})
items.push({title: "Logout", value:"/logout"})
return items
}
}
}
</script>

View file

@ -0,0 +1,33 @@
<template>
<v-container class="button-container">
<v-btn color="primary" @click="generatePdf()" class="mr-2">Create PDF</v-btn>
<v-btn color="grey" v-print="printObj">Print</v-btn>
</v-container>
</template>
<script>
import html2pdf from 'html2pdf.js';
export default {
props: {
scope: String
},
data() {
return {
printObj: {
id: this.scope,
previewTitle: "Report Print",
popTitle: "Report Print Pop"
}
}
},
methods:{
async generatePdf() {
html2pdf(document.getElementById(this.scope))
}
}
}
</script>
<style>
.button-container {
margin-top:1rem;
}
</style>

View file

@ -0,0 +1,32 @@
<template>
<PrintButtons :scope="scope" />
<v-container>
<v-card class="a4 page">
<div :id="scope" class="pdf-scope">
<slot></slot>
</div>
</v-card>
</v-container>
</template>
<script>
import PrintButtons from './PrintButtons.vue'
export default {
props: {
scope: String
},
components: {
PrintButtons
},
data() {
return {
}
}
}
</script>
<style>
@media print {
.pdf-scope {
padding:0;
}
}
</style>

View file

@ -0,0 +1,18 @@
<template>
<v-responsive
class="mx-8"
max-width="500"
>
<v-text-field
clearable
label="Search"
variant="outlined"
density="compact"
append-inner-icon="mdi-magnify"></v-text-field>
</v-responsive>
</template>
<script>
export default {
name: 'SearchBox',
}
</script>

View file

@ -0,0 +1,7 @@
<script>
import axios from 'axios';
export default {
methods: {
}
}
</script>

45
src/main.js Normal file
View file

@ -0,0 +1,45 @@
import { createApp } from 'vue'
import App from './App.vue'
import vuetify from './plugins/vuetify'
import { loadFonts } from './plugins/webfontloader'
import router from './router'
import print from 'vue3-print-nb'
import Datepicker from '@vuepic/vue-datepicker'
import '@vuepic/vue-datepicker/dist/main.css'
import './assets/css/app.scss'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import axios from 'axios'
import VueVirtualScroller from 'vue-virtual-scroller'
axios.defaults.headers.common['X-Authentication'] = `Bearer ${localStorage.getItem('access_token')}`;
loadFonts()
const app = createApp(App).use(router)
.use(vuetify)
.use(print)
.use(VueVirtualScroller)
.component('DatePicker', Datepicker)
var url = window.location.protocol + "//" + window.location.host + "/api/v1"
let site_info
console.log("URL :" + url)
axios.get(url + "/info")
.then(resp => {
console.log("URL :" + url)
site_info = resp.data
app.config.globalProperties.$api_url = url
app.config.globalProperties.$site_info = site_info
app.mount('#app')
})
.catch(err => {
console.log("Error : " + err)
url = "http://localhost:3000/api/v1"
console.log("Trying to use URL : " + url)
app.config.globalProperties.$api_url = url
app.config.globalProperties.$site_info = site_info
app.mount('#app')
})

10
src/plugins/vuetify.js Normal file
View file

@ -0,0 +1,10 @@
// Styles
import '@mdi/font/css/materialdesignicons.css'
import 'vuetify/styles'
// Vuetify
import { createVuetify } from 'vuetify'
export default createVuetify(
// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
)

View file

@ -0,0 +1,15 @@
/**
* plugins/webfontloader.js
*
* webfontloader documentation: https://github.com/typekit/webfontloader
*/
export async function loadFonts () {
const webFontLoader = await import(/* webpackChunkName: "webfontloader" */'webfontloader')
webFontLoader.load({
google: {
families: ['Roboto:100,300,400,500,700,900&display=swap'],
},
})
}

65
src/router/index.js Normal file
View file

@ -0,0 +1,65 @@
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const AboutView = () => import('../views/AboutView.vue')
const LoginPage = () => import('../views/LoginPage.vue')
const LogOut = () => import('../components/LogOut.vue')
const CustomerList = () => import('../views/customers/CustomerList.vue')
const ContractList = () => import('../views/contracts/ContractList.vue')
const ComplaintsList = () => import('../views/complaints/ComplaintsList.vue')
const MedFeedsList = () => import('../views/medfeeds/MedFeedsList.vue')
const SOPPrintedList = () => import('../views/salesorders/SOPPrinted.vue')
const routes = [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
component: AboutView
},
{
path: '/login',
name: 'login',
component: LoginPage
},
{
path: '/logout',
name: 'logout',
component: LogOut
},
{
path: '/customers/list',
name: 'customerlist',
component: CustomerList
},
{
path: '/customers/contracts/list',
name: 'contractlist',
component: ContractList
},
{
path: '/customers/medicated-feeds/list',
name: 'medfeedslist',
component: MedFeedsList
},
{
path: '/customers/complaints/list',
name: 'complaintslist',
component: ComplaintsList
},
{
path: '/sop/printed',
name: 'sopprinted',
component: SOPPrintedList
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router

6
src/views/AboutView.vue Normal file
View file

@ -0,0 +1,6 @@
<template>
<div class="about">
<h1>About</h1>
<p></p>
</div>
</template>

15
src/views/HomeView.vue Normal file
View file

@ -0,0 +1,15 @@
<template>
<div class="home">
Dashboard
</div>
</template>
<script>
// @ is an alias to /src
export default {
name: 'HomeView',
components: {
}
}
</script>

57
src/views/LoginPage.vue Normal file
View file

@ -0,0 +1,57 @@
<template>
<v-card width="500" height="300"
class="mx-auto my-12"
title="Welcome!"
subtitle="Please Log In">
<v-form>
<v-responsive class="mx-auto" max-width="475">
<v-text-field label="Email" clearable v-model="user.email"></v-text-field>
<v-text-field label="Password" v-model="user.password" clearable
type="password"></v-text-field>
<v-btn color="blue" @click="submitLogin">Login</v-btn>
</v-responsive>
</v-form>
</v-card>
</template>
<script>
import axios from 'axios'
export default {
name: 'LoginPage',
data() {
return {
user: {
email: "",
password: ""
}
}
},
methods: {
submitLogin() {
let url = this.$api_url + "/users/login"
console.log("Logging in...")
axios
.post(url, {
email: this.user.email,
password: this.user.password
})
.then(resp => {
let data = resp.data
if (data.logged_in) {
let token = data.token
localStorage.setItem('access_token', token.content)
let tok = localStorage.getItem('access_token')
if (tok.content != "") {
this.user.logged_in = true
console.log("Logged in")
window.location.href = '/'
}
}
})
.catch(error => { console.log(error) })
}
}
}
</script>

View file

@ -0,0 +1,208 @@
<template>
<h3>Complaints</h3>
<v-tabs v-model="tab" fixed-tabs>
<v-tab title="List" v-model="list" />
<v-tab title="Edit" v-model="edit" v-if="edit" />
<v-tab title="Report" v-model="report" v-if="report" />
</v-tabs>
<v-window v-model="tab">
<v-window-item v-model="list">
<v-responsive
max-width="500"
>
<v-text-field
clearable
label="Search"
variant="outlined"
v-model="searchQuery"
density="compact"
append-inner-icon="mdi-magnify"></v-text-field>
<v-switch color="blue" label="Show Only Active" v-model="showActive"></v-switch>
<v-btn v-if="site_info.features.addcomplaint" color="warning">+ Add</v-btn>
</v-responsive>
<v-table>
<thead>
<tr>
<th>Comp No</th>
<th>Date</th>
<th>Order</th>
<th>Acc No</th>
<th>Name</th>
<th>Reason</th>
<th>Active</th>
<th></th>
</tr>
</thead>
<tbody>
<tr v-if="loading">
<td colspan=7>
<img class="loading" src="/images/icons/loading.gif"/>
</td>
</tr>
<template v-for="complaint in filteredComplaints" :key="complaint.id">
<tr :class="{ at_risk : complaint.at_risk, cust_at_risk: complaint.customer.at_risk }">
<td>{{ complaint.id }}</td>
<td>{{ complaint.complaint_date }}</td>
<td>{{ complaint.sop.doc_no }}</td>
<td>{{ complaint.customer.acc_no }}</td>
<td>{{ complaint.customer.name }}
</td>
<td>{{ complaint.reason }}</td>
<td>
<img class="icon" v-if="complaint.active" v-bind:alt="complaint.active" v-bind:title="complaint.active" src="/images/icons/Live.png"/>
</td>
<td>
<v-btn @click="showInfo(complaint)">
<span v-if="complaint.info_shown">
Hide
</span>
<span v-else>
View
</span>
</v-btn>
</td>
</tr>
<tr v-if="complaint.info_shown">
<template v-if="complaint.info">
<template v-if="complaint.info_loaded">
<td colspan=4>
{{ complaint.info.comments }}
<br />
<sub>- {{ complaint.info.added_by }}</sub>
</td>
<td colspan=2>
<v-container>
<v-form :disabled="complaint.info.loading">
<v-row dense nogutters>
<v-checkbox label="1" type="checkbox" v-model="complaint.info.one" @change="changeComplaintCheckbox(complaint)" />
<v-checkbox label="2" v-model="complaint.info.two" @change="changeComplaintCheckbox(complaint)"/>
<v-checkbox label="3" v-model="complaint.info.three" @change="changeComplaintCheckbox(complaint)"/>
</v-row>
<v-row dense nogutters>
<v-checkbox label="At Risk" v-model="complaint.at_risk" @change="changeComplaintCheckbox(complaint)" />
<v-checkbox label="Permanent" v-model="complaint.info.permanent" @change="changeComplaintCheckbox(complaint)" />
</v-row>
</v-form>
</v-container>
</td>
<td>
<img v-if="complaint.info.loading" class="loading" src="/images/icons/loading.gif"/>
<template v-else>
<v-btn v-if="site_info.features.editcomplaint" color="warning">Edit</v-btn>
</template>
</td>
</template>
</template>
<template v-else>
<td colspan=6>
<img class="loading" src="/images/icons/loading.gif"/>
</td>
</template>
</tr>
</template>
</tbody>
</v-table>
</v-window-item>
</v-window>
</template>
<script>
import axios from 'axios'
export default {
props: {
site_info:{},
},
data() {
return {
tab: "list",
list: [],
listreceived: false,
showActive: true,
loading: true,
limit: 300,
searchQuery: "",
edit: false,
report: false
}
},
computed: {
filteredComplaints() {
let query = this.searchQuery.toLowerCase()
if (!this.listreceived){
this.getComplaintsList()
}
let clist = this.list.filter(q =>
q.customer.name.toLowerCase().includes(query) ||
q.customer.acc_no.includes(query) ||
q.id == query ||
q.reason.toLowerCase().includes(query)
)
if (this.showActive) {
clist = clist.filter(q =>
q.active == true
)
}
return clist
}
},
methods: {
async getComplaintsList(){
this.loading = true
let url = this.$api_url + "/customers/complaints/list"
console.log("Getting Complaint list...")
axios.get(url,{
params: {
limit: this.limit,
query: this.searchQuery
}
}).then(resp => {
this.list = resp.data
this.loading = false
this.listreceived = true
})
},
async getComplaintInfo(complaint) {
complaint.info.loading = true
let url = this.$api_url + "/customers/complaints/" + complaint.id + "/info"
console.log("Getting Complaint Info...")
axios.get(url)
.then(resp =>{
complaint.info = resp.data
complaint.info.loading = false
})
},
async showInfo(complaint) {
console.log(complaint.id)
complaint.info_loaded = false
complaint.info_shown = !complaint.info_shown
if (complaint.info_shown) {
complaint.info = await this.getComplaintInfo(complaint)
complaint.info_loaded = true
}
},
async changeComplaintCheckbox(complaint) {
let set = await this.setComplaintStatus(complaint)
if (complaint.at_risk && set && complaint.info.one && complaint.info.two && complaint.info.three) {
console.log("Contract no longer at risk")
complaint.at_risk = false
}
},
async setComplaintStatus(complaint) {
complaint.info.loading = true
let url = this.$api_url + "/customers/complaints/" + complaint.id + "/info/set"
console.log("Getting Complaint Info...")
axios.post(url, {
one: complaint.info.one,
two: complaint.info.two,
three: complaint.info.three,
at_risk: complaint.at_risk,
permanent: complaint.info.permanent
}).then(resp => {
console.log(resp.data)
this.getComplaintInfo(complaint)
complaint.info_loaded = true
})
return true
},
}
}
</script>

View file

@ -0,0 +1,162 @@
<template>
<v-card title="Edit Contract" :subtitle="'Contract : ' + contract.no">
<v-card-text>
<v-container>
<v-row>
<v-col cols="6">
<v-text-field label="Customer" v-model="contract.customer.name" readonly></v-text-field>
<v-text-field type="number" label="Tonnage Per Month" v-model="contract.tonnage_per_month"></v-text-field>
</v-col>
<v-col cols="6">
<label>
Start Date
<DatePicker v-model="contract.start_date" format="dd/MM/yyyy" />
</label><br />
<label>
Finish Date
<DatePicker v-model="contract.finish_date" format="dd/MM/yyyy" />
</label><br />
</v-col>
</v-row>
<template v-for="p in contract.products" :key="p.code">
<v-row>
<v-col cols="6">
<v-autocomplete v-model="p.code"
v-model:search="product_search"
:loading="products_loading"
:items="products"
cache-items
hide-no-data
hide-details
solo-inverted
label="Code"
no-data-text="No Products Found"
item-title="code"
item-value="code" ></v-autocomplete>
</v-col>
<v-col cols="4">
<v-text-field type="number" label="Price" v-model="p.price"></v-text-field>
</v-col>
<v-col cols="2">
<v-btn size="small" color="error" title="Remove" variant="plain" icon @click="contract.products.pop()"><v-icon>mdi-minus</v-icon></v-btn>
</v-col>
</v-row>
</template>
<v-row>
<v-col cols="4">
<v-btn @click="contract.products.push({})" v-if="contract.products.length < 4">+ Add Product</v-btn>
</v-col>
</v-row>
<v-row>
<v-col cols="6">
<label>
Agree Date
<DatePicker v-model="contract.agree_date" format="dd/MM/yyyy" />
</label>
</v-col>
<v-col cols="12">
<v-textarea rows=3 label="Comments" v-model="contract.comments"></v-textarea>
<v-textarea rows=3 label="Office Comments" v-model="contract.office_comments"></v-textarea>
</v-col>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-btn color="red-darken-1"
variant="text"
@click="saveContract(selected_contract)">Save</v-btn>
<v-spacer></v-spacer>
<v-btn color="blue-darken-1"
variant="text"
@click="close">Close</v-btn>
</v-card-actions>
</v-card>
</template>
<script>
import axios from 'axios'
import DatePicker from '@vuepic/vue-datepicker'
export default {
props: {
setcontract: {
no: Number,
customer: {
acc_no: String,
name: String,
at_risk: Boolean,
},
products: [{
code: String,
name: String,
price: Number,
}],
start_date: String,
finish_date: String,
agree_date: String,
tonnage_per_month: Number,
comments: String,
office_comments: String,
active: Boolean
}
},
components: {
DatePicker
},
watch: {
setcontract(newval) {
this.contract = newval
},
product_search(val) {
if (val && val.length > 1) {
this.searchProducts(val)
}
}
},
data() {
return {
contract: this.setcontract,
dialog: this.opendialog,
saving: false,
product_search: null,
products_loading: false,
products: [],
}
},
methods: {
close() {
this.$emit('closetab','list')
},
async saveContract(){
this.saving = true
let url = this.$api_url + "/customers/contracts/" + this.contract.no + "/save"
console.log("Saving Contract : ", this.contract.no)
console.log(this.contract)
axios.post(url, {
contract: this.contract
}).then(resp => {
console.log("Saved Contract : " + JSON.stringify(resp.data))
this.saving = false
this.$emit('contractupdate', resp.data)
}).catch(err => {
console.log(err)
this.saving = false
})
},
searchProducts(code) {
let url = this.$api_url + "/products/search/" + code
console.log(url)
axios.get(url)
.then(resp => {
console.log(resp)
this.products = resp.data
})
.catch(err => {
console.log(err)
this.products = [{code:"NoProductsFound", name:"No Products Found"}]
})
},
productCodeName(p) {
return p.code + ' - ' + p.name
}
}
}
</script>

Some files were not shown because too many files have changed in this diff Show more