Compare commits
115 commits
Author | SHA1 | Date | |
---|---|---|---|
5817f648b6 | |||
8a594cc653 | |||
a032ff6eea | |||
5827002154 | |||
499c92448a | |||
b7437298d1 | |||
33176f1756 | |||
0ab97f2afc | |||
d22bb18898 | |||
c741f58f01 | |||
88976c0c7b | |||
e5ca7ae946 | |||
fe7daccadd | |||
e2cff40cd7 | |||
5fed496a36 | |||
a1426eec60 | |||
c5805185c7 | |||
86378a58c3 | |||
c0d1df0944 | |||
d357201b06 | |||
218baab58a | |||
fe136d9fd9 | |||
b7d6eb1987 | |||
d0a6e6fa5b | |||
59f77df2a2 | |||
16ae90d0c5 | |||
1e203291f1 | |||
1e0f505c67 | |||
a96164236d | |||
0567bd9465 | |||
ded985353a | |||
28e97f1c7f | |||
eef3f9816c | |||
1e946827c1 | |||
0a3497a192 | |||
1ab4e9ecae | |||
d565b4ab6a | |||
2acb73802a | |||
9e77272535 | |||
68c2109438 | |||
2c5457673c | |||
7b61ccf99b | |||
64fcfff373 | |||
43ab44794c | |||
8195e81657 | |||
d4f7fa7337 | |||
342bbfc9bc | |||
8799da8440 | |||
a3419e99a3 | |||
ed5010637f | |||
1e69f92af6 | |||
d0f521adac | |||
193ed07ca4 | |||
3a22597410 | |||
696756aa63 | |||
a23708c992 | |||
ab2f730086 | |||
16f636a387 | |||
42a3324d04 | |||
56f9be0a21 | |||
676e44c794 | |||
d86fde9d19 | |||
31bc1e4eae | |||
0bf0c8c8ab | |||
9f073ddba3 | |||
a46eebe5d3 | |||
623b107f65 | |||
5f021f8d5c | |||
0caeb1d859 | |||
814fe233e4 | |||
bd99364728 | |||
c5fd049d07 | |||
b78da566d9 | |||
35c7f00ba3 | |||
834ac4201f | |||
b6dfa61f04 | |||
b19a24842d | |||
92fc782ca5 | |||
6991d1adfd | |||
28fc9e9629 | |||
710eadc49b | |||
b80bba3d3d | |||
a5b3970d7f | |||
bdf42122f7 | |||
c4d3ccf66a | |||
207035bd8d | |||
9ab4f2a6db | |||
6f82189a42 | |||
351e4e3e14 | |||
c9829b5d76 | |||
c50feca092 | |||
81431f1896 | |||
bb0f26c1fc | |||
d941ff9663 | |||
c10d69a54d | |||
b3338fb1a5 | |||
ed1a240777 | |||
22f368bf22 | |||
4104b28f6f | |||
0f1b107904 | |||
26ae32aa52 | |||
7efba6ab7c | |||
5cf3f65fb8 | |||
a62d4ce25f | |||
9f35e4f3a1 | |||
784ab71557 | |||
2dfd7007c9 | |||
494c25e8ec | |||
527d3590b3 | |||
0a761950b1 | |||
ce965bb01f | |||
92b3901eb7 | |||
ea87461d73 | |||
281dec65fe | |||
7c2046f945 |
1
.gitignore
vendored
|
@ -21,3 +21,4 @@ pnpm-debug.log*
|
||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
|
|
BIN
cmc_fe.tar.gz
Normal file
17
create-release.sh
Executable file
|
@ -0,0 +1,17 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
method="patch"
|
||||||
|
if [[ $1 != "" ]]; then
|
||||||
|
method=$1
|
||||||
|
fi
|
||||||
|
pnpm version $method
|
||||||
|
if [[ $? != 0 ]]; then
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
pnpm run build
|
||||||
|
if [[ $? == 0 ]]; then
|
||||||
|
tar czf cmc_fe.tar.gz dist
|
||||||
|
git add . cmc_fe.tar.gz
|
||||||
|
git commit -m "updated cmc_fe.tar.gz"
|
||||||
|
git push
|
||||||
|
fi
|
12709
package-lock.json
generated
Normal file
44
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "cmc_fe",
|
"name": "cmc_fe",
|
||||||
"version": "0.1.0",
|
"version": "0.1.10",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
|
@ -9,35 +9,37 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdi/font": "5.9.55",
|
"@mdi/font": "5.9.55",
|
||||||
"@vuepic/vue-datepicker": "^3.6.4",
|
"@vuepic/vue-datepicker": "^3.6.8",
|
||||||
"axios": "^1.2.2",
|
"animate.css": "^4.1.1",
|
||||||
"core-js": "^3.8.3",
|
"axios": "^1.3.4",
|
||||||
|
"core-js": "^3.30.0",
|
||||||
|
"html2pdf.js": "^0.10.1",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"roboto-fontface": "*",
|
"roboto-fontface": "^0.10.0",
|
||||||
"sass": "^1.57.1",
|
"sass": "^1.60.0",
|
||||||
"sass-loader": "^13.2.0",
|
"sass-loader": "^13.2.2",
|
||||||
"scss": "^0.2.4",
|
"scss": "^0.2.4",
|
||||||
"vue": "^3.2.13",
|
"vue": "^3.2.47",
|
||||||
"vue-datepicker": "^1.3.0",
|
"vue-datepicker": "^1.3.0",
|
||||||
"vue-meta": "^2.4.0",
|
"vue-meta": "^2.4.0",
|
||||||
"vue-router": "^4.0.3",
|
"vue-router": "^4.1.6",
|
||||||
"vue-virtual-scroller": "^2.0.0-beta.7",
|
"vue-splash": "^1.2.1",
|
||||||
"vue3-html2pdf": "^1.1.2",
|
"vue-virtual-scroller": "2.0.0-beta.8",
|
||||||
"vue3-print-nb": "^0.1.4",
|
"vue3-print-nb": "^0.1.4",
|
||||||
"vuetify": "^3.0.0-beta.0",
|
"vuetify": "^3.1.12",
|
||||||
"webfontloader": "^1.0.0"
|
"webfontloader": "^1.6.28"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.12.16",
|
"@babel/core": "^7.21.4",
|
||||||
"@babel/eslint-parser": "^7.12.16",
|
"@babel/eslint-parser": "^7.21.3",
|
||||||
"@vue/cli-plugin-babel": "~5.0.0",
|
"@vue/cli-plugin-babel": "~5.0.8",
|
||||||
"@vue/cli-plugin-eslint": "~5.0.0",
|
"@vue/cli-plugin-eslint": "~5.0.8",
|
||||||
"@vue/cli-plugin-router": "~5.0.0",
|
"@vue/cli-plugin-router": "~5.0.8",
|
||||||
"@vue/cli-service": "~5.0.0",
|
"@vue/cli-service": "~5.0.8",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^7.32.0",
|
||||||
"eslint-plugin-vue": "^8.0.3",
|
"eslint-plugin-vue": "^8.7.1",
|
||||||
"vue-cli-plugin-vuetify": "~2.5.8",
|
"vue-cli-plugin-vuetify": "~2.5.8",
|
||||||
"webpack-plugin-vuetify": "^2.0.0-alpha.0"
|
"webpack-plugin-vuetify": "^2.0.1"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"root": true,
|
"root": true,
|
||||||
|
|
7366
pnpm-lock.yaml
Normal file
BIN
public/images/favicon.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/images/favicon180.png
Normal file
After Width: | Height: | Size: 9.1 KiB |
BIN
public/images/favicon192.png
Normal file
After Width: | Height: | Size: 9.3 KiB |
BIN
public/images/favicon270.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
public/images/favicon32.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
public/images/favicon64.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
public/images/favicon96.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
public/images/logo_fields.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
|
@ -4,8 +4,12 @@
|
||||||
<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">
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
<link rel="icon" href="<%= BASE_URL %>images/logo_fields.png">
|
||||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||||
|
<link rel="icon" href="<%= BASE_URL %>images/favicon32.png" sizes="32x32" />
|
||||||
|
<link rel="icon" href="<%= BASE_URL %>images/favicon192.png" sizes="192x192" />
|
||||||
|
<link rel="apple-touch-icon" href="<%= BASE_URL %>images/favicon180.png" />
|
||||||
|
<meta name="msapplication-TileImage" content="<%= BASE_URL %>images/favicon270.png" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>
|
<noscript>
|
||||||
|
|
34
src/App.vue
|
@ -1,12 +1,40 @@
|
||||||
<template>
|
<template>
|
||||||
<CMCApp />
|
<CMCApp @ready="isReady" :class="{ fadein : !isLoading }" />
|
||||||
|
<LoadingScreen :isLoading="isLoading" />
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import CMCApp from './CMCApp.vue'
|
import CMCApp from './CMCApp.vue'
|
||||||
|
import LoadingScreen from "./components/LoadingScreen.vue"
|
||||||
export default {
|
export default {
|
||||||
components: {
|
data(){
|
||||||
CMCApp
|
return {
|
||||||
|
isLoading: true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
CMCApp,
|
||||||
|
LoadingScreen
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
isReady() {
|
||||||
|
this.isLoading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<style>
|
||||||
|
.fadein {
|
||||||
|
animation: fadein 1s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadein {
|
||||||
|
from {
|
||||||
|
opacity:0;
|
||||||
|
visibility:hidden;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity:1;
|
||||||
|
visibility:visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<v-app>
|
<v-app>
|
||||||
<MyNav :user="user" :site_info="site_info" />
|
<MyNav :user="user" :site_info="site_info" v-if="!isLoading" />
|
||||||
<v-main class="ma-4">
|
<v-main class="ma-4">
|
||||||
|
<v-banner v-if="!site_info.backend_connected"
|
||||||
|
icon="mdi-exclamation"
|
||||||
|
color="error"
|
||||||
|
text="Cannot connect to the data service. Please contact support." >
|
||||||
|
</v-banner>
|
||||||
<router-view :site_info="site_info" :user_info="user_info"></router-view>
|
<router-view :site_info="site_info" :user_info="user_info"></router-view>
|
||||||
</v-main>
|
</v-main>
|
||||||
<v-footer>
|
|
||||||
<sub>{{ site_info.name }} v{{ site_info.version }}</sub>
|
|
||||||
</v-footer>
|
|
||||||
</v-app>
|
</v-app>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -17,13 +19,16 @@ import axios from 'axios'
|
||||||
export default {
|
export default {
|
||||||
name: 'App',
|
name: 'App',
|
||||||
components: {
|
components: {
|
||||||
MyNav,
|
MyNav
|
||||||
},
|
},
|
||||||
|
emits: ["ready"],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
isLoading: true,
|
||||||
site_info: {
|
site_info: {
|
||||||
name: "Loading...",
|
name: "",
|
||||||
features: {}
|
features: {},
|
||||||
|
backend_connected: false
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
first_name: "",
|
first_name: "",
|
||||||
|
@ -44,6 +49,14 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
checkIfReady(){
|
||||||
|
if (this.site_info.name != "") {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.isLoading = false
|
||||||
|
this.$emit("ready")
|
||||||
|
},1000)
|
||||||
|
}
|
||||||
|
},
|
||||||
checkLoginStatus() {
|
checkLoginStatus() {
|
||||||
let url = this.$api_url + "/users/check_login"
|
let url = this.$api_url + "/users/check_login"
|
||||||
console.log("Checking login status...")
|
console.log("Checking login status...")
|
||||||
|
@ -70,10 +83,24 @@ export default {
|
||||||
|
|
||||||
},
|
},
|
||||||
async getSiteInfo() {
|
async getSiteInfo() {
|
||||||
|
console.log("Trying to get site Info...")
|
||||||
axios
|
axios
|
||||||
.get(this.$api_url + "/info")
|
.get(this.$api_url + "/info")
|
||||||
.then(response => {this.site_info = response.data})
|
.then(response => {
|
||||||
.catch(error => (console.log(error)))
|
this.site_info = response.data
|
||||||
|
console.log(this.site_info)
|
||||||
|
this.site_info.backend_connected = true
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
this.site_info.name = "Error getting data connection"
|
||||||
|
this.site_info.backend_connected = false
|
||||||
|
console.log(error)
|
||||||
|
setTimeout(() => {
|
||||||
|
this.getSiteInfo()
|
||||||
|
},5000)
|
||||||
|
}).finally(() => {
|
||||||
|
this.checkIfReady()
|
||||||
|
})
|
||||||
|
|
||||||
},
|
},
|
||||||
async getUserInfo() {
|
async getUserInfo() {
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
|
import axios from 'axios'
|
||||||
|
import Error from '@/types/ErrorType.vue'
|
||||||
export default {
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
customer_list: [],
|
||||||
|
errors: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
methods:{
|
methods:{
|
||||||
formatDate(d,f) {
|
formatDate(d,f) {
|
||||||
return moment(String(d)).format(f)
|
return moment(String(d)).format(f)
|
||||||
|
@ -26,6 +34,35 @@ export default {
|
||||||
minute: '2-digit',
|
minute: '2-digit',
|
||||||
second: '2-digit'})
|
second: '2-digit'})
|
||||||
},
|
},
|
||||||
|
async error(msg) {
|
||||||
|
let e = new Error()
|
||||||
|
e.msg = msg
|
||||||
|
e.id = crypto.randomUUID()
|
||||||
|
this.errors[e.id] = e
|
||||||
|
setTimeout(() => {
|
||||||
|
delete this.errors[e.id]
|
||||||
|
}, 10000)
|
||||||
|
},
|
||||||
|
async getCustomerList(name) {
|
||||||
|
console.log("searching customers...")
|
||||||
|
if (this.customer_list.length == 0) {
|
||||||
|
console.log("getting new customers...")
|
||||||
|
let url = this.$api_url + "/customers/full_list"
|
||||||
|
axios.get(url)
|
||||||
|
.then(resp => {
|
||||||
|
this.customer_list = resp.data
|
||||||
|
console.log(this.customer_list)
|
||||||
|
return this.customer_list.filter(x => {
|
||||||
|
x.name.contains(name) || x.acc_no.contains(name)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return this.customer_list
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -66,12 +66,12 @@ table tr.at_risk:hover {
|
||||||
background:lightgrey;
|
background:lightgrey;
|
||||||
color: grey;
|
color: grey;
|
||||||
}
|
}
|
||||||
.scroller {
|
|
||||||
border: 1px dotted #333;
|
|
||||||
}
|
|
||||||
.scroller {
|
.scroller {
|
||||||
height: 600px;
|
height: 600px;
|
||||||
}
|
}
|
||||||
|
.scroller.small {
|
||||||
|
height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
height: 138px;
|
height: 138px;
|
||||||
|
@ -82,3 +82,6 @@ table tr.at_risk:hover {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-bottom: 1px solid #000;
|
border-bottom: 1px solid #000;
|
||||||
}
|
}
|
||||||
|
.clickable {
|
||||||
|
cursor:pointer;
|
||||||
|
}
|
||||||
|
|
50
src/components/AddNote.vue
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<template>
|
||||||
|
<v-container>
|
||||||
|
<v-card title="Add Comment">
|
||||||
|
<v-card-text>
|
||||||
|
<p>Add new comment for : {{ customer.acc_no }} - {{ customer.name }}</p>
|
||||||
|
<v-textarea v-model="comment" label="Comment"></v-textarea>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-btn color="blue" :loading="saving" @click="saveComment">Save</v-btn>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="grey" @click="$emit('return','cancel')">Cancel</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
|
||||||
|
</v-card>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import axios from 'axios'
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
customer: null
|
||||||
|
},
|
||||||
|
data(){
|
||||||
|
return {
|
||||||
|
comment: "",
|
||||||
|
saving: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
saveComment() {
|
||||||
|
this.saving = true
|
||||||
|
let url = this.$api_url + "/customers/comments"
|
||||||
|
axios.put(url, {
|
||||||
|
acc_no: this.customer.acc_no,
|
||||||
|
comment: this.comment
|
||||||
|
})
|
||||||
|
.then(resp => {
|
||||||
|
console.log(resp.data)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.saving = false
|
||||||
|
this.$emit('return',"saved")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
156
src/components/ComplaintInfo.vue
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
<template>
|
||||||
|
<v-card :class="{ 'bg-red-lighten-5' : complaint.at_risk }">
|
||||||
|
<v-card-title>
|
||||||
|
Complaint Info : {{ complaint.id }}
|
||||||
|
<v-icon v-if="complaint.active" icon="mdi-play" title="Active"></v-icon>
|
||||||
|
<v-icon v-if="complaint.at_risk" color="red" icon="mdi-exclamation" title="At Risk"></v-icon>
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-subtitle>
|
||||||
|
{{ complaint.customer.acc_no }} - {{ complaint.customer.name }}<br/>
|
||||||
|
{{ formatDate(complaint.complaint_date,"DD/MM/YYYY") }}
|
||||||
|
</v-card-subtitle>
|
||||||
|
<v-card-text>
|
||||||
|
Reason: {{ complaint.reason.reason }}<br/>
|
||||||
|
Driver: {{ complaint.driver.name }}<br/>
|
||||||
|
Delivery Date {{ formatDate(complaint.delivery_date,"DD/MM/YYYY") }}<br/>
|
||||||
|
Order : {{ complaint.sop.doc_no }}<br/>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-subtitle>
|
||||||
|
<v-progress-linear indeterminate :active="info_loading">
|
||||||
|
</v-progress-linear>
|
||||||
|
Comments
|
||||||
|
</v-card-subtitle>
|
||||||
|
<v-card-text>
|
||||||
|
{{ complaint.info.comments }}
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-subtitle>
|
||||||
|
Info
|
||||||
|
</v-card-subtitle>
|
||||||
|
<v-card-text>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols=4>
|
||||||
|
<v-row dense nogutters>
|
||||||
|
<v-btn-toggle dark multiple background-color="primary">
|
||||||
|
<v-btn :active="complaint.info.checks[1]"
|
||||||
|
:loading="loading[1]"
|
||||||
|
@click="clickComplaintCheckbox(1)">1</v-btn>
|
||||||
|
<v-btn :active="complaint.info.checks[2]"
|
||||||
|
:loading="loading[2]"
|
||||||
|
@click="clickComplaintCheckbox(2)">2</v-btn>
|
||||||
|
<v-btn :active="complaint.info.checks[3]"
|
||||||
|
:loading="loading[3]"
|
||||||
|
@click="clickComplaintCheckbox(3)">3</v-btn>
|
||||||
|
</v-btn-toggle>
|
||||||
|
</v-row>
|
||||||
|
<v-row dense nogutters>
|
||||||
|
<v-btn-toggle dark multiple background-color="primary">
|
||||||
|
<v-btn :active="complaint.at_risk"
|
||||||
|
:loading="loading[4]"
|
||||||
|
@click="clickAtRisk">At Risk</v-btn>
|
||||||
|
<v-btn :active="complaint.info.permanent"
|
||||||
|
:loading="loading[5]"
|
||||||
|
@click="clickPermanent">Permanent</v-btn>
|
||||||
|
</v-btn-toggle>
|
||||||
|
</v-row>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Complaint from '@/types/ComplaintType.vue'
|
||||||
|
import methods from '@/CommonMethods.vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
in_complaint: new Complaint()
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
in_complaint(){
|
||||||
|
this.complaint = this.in_complaint
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mixins: [methods],
|
||||||
|
emits: ["return"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
complaint: this.in_complaint,
|
||||||
|
info_loading: true,
|
||||||
|
loading: [false, false, false, false, false, false]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isThreeDone() {
|
||||||
|
let c = this.complaint.info.checks
|
||||||
|
if (c[1] && c[2] && c[3]) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods:{
|
||||||
|
getComplaintInfo() {
|
||||||
|
this.info_loading = true
|
||||||
|
let url = this.$api_url + "/customers/complaints/" + this.complaint.id + "/info"
|
||||||
|
console.log("Getting Complaint Info...")
|
||||||
|
axios.get(url)
|
||||||
|
.then(resp =>{
|
||||||
|
this.complaint.info = resp.data
|
||||||
|
this.info_loading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
checkComplaintStatus(){
|
||||||
|
if (!this.complaint.info.permanent && this.complaint.at_risk && this.isThreeDone) {
|
||||||
|
console.log("Contract no longer at risk")
|
||||||
|
this.complaint.at_risk = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clickAtRisk(){
|
||||||
|
this.loading[4] = true
|
||||||
|
this.complaint.at_risk = !this.complaint.at_risk
|
||||||
|
if (this.setComplaintStatus(4)) {
|
||||||
|
this.checkComplaintStatus()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clickComplaintCheckbox(box) {
|
||||||
|
this.loading[box] = true
|
||||||
|
this.complaint.info.checks[box] = !this.complaint.info.checks[box]
|
||||||
|
if (this.setComplaintStatus(box)) {
|
||||||
|
this.checkComplaintStatus()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clickPermanent(){
|
||||||
|
this.loading[5] = true
|
||||||
|
this.complaint.info.permanent = !this.complaint.info.permanent
|
||||||
|
if (this.setComplaintStatus(5)) {
|
||||||
|
this.checkComplaintStatus()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setComplaintStatus(idx) {
|
||||||
|
this.info_loading = true
|
||||||
|
let url = this.$api_url + "/customers/complaints/" + this.complaint.id + "/info/set"
|
||||||
|
console.log("Getting Complaint Info...")
|
||||||
|
axios.post(url, {
|
||||||
|
checks: JSON.stringify(this.complaint.info.checks),
|
||||||
|
at_risk: this.complaint.at_risk,
|
||||||
|
permanent: this.complaint.info.permanent
|
||||||
|
}).then(resp => {
|
||||||
|
let stat = resp.data
|
||||||
|
console.log(stat)
|
||||||
|
this.getComplaintInfo()
|
||||||
|
this.info_loading = false
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
}).finally(() => {
|
||||||
|
this.loading[idx] = false
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getComplaintInfo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
113
src/components/CustomerComments.vue
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
<template>
|
||||||
|
<v-card style="min-height:200px">
|
||||||
|
<v-card-title>
|
||||||
|
Comments
|
||||||
|
<v-btn v-if="customer.id != ''" color="warning" size="smaller" variant="text" icon="mdi-plus" @click="showAddNote"></v-btn>
|
||||||
|
<v-btn v-if="customer.id != ''" color="green" size="smaller" variant="text" icon="mdi-refresh" @click="getCustomerComments" :loading="comments_loading"></v-btn>
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<v-progress-linear color="yellow" :active="comments_loading" indeterminate>
|
||||||
|
</v-progress-linear>
|
||||||
|
<v-list>
|
||||||
|
<RecycleScroller
|
||||||
|
class="scroller small"
|
||||||
|
item-size="50"
|
||||||
|
:items="comments"
|
||||||
|
v-slot="{ item }"
|
||||||
|
key-field="id">
|
||||||
|
<v-list-item :class="{ inactive : !item.active }">
|
||||||
|
{{ item.comment }}
|
||||||
|
<template v-slot:append>
|
||||||
|
<v-icon v-if="item.actioned" icon="mdi-tick" color="green" title="Actioned"></v-icon>
|
||||||
|
<v-btn v-if="item.active" variant="text" :loading="item.active_changing" @click="toggleActiveState(item)" size="small" icon="mdi-play" color="blue" title="Active"></v-btn>
|
||||||
|
<v-btn v-if="!item.active" variant="text" :loading="item.active_changing" @click="toggleActiveState(item)" size="small" icon="mdi-pause" title="Inactive"></v-btn>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
</RecycleScroller>
|
||||||
|
</v-list>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
<v-overlay v-model="dialog[0]" contained class="align-center justify-center">
|
||||||
|
<AddNote :customer="customer" @return="closeAddNote"></AddNote>
|
||||||
|
</v-overlay>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Customer from '@/types/CustomerType.vue'
|
||||||
|
import AddNote from '@/components/AddNote.vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
customer: new Customer()
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
AddNote
|
||||||
|
},
|
||||||
|
data(){
|
||||||
|
return {
|
||||||
|
comments_loading: null,
|
||||||
|
comments: [],
|
||||||
|
active_changing: false,
|
||||||
|
dialog: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
customer() {
|
||||||
|
this.getCustomerComments()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showAddNote() {
|
||||||
|
this.dialog[0] = true
|
||||||
|
},
|
||||||
|
closeAddNote(e){
|
||||||
|
if (e == "saved"){
|
||||||
|
this.getCustomerComments()
|
||||||
|
}
|
||||||
|
this.dialog[0] = false
|
||||||
|
},
|
||||||
|
getCustomerComments(){
|
||||||
|
this.comments_loading = true
|
||||||
|
let url = this.$api_url + "/customers/comments"
|
||||||
|
axios.get(url,{
|
||||||
|
params: {
|
||||||
|
c_id: this.customer.id
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(resp => {
|
||||||
|
this.comments = resp.data
|
||||||
|
})
|
||||||
|
.catch(error => (console.log(error)))
|
||||||
|
.finally(() => {
|
||||||
|
this.comments_loading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
toggleActiveState(cmt){
|
||||||
|
cmt.active_changing = true
|
||||||
|
let url = this.$api_url + "/customers/comments/" + cmt.id + "/active"
|
||||||
|
axios.put(url,{
|
||||||
|
state: !cmt.active
|
||||||
|
})
|
||||||
|
.then(resp => {
|
||||||
|
console.log(resp.data)
|
||||||
|
this.getActiveState(cmt)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getActiveState(cmt) {
|
||||||
|
let url = this.$api_url + "/customers/comments/" + cmt.id + "/active"
|
||||||
|
axios.get(url)
|
||||||
|
.then(resp => {
|
||||||
|
cmt.active = resp.data
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
cmt.active_changing = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
66
src/components/CustomerSearch.vue
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
<template>
|
||||||
|
<v-card title="Customer Search">
|
||||||
|
<v-card-text>
|
||||||
|
<v-text-field label="Search" v-model="customer_search" append-icon="mdi-magnify" @click:append="searchCustomers" @keyup.enter.prevent="searchCustomers"></v-text-field>
|
||||||
|
<v-progress-linear indeterminate :active="customers_loading">
|
||||||
|
</v-progress-linear>
|
||||||
|
<v-list>
|
||||||
|
<RecycleScroller class="scroller"
|
||||||
|
:items="filteredCustomers"
|
||||||
|
:item-size="50"
|
||||||
|
v-slot="{ item }"
|
||||||
|
key-field="acc_no"
|
||||||
|
>
|
||||||
|
<v-list-item @click="selectCustomer(item)">
|
||||||
|
{{ item.acc_no }} - {{ item.name }}
|
||||||
|
</v-list-item>
|
||||||
|
</RecycleScroller>
|
||||||
|
</v-list>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import axios from 'axios'
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
customer_search: "",
|
||||||
|
customers_loading: null,
|
||||||
|
customers: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filteredCustomers(){
|
||||||
|
if (this.customer_search == null){
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
let query = this.customer_search.toLowerCase()
|
||||||
|
let clist = this.customers.filter(q =>
|
||||||
|
q.name.toLowerCase().includes(query) ||
|
||||||
|
q.acc_no.includes(query)
|
||||||
|
)
|
||||||
|
return clist
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ['returnCustomer'],
|
||||||
|
methods: {
|
||||||
|
selectCustomer(cust){
|
||||||
|
this.$emit('returnCustomer',cust)
|
||||||
|
},
|
||||||
|
searchCustomers() {
|
||||||
|
this.customers_loading = true
|
||||||
|
let url = this.$api_url + "/customers/search/" + this.customer_search
|
||||||
|
axios.get(url)
|
||||||
|
.then(resp => {
|
||||||
|
this.customers = resp.data
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.customers_loading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
25
src/components/DebugPanel.vue
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<template>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols=12>
|
||||||
|
<v-expansion-panels v-model="debugPanel">
|
||||||
|
<v-expansion-panel title="Debug Info">
|
||||||
|
<v-expansion-panel-text>
|
||||||
|
{{ data }}
|
||||||
|
</v-expansion-panel-text>
|
||||||
|
</v-expansion-panel>
|
||||||
|
</v-expansion-panels>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
data: {}
|
||||||
|
},
|
||||||
|
data(){
|
||||||
|
return {
|
||||||
|
debugPanel: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
75
src/components/DriverSearch.vue
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
<template>
|
||||||
|
<v-card title="Driver Search">
|
||||||
|
<v-card-text>
|
||||||
|
<v-text-field label="Search" v-model="driver_search" append-icon="mdi-magnify"></v-text-field>
|
||||||
|
<v-progress-linear indeterminate :active="drivers_loading">
|
||||||
|
</v-progress-linear>
|
||||||
|
<v-list>
|
||||||
|
<RecycleScroller class="scroller"
|
||||||
|
:items="filteredDrivers"
|
||||||
|
:item-size="50"
|
||||||
|
v-slot="{ item }"
|
||||||
|
key-field="id"
|
||||||
|
>
|
||||||
|
<v-list-item @click="setDriver(item)">
|
||||||
|
{{ item.name }}
|
||||||
|
</v-list-item>
|
||||||
|
</RecycleScroller>
|
||||||
|
</v-list>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import axios from 'axios'
|
||||||
|
export default {
|
||||||
|
props:{
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
driver_search: "",
|
||||||
|
drivers: [],
|
||||||
|
drivers_loading: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['return'],
|
||||||
|
created() {
|
||||||
|
this.allDrivers()
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filteredDrivers() {
|
||||||
|
let q = this.driver_search.toLowerCase()
|
||||||
|
if (q == ""){ return this.drivers }
|
||||||
|
let ms = this.drivers.filter(m =>
|
||||||
|
m.name.toLowerCase().includes(q)
|
||||||
|
)
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
allDrivers(){
|
||||||
|
this.drivers_loading = true
|
||||||
|
console.log("All Drivers...")
|
||||||
|
let url = this.$api_url + "/drivers"
|
||||||
|
axios.get(url)
|
||||||
|
.then(resp => {
|
||||||
|
this.drivers = resp.data
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.drivers_loading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setDriver(p){
|
||||||
|
this.$emit('return',p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.scroller {
|
||||||
|
height:500px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
20
src/components/ErrorBanner.vue
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<template>
|
||||||
|
<v-row>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item v-for="(e, index) in errors" :key="index">
|
||||||
|
<v-banner color="error" icon="$error">
|
||||||
|
<v-banner-text>
|
||||||
|
{{ e }}
|
||||||
|
</v-banner-text>
|
||||||
|
</v-banner>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
errors: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
49
src/components/LoadingScreen.vue
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<template>
|
||||||
|
<div :class="{ loader: true, fadeout: !isLoading }">
|
||||||
|
<div class="animate__animated animate__rubberBand animate__infinite animate__slow">
|
||||||
|
<v-img style="border-radius:5%" height="128" src="/images/cmc-logo.png" /><br/>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<v-progress-circular
|
||||||
|
color="deep-orange"
|
||||||
|
indeterminate
|
||||||
|
></v-progress-circular>
|
||||||
|
Starting ...
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import 'animate.css';
|
||||||
|
export default {
|
||||||
|
name: "LoadingScreen",
|
||||||
|
props: ["isLoading"]
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.loader {
|
||||||
|
background-color: lightyellow;
|
||||||
|
bottom: 0;
|
||||||
|
color: black;
|
||||||
|
display: block;
|
||||||
|
font-size: 16px;
|
||||||
|
left: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
padding-top: 10vh;
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
text-align: center;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fadeout {
|
||||||
|
animation: fadeout 1s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeout {
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
93
src/components/MedSearch.vue
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
<template>
|
||||||
|
<v-card title="Med Search">
|
||||||
|
<v-card-text>
|
||||||
|
<v-text-field label="Search" v-model="med_search" append-icon="mdi-magnify" @click:append="searchMeds" @keyup.enter.prevent="searchMeds"></v-text-field>
|
||||||
|
<v-progress-linear indeterminate :active="meds_loading">
|
||||||
|
</v-progress-linear>
|
||||||
|
<v-list>
|
||||||
|
<RecycleScroller class="scroller"
|
||||||
|
:items="filteredMeds"
|
||||||
|
:item-size="50"
|
||||||
|
v-slot="{ item }"
|
||||||
|
key-field="id"
|
||||||
|
>
|
||||||
|
<v-list-item @click="setMed(item)">
|
||||||
|
{{ item.med_code }} : {{ item.name }}
|
||||||
|
{{ item }}
|
||||||
|
</v-list-item>
|
||||||
|
</RecycleScroller>
|
||||||
|
</v-list>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import axios from 'axios'
|
||||||
|
export default {
|
||||||
|
props:{
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
med_search: "",
|
||||||
|
meds: [],
|
||||||
|
meds_loading: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['returnMed'],
|
||||||
|
created() {
|
||||||
|
this.allMeds()
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
filteredMeds() {
|
||||||
|
let q = this.med_search.toLowerCase()
|
||||||
|
if (q == ""){ return this.meds }
|
||||||
|
let ms = this.meds.filter(m =>
|
||||||
|
m.name.toLowerCase().includes(q) ||
|
||||||
|
m.med_code.toLowerCase().includes(q)
|
||||||
|
)
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
allMeds(){
|
||||||
|
this.meds_loading = true
|
||||||
|
console.log("All Meds...")
|
||||||
|
let url = this.$api_url + "/meds/list"
|
||||||
|
axios.get(url)
|
||||||
|
.then(resp => {
|
||||||
|
this.meds = resp.data
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.meds_loading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
searchMeds() {
|
||||||
|
this.meds_loading = true
|
||||||
|
console.log("Searching for " & this.med_search)
|
||||||
|
let url = this.$api_url + "/meds/search/" + this.med_search
|
||||||
|
axios.get(url)
|
||||||
|
.then(resp => {
|
||||||
|
console.log(resp)
|
||||||
|
this.meds = resp.data
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.meds_loading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setMed(p){
|
||||||
|
this.$emit('returnMed',p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.scroller {
|
||||||
|
height:500px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -4,7 +4,25 @@
|
||||||
<v-app-bar-nav-icon v-if="user.logged_in" variant="text" @click.stop="drawer = !drawer"></v-app-bar-nav-icon>
|
<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-toolbar-title>{{ site_info.name }}</v-toolbar-title>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-btn variant="text" v-if="user.logged_in">Hi {{ user.first_name }}</v-btn>
|
<v-menu>
|
||||||
|
<template v-slot:activator="{ props }">
|
||||||
|
<v-btn
|
||||||
|
variant="text"
|
||||||
|
v-if="user.logged_in"
|
||||||
|
v-bind="props">
|
||||||
|
Hi {{ user.first_name }}
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
<v-list theme="dark">
|
||||||
|
<v-list-item
|
||||||
|
v-for="(item, index) in user_items"
|
||||||
|
:key="index"
|
||||||
|
:title="item.title"
|
||||||
|
:to="item.value"
|
||||||
|
>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
</v-app-bar>
|
</v-app-bar>
|
||||||
<v-navigation-drawer
|
<v-navigation-drawer
|
||||||
v-if="user.logged_in"
|
v-if="user.logged_in"
|
||||||
|
@ -28,11 +46,22 @@
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-footer>
|
||||||
|
<v-banner>
|
||||||
|
<v-banner-text>
|
||||||
|
<sub>{{ site_info.name }}<br/>
|
||||||
|
BE v{{ site_info.version }}<br/>
|
||||||
|
FE v{{ appVersion }}
|
||||||
|
</sub>
|
||||||
|
</v-banner-text>
|
||||||
|
</v-banner>
|
||||||
|
</v-footer>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-navigation-drawer>
|
</v-navigation-drawer>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
import { version } from "@/../package.json"
|
||||||
export default{
|
export default{
|
||||||
name: "MyNav",
|
name: "MyNav",
|
||||||
props: {
|
props: {
|
||||||
|
@ -49,12 +78,19 @@ export default{
|
||||||
} else {
|
} else {
|
||||||
this.items = []
|
this.items = []
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
user_items() {
|
||||||
|
return this.items.filter(x => x.isUserMgmt == true)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data(){
|
data(){
|
||||||
return {
|
return {
|
||||||
|
appVersion: version,
|
||||||
items: [],
|
items: [],
|
||||||
drawer: null
|
drawer: null,
|
||||||
|
drawer2: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
@ -65,7 +101,6 @@ export default{
|
||||||
methods:{
|
methods:{
|
||||||
get_menu() {
|
get_menu() {
|
||||||
let items = []
|
let items = []
|
||||||
items.push({title: "Dashboard", value:"/about"})
|
|
||||||
items.push({title: "Customers",
|
items.push({title: "Customers",
|
||||||
value:"/customers",
|
value:"/customers",
|
||||||
children:[{title:"List",
|
children:[{title:"List",
|
||||||
|
@ -84,7 +119,7 @@ export default{
|
||||||
value:"/sop/printed"}]
|
value:"/sop/printed"}]
|
||||||
})
|
})
|
||||||
|
|
||||||
items.push({title: "Logout", value:"/logout"})
|
items.push({title: "Logout", value:"/logout", isUserMgmt: true})
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<v-container class="button-container">
|
<v-container class="button-container">
|
||||||
<v-btn color="primary" @click="generatePdf()" class="mr-2">Create PDF</v-btn>
|
<v-btn :loading="generating" color="primary" @click="generatePdf()" class="mr-2">Create PDF</v-btn>
|
||||||
<v-btn color="grey" v-print="printObj">Print</v-btn>
|
<v-btn color="grey" v-print="printObj">Print</v-btn>
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
@ -8,10 +8,12 @@
|
||||||
import html2pdf from 'html2pdf.js';
|
import html2pdf from 'html2pdf.js';
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
scope: String
|
scope: String,
|
||||||
|
filename: String
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
generating: false,
|
||||||
printObj: {
|
printObj: {
|
||||||
id: this.scope,
|
id: this.scope,
|
||||||
previewTitle: "Report Print",
|
previewTitle: "Report Print",
|
||||||
|
@ -20,8 +22,16 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods:{
|
methods:{
|
||||||
async generatePdf() {
|
generatePdf() {
|
||||||
html2pdf(document.getElementById(this.scope))
|
this.generating = true
|
||||||
|
setTimeout(() => {
|
||||||
|
let el = document.getElementById(this.scope)
|
||||||
|
let opts = {
|
||||||
|
filename: (this.filename || 'file' ) + ".pdf"
|
||||||
|
}
|
||||||
|
html2pdf().set(opts).from(el).save()
|
||||||
|
this.generating = false
|
||||||
|
},500)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
64
src/components/ProductSearch.vue
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
<template>
|
||||||
|
<v-card title="Product Search">
|
||||||
|
<v-card-text>
|
||||||
|
<v-text-field label="Search" v-model="product_search" append-icon="mdi-magnify" @click:append="searchProducts" @keyup.enter.prevent="searchProducts"></v-text-field>
|
||||||
|
<v-progress-linear indeterminate :active="products_loading">
|
||||||
|
</v-progress-linear>
|
||||||
|
<v-list>
|
||||||
|
<RecycleScroller class="scroller"
|
||||||
|
:items="products"
|
||||||
|
:item-size="50"
|
||||||
|
v-slot="{ item }"
|
||||||
|
key-field="code"
|
||||||
|
>
|
||||||
|
<v-list-item @click="setProduct(item)">
|
||||||
|
|
||||||
|
{{ item.code }} - {{ item.name }}
|
||||||
|
</v-list-item>
|
||||||
|
</RecycleScroller>
|
||||||
|
</v-list>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import axios from 'axios'
|
||||||
|
export default {
|
||||||
|
props:{
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
product_search: "",
|
||||||
|
products: [],
|
||||||
|
products_loading: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['returnProduct'],
|
||||||
|
methods: {
|
||||||
|
searchProducts() {
|
||||||
|
this.products_loading = true
|
||||||
|
console.log("Searching for " & this.product_search)
|
||||||
|
let url = this.$api_url + "/products/search/" + this.product_search
|
||||||
|
axios.get(url)
|
||||||
|
.then(resp => {
|
||||||
|
console.log(resp)
|
||||||
|
this.products = resp.data
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.products_loading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setProduct(p){
|
||||||
|
this.$emit('returnProduct',p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.scroller {
|
||||||
|
height:500px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
152
src/components/RecentOrders.vue
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
<template>
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>
|
||||||
|
{{ doc_types[doc_status] }} Orders - {{ customer.acc_no }} {{ customer.name }}
|
||||||
|
<v-btn v-if="customer.id != ''" size="smaller" icon="mdi-refresh" variant="text" color="green" @click="getCustomerRecentOrders" :loading="orders_loading"></v-btn>
|
||||||
|
|
||||||
|
</v-card-title>
|
||||||
|
<v-progress-linear color="blue" :active="orders_loading" indeterminate>
|
||||||
|
</v-progress-linear>
|
||||||
|
<v-container>
|
||||||
|
<v-row dense>
|
||||||
|
<v-col cols=12>
|
||||||
|
<RecycleScroller :items="orders"
|
||||||
|
class="scroller"
|
||||||
|
:class="{ small : size_small }"
|
||||||
|
:item-size="180"
|
||||||
|
key-field="doc_no"
|
||||||
|
v-slot="{ item }">
|
||||||
|
|
||||||
|
<v-card class="ma-2" density="compact" :class="{ 'bg-green-lighten-5' : item.doc_status == 'Live' }">
|
||||||
|
<v-row dense>
|
||||||
|
<v-col>
|
||||||
|
<v-card-title>
|
||||||
|
Order: {{ item.doc_no }}
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-subtitle>
|
||||||
|
Order Date : {{ formatDate(item.doc_date,"DD/MM/YYYY") }}<br/>
|
||||||
|
Delivery Date : {{ formatDate(item.req_del_date,"DD/MM/YYYY") }}<br/>
|
||||||
|
<v-icon color="blue" v-if="item.doc_status == 'Completed'" icon="mdi-check"></v-icon>
|
||||||
|
<v-icon v-if="item.doc_status == 'Live'" icon="mdi-play"></v-icon>
|
||||||
|
{{ item.doc_status }}
|
||||||
|
<br/>
|
||||||
|
<v-icon color="blue-lighten-1" v-if="item.print_status == 'Printed'" icon="mdi-printer"></v-icon>
|
||||||
|
<v-icon v-if="item.print_status == 'Not printed'" icon="mdi-printer-off"></v-icon>
|
||||||
|
{{ item.print_status }}
|
||||||
|
</v-card-subtitle>
|
||||||
|
<v-card-text>
|
||||||
|
{{ item.customer.acc_no }} - {{ item.customer.name }}
|
||||||
|
</v-card-text >
|
||||||
|
</v-col>
|
||||||
|
<v-col>
|
||||||
|
<v-card-text >
|
||||||
|
<!--<h5>Address :</h5>-->
|
||||||
|
<template v-if="item.del_addr.id != 0 && item.del_addr.postal_name != ''">
|
||||||
|
<h5>Delivery Address :</h5>
|
||||||
|
<template v-for="(v, k , index) in item.del_addr" :key="index">
|
||||||
|
<span class="text-caption" v-if="k != 'id' && (v != '' || v != 0)">
|
||||||
|
{{ v }}<br/>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</v-card-text>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols=5>
|
||||||
|
<v-card-text style="max-height:160px;overflow-y:scroll;">
|
||||||
|
<h5>Items :</h5>
|
||||||
|
<span class="text-caption" v-for="(i, index) in item.products" :key="index" style="border-bottom: 1px dotted #000;">
|
||||||
|
{{ i.code }} - {{ i.name }}
|
||||||
|
<span v-if="i.quantity != 0">x {{ i.quantity }}</span><br/>
|
||||||
|
</span>
|
||||||
|
</v-card-text>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols=1>
|
||||||
|
<v-btn size="small" color="warning" title="Add Complaint">
|
||||||
|
<v-icon>mdi-plus</v-icon>
|
||||||
|
<v-icon>mdi-exclamation</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
</v-card>
|
||||||
|
</RecycleScroller>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import axios from 'axios'
|
||||||
|
import Customer from '@/types/CustomerType.vue'
|
||||||
|
import methods from '@/CommonMethods.vue'
|
||||||
|
export default {
|
||||||
|
props:{
|
||||||
|
customer: new Customer(),
|
||||||
|
doc_status: Number,
|
||||||
|
limit: Number,
|
||||||
|
size_small: Boolean
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
customer() {
|
||||||
|
this.getCustomerRecentOrders()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mixins: [methods],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
orders_loading: false,
|
||||||
|
orders: [],
|
||||||
|
doc_types: ["Live","","Completed"],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
prog_col(){
|
||||||
|
if (this.doc_status == 2){
|
||||||
|
return "green"
|
||||||
|
} else {
|
||||||
|
return "blue"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sortedOrders() {
|
||||||
|
let sorted = this.orders
|
||||||
|
sorted.sort((a, b) => {
|
||||||
|
if (a.doc_date < b.doc_date){
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if (a.doc_date > b.doc_date){
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
return sorted
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getCustomerRecentOrders(){
|
||||||
|
this.orders_loading = true
|
||||||
|
let url = this.$api_url + "/customers/" + this.customer.id + "/orders/recent"
|
||||||
|
axios.get(url, {
|
||||||
|
params: {
|
||||||
|
doc_status: this.doc_status,
|
||||||
|
limit: this.limit || 6
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(resp => {
|
||||||
|
this.orders = resp.data
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.orders_loading = false
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (this.customer.id != "") {
|
||||||
|
this.getCustomerRecentOrders()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,8 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<PrintButtons :scope="scope" />
|
<PrintButtons :scope="scope" :filename="filename" />
|
||||||
<v-container>
|
<v-container>
|
||||||
<v-card class="a4 page">
|
<v-card class="a4 page">
|
||||||
<div :id="scope" class="pdf-scope">
|
<div :id="scope" class="pdf-scope" >
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
@ -10,9 +10,11 @@
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import PrintButtons from './PrintButtons.vue'
|
import PrintButtons from './PrintButtons.vue'
|
||||||
|
import '@/assets/css/reports.css';
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
scope: String
|
scope: String,
|
||||||
|
filename: String
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
PrintButtons
|
PrintButtons
|
||||||
|
|
73
src/components/VetSearch.vue
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
<template>
|
||||||
|
<v-card title="Vet Search">
|
||||||
|
<v-card-text>
|
||||||
|
<v-text-field label="Search" v-model="vet_search" append-icon="mdi-magnify" @click:append="searchVets" @keyup.enter.prevent="searchVets"></v-text-field>
|
||||||
|
<v-progress-linear indeterminate :active="vets_loading">
|
||||||
|
</v-progress-linear>
|
||||||
|
<v-list>
|
||||||
|
<RecycleScroller class="scroller"
|
||||||
|
:items="vets"
|
||||||
|
:item-size="50"
|
||||||
|
v-slot="{ item }"
|
||||||
|
key-field="id"
|
||||||
|
>
|
||||||
|
<v-list-item @click="setVet(item)">
|
||||||
|
{{ item.practice }}
|
||||||
|
<v-tooltip activator="parent"
|
||||||
|
location="end">
|
||||||
|
{{ item.practice }}
|
||||||
|
<template v-for="(c,index) in item.contacts" :key="index">
|
||||||
|
<template v-if="c != ''">
|
||||||
|
{{ c }}<br/>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</v-tooltip>
|
||||||
|
|
||||||
|
</v-list-item>
|
||||||
|
</RecycleScroller>
|
||||||
|
</v-list>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import axios from 'axios'
|
||||||
|
export default {
|
||||||
|
props:{
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
vet_search: "",
|
||||||
|
vets: [],
|
||||||
|
vets_loading: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['returnVet'],
|
||||||
|
methods: {
|
||||||
|
searchVets() {
|
||||||
|
this.vets_loading = true
|
||||||
|
console.log("Searching for " & this.vet_search)
|
||||||
|
let url = this.$api_url + "/vets/search/" + this.vet_search
|
||||||
|
axios.get(url)
|
||||||
|
.then(resp => {
|
||||||
|
console.log(resp)
|
||||||
|
this.vets = resp.data
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.vets_loading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setVet(p){
|
||||||
|
this.$emit('returnVet',p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.scroller {
|
||||||
|
height:500px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -10,6 +10,8 @@ import './assets/css/app.scss'
|
||||||
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
|
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import VueVirtualScroller from 'vue-virtual-scroller'
|
import VueVirtualScroller from 'vue-virtual-scroller'
|
||||||
|
import DebugPanel from '@/components/DebugPanel.vue'
|
||||||
|
import ErrorBanner from '@/components/ErrorBanner.vue'
|
||||||
|
|
||||||
axios.defaults.headers.common['X-Authentication'] = `Bearer ${localStorage.getItem('access_token')}`;
|
axios.defaults.headers.common['X-Authentication'] = `Bearer ${localStorage.getItem('access_token')}`;
|
||||||
|
|
||||||
|
@ -19,6 +21,8 @@ const app = createApp(App).use(router)
|
||||||
.use(vuetify)
|
.use(vuetify)
|
||||||
.use(print)
|
.use(print)
|
||||||
.use(VueVirtualScroller)
|
.use(VueVirtualScroller)
|
||||||
|
.use(DebugPanel)
|
||||||
|
.use(ErrorBanner)
|
||||||
.component('DatePicker', Datepicker)
|
.component('DatePicker', Datepicker)
|
||||||
|
|
||||||
var url = window.location.protocol + "//" + window.location.host + "/api/v1"
|
var url = window.location.protocol + "//" + window.location.host + "/api/v1"
|
||||||
|
|
17
src/methods/CustomerMethods.vue
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<script>
|
||||||
|
import axios from 'axios'
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
customerSearch(query) {
|
||||||
|
let url = this.$api_url + "/customers/search/" + query
|
||||||
|
axios.get(url)
|
||||||
|
.then(resp => {
|
||||||
|
return resp.data
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,5 +1,4 @@
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import HomeView from '../views/HomeView.vue'
|
|
||||||
const AboutView = () => import('../views/AboutView.vue')
|
const AboutView = () => import('../views/AboutView.vue')
|
||||||
const LoginPage = () => import('../views/LoginPage.vue')
|
const LoginPage = () => import('../views/LoginPage.vue')
|
||||||
const LogOut = () => import('../components/LogOut.vue')
|
const LogOut = () => import('../components/LogOut.vue')
|
||||||
|
@ -7,13 +6,14 @@ const CustomerList = () => import('../views/customers/CustomerList.vue')
|
||||||
const ContractList = () => import('../views/contracts/ContractList.vue')
|
const ContractList = () => import('../views/contracts/ContractList.vue')
|
||||||
const ComplaintsList = () => import('../views/complaints/ComplaintsList.vue')
|
const ComplaintsList = () => import('../views/complaints/ComplaintsList.vue')
|
||||||
const MedFeedsList = () => import('../views/medfeeds/MedFeedsList.vue')
|
const MedFeedsList = () => import('../views/medfeeds/MedFeedsList.vue')
|
||||||
|
const OrderList = () => import('../views/salesorders/OrdersList.vue')
|
||||||
const SOPPrintedList = () => import('../views/salesorders/SOPPrinted.vue')
|
const SOPPrintedList = () => import('../views/salesorders/SOPPrinted.vue')
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'home',
|
name: 'home',
|
||||||
component: HomeView
|
component: CustomerList
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/about',
|
path: '/about',
|
||||||
|
@ -40,16 +40,41 @@ const routes = [
|
||||||
name: 'contractlist',
|
name: 'contractlist',
|
||||||
component: ContractList
|
component: ContractList
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/customers/contracts/list/:id',
|
||||||
|
name: 'contractlistid',
|
||||||
|
component: ContractList
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/customers/medicated-feeds/list',
|
path: '/customers/medicated-feeds/list',
|
||||||
name: 'medfeedslist',
|
name: 'medfeedslist',
|
||||||
component: MedFeedsList
|
component: MedFeedsList
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/customers/medicated-feeds/list/:id',
|
||||||
|
name: 'medfeedslistid',
|
||||||
|
component: MedFeedsList
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/customers/complaints/list',
|
path: '/customers/complaints/list',
|
||||||
name: 'complaintslist',
|
name: 'complaintslist',
|
||||||
component: ComplaintsList
|
component: ComplaintsList
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/customers/complaints/list/:id',
|
||||||
|
name: 'complaintslistid',
|
||||||
|
component: ComplaintsList
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/customers/orders/list',
|
||||||
|
name: 'orderslist',
|
||||||
|
component: OrderList
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/customers/orders/list/:id',
|
||||||
|
name: 'orderslistid',
|
||||||
|
component: OrderList
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/sop/printed',
|
path: '/sop/printed',
|
||||||
name: 'sopprinted',
|
name: 'sopprinted',
|
||||||
|
|
7
src/types/ComplaintInfoType.vue
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<script>
|
||||||
|
export default class ComplaintInfo {
|
||||||
|
checks = [false, false, false, false]
|
||||||
|
permanent = false
|
||||||
|
comments = ""
|
||||||
|
}
|
||||||
|
</script>
|
16
src/types/ComplaintType.vue
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<script>
|
||||||
|
import Customer from '@/types/CustomerType.vue'
|
||||||
|
import ComplaintInfo from '@/types/ComplaintInfoType.vue'
|
||||||
|
export default class Complaint {
|
||||||
|
id = 0
|
||||||
|
complaint_date = new Date()
|
||||||
|
delivery_date = ""
|
||||||
|
reason = ""
|
||||||
|
sop = {}
|
||||||
|
at_risk = false
|
||||||
|
driver = ""
|
||||||
|
info = new ComplaintInfo()
|
||||||
|
customer = new Customer()
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
18
src/types/ContractType.vue
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<script>
|
||||||
|
import Customer from '@/types/CustomerType.vue';
|
||||||
|
export default class Contract {
|
||||||
|
no = "";
|
||||||
|
customer = new Customer();
|
||||||
|
terms = "";
|
||||||
|
products = [];
|
||||||
|
start_date = new Date();
|
||||||
|
finish_date = new Date();
|
||||||
|
agree_date = "";
|
||||||
|
tonnage_per_month = 1;
|
||||||
|
total_tonnage = 1;
|
||||||
|
comments = "";
|
||||||
|
office_comments = "";
|
||||||
|
active = true;
|
||||||
|
isNew = true;
|
||||||
|
}
|
||||||
|
</script>
|
19
src/types/CustomerAddressType.vue
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<script>
|
||||||
|
export default class CustomerAddress {
|
||||||
|
id = "";
|
||||||
|
description = "";
|
||||||
|
contract = "";
|
||||||
|
postal_name = "";
|
||||||
|
line_1 = "";
|
||||||
|
line_2 = "";
|
||||||
|
line_3 = "";
|
||||||
|
line_4 = "";
|
||||||
|
city = "";
|
||||||
|
county = "";
|
||||||
|
postcode = "";
|
||||||
|
country = "";
|
||||||
|
email = "";
|
||||||
|
faxno = "";
|
||||||
|
telephone = "";
|
||||||
|
}
|
||||||
|
</script>
|
13
src/types/CustomerType.vue
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<script>
|
||||||
|
import CustomerAddress from '@/types/CustomerAddressType.vue'
|
||||||
|
export default class Customer {
|
||||||
|
id = "";
|
||||||
|
acc_no = "";
|
||||||
|
name = "";
|
||||||
|
short_name = "";
|
||||||
|
full_title = "";
|
||||||
|
at_risk = false;
|
||||||
|
address = new CustomerAddress();
|
||||||
|
notes = {}
|
||||||
|
}
|
||||||
|
</script>
|
6
src/types/ErrorType.vue
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<script>
|
||||||
|
export default class Error {
|
||||||
|
id = "";
|
||||||
|
msg = "";
|
||||||
|
}
|
||||||
|
</script>
|
23
src/types/MedFeedType.vue
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<script>
|
||||||
|
import Medication from '@/types/MedicationType.vue'
|
||||||
|
import Customer from '@/types/CustomerType.vue'
|
||||||
|
import CustomerAddress from '@/types/CustomerAddressType.vue'
|
||||||
|
import Product from '@/types/ProductType.vue'
|
||||||
|
import Vet from '@/types/VetType.vue'
|
||||||
|
export default class MedicatedFeed {
|
||||||
|
id = 0;
|
||||||
|
med_feed_id = 0;
|
||||||
|
medication = new Medication();
|
||||||
|
customer = new Customer();
|
||||||
|
product = new Product();
|
||||||
|
tonnage = 0;
|
||||||
|
vet = new Vet();
|
||||||
|
date_required = "";
|
||||||
|
delivery_address = new CustomerAddress();
|
||||||
|
alt_adds = [];
|
||||||
|
repeat = false;
|
||||||
|
repeat_message = "";
|
||||||
|
current = true;
|
||||||
|
isNew = true;
|
||||||
|
}
|
||||||
|
</script>
|
9
src/types/MedicationType.vue
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<script>
|
||||||
|
export default class Medication {
|
||||||
|
id = 0;
|
||||||
|
name = "";
|
||||||
|
info = [];
|
||||||
|
med_code = "";
|
||||||
|
inclusion_rate = "";
|
||||||
|
}
|
||||||
|
</script>
|
7
src/types/ProductType.vue
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<script>
|
||||||
|
export default class Product {
|
||||||
|
code = "";
|
||||||
|
name = "";
|
||||||
|
price = "";
|
||||||
|
}
|
||||||
|
</script>
|
7
src/types/VetType.vue
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<script>
|
||||||
|
export default class Vet {
|
||||||
|
id = 0;
|
||||||
|
practice = "";
|
||||||
|
contacts = [];
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,14 +1,23 @@
|
||||||
<template>
|
<template>
|
||||||
<v-card width="500" height="300"
|
<v-card width="500" min-height="350"
|
||||||
class="mx-auto my-12"
|
class="mx-auto my-12"
|
||||||
|
id="login-box"
|
||||||
title="Welcome!"
|
title="Welcome!"
|
||||||
subtitle="Please Log In">
|
subtitle="Please Log In">
|
||||||
<v-form>
|
<v-form :disabled="!my_site_info.backend_connected">
|
||||||
<v-responsive class="mx-auto" max-width="475">
|
<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="Username"
|
||||||
<v-text-field label="Password" v-model="user.password" clearable
|
@keyup.enter="submitLogin"
|
||||||
|
clearable
|
||||||
|
v-model="user.email"></v-text-field>
|
||||||
|
<v-text-field label="Password"
|
||||||
|
@keyup.enter="submitLogin"
|
||||||
|
v-model="user.password" clearable
|
||||||
type="password"></v-text-field>
|
type="password"></v-text-field>
|
||||||
<v-btn color="blue" @click="submitLogin">Login</v-btn>
|
<v-btn :disabled="!my_site_info.backend_connected" color="blue" :loading="logging_in" @click="submitLogin">Login</v-btn>
|
||||||
|
<v-banner v-if="show_error" color="error" icon="mdi-exclamation-thick" theme="dark">
|
||||||
|
<v-banner-text>{{ error_message }}</v-banner-text>
|
||||||
|
</v-banner>
|
||||||
</v-responsive>
|
</v-responsive>
|
||||||
</v-form>
|
</v-form>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
@ -18,18 +27,35 @@
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
props: {
|
||||||
|
site_info: {}
|
||||||
|
},
|
||||||
name: 'LoginPage',
|
name: 'LoginPage',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
my_site_info: this.site_info,
|
||||||
user: {
|
user: {
|
||||||
email: "",
|
email: "",
|
||||||
password: ""
|
password: ""
|
||||||
|
},
|
||||||
|
show_error: false,
|
||||||
|
error_message: "",
|
||||||
|
error_count: 0,
|
||||||
|
logging_in: false
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
'site_info.backend_connected'(new_val) {
|
||||||
|
console.log(new_val)
|
||||||
|
this.my_site_info.backend_connected = new_val
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
submitLogin() {
|
submitLogin() {
|
||||||
|
this.error_message = ""
|
||||||
|
this.show_error = false
|
||||||
let url = this.$api_url + "/users/login"
|
let url = this.$api_url + "/users/login"
|
||||||
|
this.logging_in = true
|
||||||
console.log("Logging in...")
|
console.log("Logging in...")
|
||||||
axios
|
axios
|
||||||
.post(url, {
|
.post(url, {
|
||||||
|
@ -38,6 +64,7 @@ export default {
|
||||||
})
|
})
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
let data = resp.data
|
let data = resp.data
|
||||||
|
console.log(data)
|
||||||
if (data.logged_in) {
|
if (data.logged_in) {
|
||||||
let token = data.token
|
let token = data.token
|
||||||
localStorage.setItem('access_token', token.content)
|
localStorage.setItem('access_token', token.content)
|
||||||
|
@ -47,10 +74,34 @@ export default {
|
||||||
console.log("Logged in")
|
console.log("Logged in")
|
||||||
window.location.href = '/'
|
window.location.href = '/'
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.error_message = "Login failed. Invalid username or password."
|
||||||
|
this.error_count += 1
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => { console.log(error) })
|
.catch(error => {
|
||||||
|
console.log(error)
|
||||||
|
this.error_message = "Login failed. Invalid username or password."
|
||||||
|
this.error_count += 1
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (this.error_message != ""){
|
||||||
|
this.show_error = true
|
||||||
|
}
|
||||||
|
this.logging_in = false
|
||||||
|
if (this.error_count > 2) {
|
||||||
|
let box = document.getElementById("login-box")
|
||||||
|
box.classList.add("animate__animated")
|
||||||
|
box.classList.add("animate__hinge")
|
||||||
|
setTimeout(() => {
|
||||||
|
box.classList.add("animate__fadeInUp")
|
||||||
|
box.classList.remove("animate__hinge")
|
||||||
|
},5000)
|
||||||
|
this.error_count = 0
|
||||||
|
}
|
||||||
|
},2000)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
175
src/views/complaints/ComplaintEdit.vue
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
<template>
|
||||||
|
<v-card :title="title">
|
||||||
|
<v-card-subtitle>
|
||||||
|
Complaint : {{ complaint.id }}
|
||||||
|
</v-card-subtitle>
|
||||||
|
<v-card-text>
|
||||||
|
<v-container>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols=6>
|
||||||
|
<v-text-field readonly variant="outlined" prepend-inner-icon="mdi-magnify" @click="showCustomerSearch" label="Customer" :model-value="complaint.customer.acc_no + ' - ' + complaint.customer.name">
|
||||||
|
</v-text-field>
|
||||||
|
<label>
|
||||||
|
Complaint Date :
|
||||||
|
<DatePicker v-model="complaint.complaint_date" format="dd/MM/yyyy" />
|
||||||
|
</label>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols=6>
|
||||||
|
<v-text-field label="Sales Order Number" v-model="complaint.sop.doc_no" variant="outlined"></v-text-field>
|
||||||
|
<v-select label="Reason" v-model="complaint.reason" :items="reasons" item-title="reason" item-value="id" return-object variant="outlined"></v-select>
|
||||||
|
<v-text-field readonly prepend-inner-icon="mdi-magnify" label="Driver" v-model="complaint.driver.name" variant="outlined" @click="showDriverSearch"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols=12>
|
||||||
|
<v-progress-linear :active="info_loading" indeterminate></v-progress-linear>
|
||||||
|
<v-textarea variant="outlined" label="Comments" v-model="complaint.info.comments">
|
||||||
|
</v-textarea>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</v-card-text>
|
||||||
|
<DebugPanel :data="complaint"></DebugPanel>
|
||||||
|
<ErrorBanner :errors="errors" />
|
||||||
|
<v-card-actions>
|
||||||
|
<v-btn v-if="!complaint.isNew" color="red-darken-1"
|
||||||
|
variant="text"
|
||||||
|
:loading="saving"
|
||||||
|
@click="saveComplaint(selected_complaint)">Save</v-btn>
|
||||||
|
<v-btn v-if="complaint.isNew" color="red-darken-1"
|
||||||
|
variant="text"
|
||||||
|
:loading="saving"
|
||||||
|
@click="saveComplaint(selected_complaint)">Add</v-btn>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="blue-darken-1"
|
||||||
|
variant="text"
|
||||||
|
@click="close">Close</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
<v-dialog v-model="search[0]" scrollable>
|
||||||
|
<CustomerSearch @returnCustomer="setCustomer"></CustomerSearch>
|
||||||
|
</v-dialog>
|
||||||
|
<v-dialog v-model="search[1]">
|
||||||
|
<DriverSearch @return="setDriver"></DriverSearch>
|
||||||
|
</v-dialog>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import axios from 'axios'
|
||||||
|
import methods from '@/CommonMethods.vue'
|
||||||
|
import DatePicker from '@vuepic/vue-datepicker'
|
||||||
|
import Complaint from '@/types/ComplaintType.vue'
|
||||||
|
import CustomerSearch from '@/components/CustomerSearch.vue'
|
||||||
|
import DriverSearch from '@/components/DriverSearch.vue'
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
setcomplaint: new Complaint()
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
CustomerSearch,
|
||||||
|
DriverSearch,
|
||||||
|
DatePicker
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
setcomplaint(newval) {
|
||||||
|
this.complaint = newval
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mixins: [methods],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
complaint: this.setcomplaint,
|
||||||
|
saving: false,
|
||||||
|
info_loading: false,
|
||||||
|
search: [],
|
||||||
|
customer_search: null,
|
||||||
|
customers_loading: false,
|
||||||
|
customers: [],
|
||||||
|
errors: [],
|
||||||
|
reasons: [],
|
||||||
|
debugPanel: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
title() {
|
||||||
|
if ( this.complaint.isNew ) {
|
||||||
|
return "New Complaint"
|
||||||
|
} else {
|
||||||
|
return "Edit Complaint"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['closetab','complaintupdate'],
|
||||||
|
methods: {
|
||||||
|
close() {
|
||||||
|
this.$emit('closetab')
|
||||||
|
},
|
||||||
|
showCustomerSearch() {
|
||||||
|
this.search[0] = true
|
||||||
|
},
|
||||||
|
showDriverSearch() {
|
||||||
|
this.search[1] = true
|
||||||
|
},
|
||||||
|
async saveComplaint(){
|
||||||
|
this.errors = []
|
||||||
|
this.saving = true
|
||||||
|
let url = this.$api_url + "/customers/complaints/" + this.complaint.id + "/save"
|
||||||
|
if (this.complaint.isNew) {
|
||||||
|
url = this.$api_url + "/customers/complaints/add"
|
||||||
|
}
|
||||||
|
axios.post(url, {
|
||||||
|
complaint: this.complaint
|
||||||
|
}).then(resp => {
|
||||||
|
console.log("Saved Complaint : " + JSON.stringify(resp.data))
|
||||||
|
this.saving = false
|
||||||
|
let stat = resp.data
|
||||||
|
if (stat.status == true ) {
|
||||||
|
if (this.complaint.isNew) {
|
||||||
|
this.$emit('complaintupdate', resp.data)
|
||||||
|
} else {
|
||||||
|
this.$emit('complaintupdate', resp.data)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.errors.push("Complaint not saved.")
|
||||||
|
console.log("Not Saved")
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
this.saving = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setCustomer(c){
|
||||||
|
this.complaint.customer = c
|
||||||
|
this.search[0] = false
|
||||||
|
},
|
||||||
|
setDriver(d) {
|
||||||
|
this.complaint.driver = d
|
||||||
|
this.search[1] = false
|
||||||
|
},
|
||||||
|
getComplaintInfo(){
|
||||||
|
this.info_loading = true
|
||||||
|
let url = this.$api_url + "/customers/complaints/" + this.complaint.id + "/info"
|
||||||
|
console.log("Getting Complaint Info...")
|
||||||
|
axios.get(url)
|
||||||
|
.then(resp =>{
|
||||||
|
this.complaint.info = resp.data
|
||||||
|
}).finally(() => {
|
||||||
|
this.info_loading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getComplaintReasons() {
|
||||||
|
let url = this.$api_url + "/customers/complaints/reasons"
|
||||||
|
axios.get(url).
|
||||||
|
then(resp => {
|
||||||
|
this.reasons = resp.data
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (!this.complaint.isNew) {
|
||||||
|
this.getComplaintInfo()
|
||||||
|
}
|
||||||
|
this.getComplaintReasons()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,119 +1,88 @@
|
||||||
<template>
|
<template>
|
||||||
<h3>Complaints</h3>
|
<h3>Complaints</h3>
|
||||||
<v-tabs v-model="tab" fixed-tabs>
|
<v-tabs v-model="tab" >
|
||||||
<v-tab title="List" v-model="list" />
|
<v-tab title="List" value="isList"></v-tab>
|
||||||
<v-tab title="Edit" v-model="edit" v-if="edit" />
|
<v-tab title="Edit" value="edit" v-if="edit"></v-tab>
|
||||||
<v-tab title="Report" v-model="report" v-if="report" />
|
<v-tab title="Report" value="report" v-if="report" ></v-tab>
|
||||||
</v-tabs>
|
</v-tabs>
|
||||||
<v-window v-model="tab">
|
<v-window v-model="tab">
|
||||||
<v-window-item v-model="list">
|
<v-window-item value="isList">
|
||||||
<v-responsive
|
<v-col cols="12" xs="12" sm="12" md="12" lg=8>
|
||||||
max-width="500"
|
<v-text-field label="Search"
|
||||||
>
|
|
||||||
<v-text-field
|
|
||||||
clearable
|
|
||||||
label="Search"
|
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
v-model="searchQuery"
|
v-model="searchQuery"
|
||||||
density="compact"
|
density="compact"
|
||||||
append-inner-icon="mdi-magnify"></v-text-field>
|
append-inner-icon="mdi-magnify"></v-text-field>
|
||||||
<v-switch color="blue" label="Show Only Active" v-model="showActive"></v-switch>
|
<v-row justify="space-between">
|
||||||
<v-btn v-if="site_info.features.addcomplaint" color="warning">+ Add</v-btn>
|
<v-col cols=4>
|
||||||
</v-responsive>
|
<v-btn v-if="site_info.features.addcomplaint" color="warning" prepend-icon="mdi-plus" variant="text" @click="showAddComplaint">Add</v-btn>
|
||||||
<v-table>
|
</v-col>
|
||||||
<thead>
|
<v-col cols=3>
|
||||||
<tr>
|
<v-btn align="end" variant="text" @click="showActive = !showActive">
|
||||||
<th>Comp No</th>
|
Showing <span v-if="showActive">Active Only</span><span v-else>Inactive</span>
|
||||||
<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>
|
</v-btn>
|
||||||
</td>
|
</v-col>
|
||||||
</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>
|
||||||
<v-row dense nogutters>
|
<v-progress-linear indeterminate color="orange" :active="loading"></v-progress-linear>
|
||||||
<v-checkbox label="At Risk" v-model="complaint.at_risk" @change="changeComplaintCheckbox(complaint)" />
|
<v-card variant="outlined">
|
||||||
<v-checkbox label="Permanent" v-model="complaint.info.permanent" @change="changeComplaintCheckbox(complaint)" />
|
<RecycleScroller class="scroller"
|
||||||
|
:items="filteredComplaints"
|
||||||
|
:item-size="100"
|
||||||
|
v-slot="{ item }"
|
||||||
|
key-field="id">
|
||||||
|
<v-row dense class="item" :class="{ 'bg-red-lighten-4' : item.at_risk }">
|
||||||
|
<v-col cols="4">
|
||||||
|
Complaint : {{ item.id }}<br/>
|
||||||
|
<span class="text-caption">
|
||||||
|
Complaint Date : {{ item.complaint_date }}<br/>
|
||||||
|
Sales Order: {{ item.sop.doc_no }}<br/>
|
||||||
|
</span>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="4" class="text-body-2">
|
||||||
|
{{ item.customer.acc_no }} - {{ item.customer.name }}<br/>
|
||||||
|
Reason : {{ item.reason.reason }}<br/>
|
||||||
|
Driver : {{ item.driver.name }}
|
||||||
|
</v-col>
|
||||||
|
<v-col cols=4>
|
||||||
|
<div class="d-flex justify-space-around align-center flex-column flex-sm-row fill-height">
|
||||||
|
<v-row>
|
||||||
|
<v-icon icon="mdi-exclamation" v-if="item.at_risk" color="red" title="At Risk"></v-icon>
|
||||||
|
<v-icon icon="mdi-play" v-if="item.active" title="Active"></v-icon>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-form>
|
<v-btn @click="showInfo(item)" color="blue-lighten-1">View</v-btn>
|
||||||
</v-container>
|
<v-btn v-if="site_info.features.editcomplaint" color="orange-lighten-2" @click="showEditComplaint(item)">Edit</v-btn>
|
||||||
</td>
|
</div>
|
||||||
<td>
|
</v-col>
|
||||||
<img v-if="complaint.info.loading" class="loading" src="/images/icons/loading.gif"/>
|
</v-row>
|
||||||
<template v-else>
|
</RecycleScroller>
|
||||||
<v-btn v-if="site_info.features.editcomplaint" color="warning">Edit</v-btn>
|
</v-card>
|
||||||
</template>
|
</v-col>
|
||||||
</td>
|
</v-window-item>
|
||||||
</template>
|
<v-window-item value="edit">
|
||||||
</template>
|
<ComplaintEdit ref="edit" :setcomplaint="selected_complaint" @closetab="tab = 'isList'" @complaintupdate="complaintUpdated"></ComplaintEdit>
|
||||||
<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-item>
|
||||||
</v-window>
|
</v-window>
|
||||||
|
<v-dialog v-model="showComplaintInfo">
|
||||||
|
<ComplaintInfo :in_complaint="selected_complaint" @return="doInfoReturn" />
|
||||||
|
</v-dialog>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
import Complaint from '@/types/ComplaintType.vue'
|
||||||
|
import ComplaintInfo from '@/components/ComplaintInfo.vue'
|
||||||
|
import ComplaintEdit from '@/views/complaints/ComplaintEdit.vue'
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
site_info:{},
|
site_info:{},
|
||||||
|
user_info:{}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
ComplaintInfo,
|
||||||
|
ComplaintEdit
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
tab: "list",
|
tab: "isList",
|
||||||
list: [],
|
list: [],
|
||||||
listreceived: false,
|
listreceived: false,
|
||||||
showActive: true,
|
showActive: true,
|
||||||
|
@ -121,7 +90,9 @@ export default {
|
||||||
limit: 300,
|
limit: 300,
|
||||||
searchQuery: "",
|
searchQuery: "",
|
||||||
edit: false,
|
edit: false,
|
||||||
report: false
|
report: false,
|
||||||
|
showComplaintInfo: false,
|
||||||
|
selected_complaint: {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -134,7 +105,7 @@ export default {
|
||||||
q.customer.name.toLowerCase().includes(query) ||
|
q.customer.name.toLowerCase().includes(query) ||
|
||||||
q.customer.acc_no.includes(query) ||
|
q.customer.acc_no.includes(query) ||
|
||||||
q.id == query ||
|
q.id == query ||
|
||||||
q.reason.toLowerCase().includes(query)
|
q.reason.reason.toLowerCase().includes(query)
|
||||||
)
|
)
|
||||||
if (this.showActive) {
|
if (this.showActive) {
|
||||||
clist = clist.filter(q =>
|
clist = clist.filter(q =>
|
||||||
|
@ -145,64 +116,65 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async getComplaintsList(){
|
getComplaintsList(){
|
||||||
this.loading = true
|
this.loading = true
|
||||||
let url = this.$api_url + "/customers/complaints/list"
|
let url = this.$api_url + "/customers/complaints/list"
|
||||||
console.log("Getting Complaint list...")
|
let c_id = this.$route.params.id || ""
|
||||||
|
console.log("Getting Contracts list..." + c_id)
|
||||||
axios.get(url,{
|
axios.get(url,{
|
||||||
params: {
|
params: {
|
||||||
limit: this.limit,
|
limit: this.limit,
|
||||||
query: this.searchQuery
|
query: this.searchQuery,
|
||||||
|
c_id: c_id
|
||||||
}
|
}
|
||||||
}).then(resp => {
|
}).then(resp => {
|
||||||
this.list = resp.data
|
this.list = resp.data
|
||||||
this.loading = false
|
|
||||||
this.listreceived = true
|
this.listreceived = true
|
||||||
|
}).catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
}).finally(() => {
|
||||||
|
this.loading = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async getComplaintInfo(complaint) {
|
showInfo(complaint) {
|
||||||
complaint.info.loading = true
|
this.showComplaintInfo = true
|
||||||
let url = this.$api_url + "/customers/complaints/" + complaint.id + "/info"
|
this.selected_complaint = complaint
|
||||||
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_loaded = false
|
||||||
complaint.info_shown = !complaint.info_shown
|
},
|
||||||
if (complaint.info_shown) {
|
doInfoReturn(code) {
|
||||||
complaint.info = await this.getComplaintInfo(complaint)
|
console.log(code)
|
||||||
complaint.info_loaded = true
|
},
|
||||||
|
showAddComplaint() {
|
||||||
|
this.selected_complaint = new Complaint()
|
||||||
|
this.selected_complaint.isNew = true
|
||||||
|
this.edit = true
|
||||||
|
this.tab = "edit"
|
||||||
|
},
|
||||||
|
showEditComplaint(cmp) {
|
||||||
|
this.selected_complaint = cmp
|
||||||
|
this.selected_complaint.isNew = false
|
||||||
|
this.edit = true
|
||||||
|
this.tab = "edit"
|
||||||
|
},
|
||||||
|
complaintUpdated(cmp) {
|
||||||
|
console.log(cmp)
|
||||||
|
this.tab = "isList"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
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>
|
</script>
|
||||||
|
<style>
|
||||||
|
.scroller {
|
||||||
|
height:600px;
|
||||||
|
}
|
||||||
|
.item {
|
||||||
|
height: 100px;
|
||||||
|
overflow-y:hidden;
|
||||||
|
padding: 0 1em;
|
||||||
|
margin-bottom:2px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<v-card title="Edit Contract" :subtitle="'Contract : ' + contract.no">
|
<v-card :title="title" :subtitle="'Contract : ' + contract.no">
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-container>
|
<v-container>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="6">
|
<v-col cols="6">
|
||||||
<v-text-field label="Customer" v-model="contract.customer.name" readonly></v-text-field>
|
<v-text-field readonly variant="outlined" prepend-inner-icon="mdi-magnify" @click="showCustomerSearch" label="Customer" :model-value="contract.customer.acc_no + ' - ' + contract.customer.name">
|
||||||
<v-text-field type="number" label="Tonnage Per Month" v-model="contract.tonnage_per_month"></v-text-field>
|
</v-text-field>
|
||||||
|
<v-text-field type="number" variant="outlined" label="Tonnage Per Month" v-model="contract.tonnage_per_month"></v-text-field>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="6">
|
<v-col cols="6">
|
||||||
<label>
|
<label>
|
||||||
|
@ -18,33 +19,36 @@
|
||||||
</label><br />
|
</label><br />
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<template v-for="p in contract.products" :key="p.code">
|
<v-table density="compact">
|
||||||
<v-row>
|
<thead>
|
||||||
<v-col cols="6">
|
<tr>
|
||||||
<v-autocomplete v-model="p.code"
|
<th style="width:65%;">
|
||||||
v-model:search="product_search"
|
Product
|
||||||
:loading="products_loading"
|
</th>
|
||||||
:items="products"
|
<th style="width:20%">
|
||||||
cache-items
|
Price
|
||||||
hide-no-data
|
</th>
|
||||||
hide-details
|
<th>
|
||||||
solo-inverted
|
</th>
|
||||||
label="Code"
|
</tr>
|
||||||
no-data-text="No Products Found"
|
</thead>
|
||||||
item-title="code"
|
<tbody>
|
||||||
item-value="code" ></v-autocomplete>
|
<tr v-for="(p, index) in contract.products" :key="index">
|
||||||
</v-col>
|
<td>
|
||||||
<v-col cols="4">
|
<v-text-field readonly variant="outlined" prepend-inner-icon="mdi-magnify" @click="showProductSearch(index)" :model-value="p.code + ' - ' + p.name"></v-text-field>
|
||||||
<v-text-field type="number" label="Price" v-model="p.price"></v-text-field>
|
</td>
|
||||||
</v-col>
|
<td>
|
||||||
<v-col cols="2">
|
<v-text-field prepend-icon="mdi-currency-gbp" type="number" density="compact" variant="outlined" v-model="p.price"></v-text-field>
|
||||||
<v-btn size="small" color="error" title="Remove" variant="plain" icon @click="contract.products.pop()"><v-icon>mdi-minus</v-icon></v-btn>
|
</td>
|
||||||
</v-col>
|
<td>
|
||||||
</v-row>
|
<v-btn size="small" color="error" title="Remove" variant="plain" @click="removeProduct(p.code)" icon="mdi-minus"></v-btn>
|
||||||
</template>
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</v-table>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col cols="4">
|
<v-col cols="4">
|
||||||
<v-btn @click="contract.products.push({})" v-if="contract.products.length < 4">+ Add Product</v-btn>
|
<v-btn @click="addProduct()" v-if="contract.products.length < 4">+ Add Product</v-btn>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<v-row>
|
<v-row>
|
||||||
|
@ -55,107 +59,145 @@
|
||||||
</label>
|
</label>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12">
|
<v-col cols="12">
|
||||||
<v-textarea rows=3 label="Comments" v-model="contract.comments"></v-textarea>
|
<v-textarea rows=3 label="Comments" variant="outlined" v-model="contract.comments"></v-textarea>
|
||||||
<v-textarea rows=3 label="Office Comments" v-model="contract.office_comments"></v-textarea>
|
<v-textarea rows=3 label="Office Comments" variant="outlined" v-model="contract.office_comments"></v-textarea>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-container>
|
</v-container>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
<DebugPanel :data="contract"></DebugPanel>
|
||||||
|
<ErrorBanner :errors="errors" />
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-btn color="red-darken-1"
|
<v-btn v-if="!contract.isNew" color="red-darken-1"
|
||||||
variant="text"
|
variant="text"
|
||||||
|
:loading="saving"
|
||||||
@click="saveContract(selected_contract)">Save</v-btn>
|
@click="saveContract(selected_contract)">Save</v-btn>
|
||||||
|
<v-btn v-if="contract.isNew" color="red-darken-1"
|
||||||
|
variant="text"
|
||||||
|
:loading="saving"
|
||||||
|
@click="saveContract(selected_contract)">Add</v-btn>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-btn color="blue-darken-1"
|
<v-btn color="blue-darken-1"
|
||||||
variant="text"
|
variant="text"
|
||||||
@click="close">Close</v-btn>
|
@click="close">Close</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
<v-dialog v-model="search[0]" scrollable>
|
||||||
|
<CustomerSearch @returnCustomer="setCustomer"></CustomerSearch>
|
||||||
|
</v-dialog>
|
||||||
|
<v-dialog v-model="search[1]">
|
||||||
|
<ProductSearch @returnProduct="setProduct"></ProductSearch>
|
||||||
|
</v-dialog>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import DatePicker from '@vuepic/vue-datepicker'
|
import DatePicker from '@vuepic/vue-datepicker'
|
||||||
|
import ErrorBanner from '@/components/ErrorBanner.vue'
|
||||||
|
import methods from '@/CommonMethods.vue'
|
||||||
|
import Product from '@/types/ProductType.vue'
|
||||||
|
import ProductSearch from '@/components/ProductSearch.vue'
|
||||||
|
import CustomerSearch from '@/components/CustomerSearch.vue'
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
setcontract: {
|
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: {
|
components: {
|
||||||
DatePicker
|
DatePicker,
|
||||||
|
ErrorBanner,
|
||||||
|
ProductSearch,
|
||||||
|
CustomerSearch
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
setcontract(newval) {
|
setcontract(newval) {
|
||||||
this.contract = newval
|
this.contract = newval
|
||||||
},
|
},
|
||||||
product_search(val) {
|
|
||||||
if (val && val.length > 1) {
|
|
||||||
this.searchProducts(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
mixins: [methods],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
contract: this.setcontract,
|
contract: this.setcontract,
|
||||||
dialog: this.opendialog,
|
dialog: this.opendialog,
|
||||||
saving: false,
|
saving: false,
|
||||||
|
search: [],
|
||||||
|
searchProdIndex: null,
|
||||||
product_search: null,
|
product_search: null,
|
||||||
products_loading: false,
|
products_loading: false,
|
||||||
products: [],
|
products: [],
|
||||||
|
customer_search: null,
|
||||||
|
customers_loading: false,
|
||||||
|
customers: [],
|
||||||
|
errors: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
title() {
|
||||||
|
if ( this.contract.isNew ) {
|
||||||
|
return "New Contract"
|
||||||
|
} else {
|
||||||
|
return "Edit Contract"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['closetab','contractupdate'],
|
||||||
methods: {
|
methods: {
|
||||||
close() {
|
close() {
|
||||||
this.$emit('closetab','list')
|
this.$emit('closetab','list')
|
||||||
},
|
},
|
||||||
|
addProduct() {
|
||||||
|
this.contract.products.push(new Product())
|
||||||
|
},
|
||||||
|
removeProduct(code){
|
||||||
|
this.contract.products = this.contract.products.filter((c) => {
|
||||||
|
return c.code !== code
|
||||||
|
})
|
||||||
|
},
|
||||||
|
showCustomerSearch() {
|
||||||
|
this.search[0] = true
|
||||||
|
},
|
||||||
|
showProductSearch(num) {
|
||||||
|
this.search[1] = true
|
||||||
|
this.searchProdIndex = num
|
||||||
|
},
|
||||||
async saveContract(){
|
async saveContract(){
|
||||||
|
this.errors = []
|
||||||
this.saving = true
|
this.saving = true
|
||||||
let url = this.$api_url + "/customers/contracts/" + this.contract.no + "/save"
|
let url = this.$api_url + "/customers/contracts/" + this.contract.no + "/save"
|
||||||
console.log("Saving Contract : ", this.contract.no)
|
if (this.contract.isNew) {
|
||||||
console.log(this.contract)
|
url = this.$api_url + "/customers/contracts/add"
|
||||||
|
}
|
||||||
axios.post(url, {
|
axios.post(url, {
|
||||||
contract: this.contract
|
contract: this.contract
|
||||||
}).then(resp => {
|
}).then(resp => {
|
||||||
console.log("Saved Contract : " + JSON.stringify(resp.data))
|
console.log("Saved Contract : " + JSON.stringify(resp.data))
|
||||||
this.saving = false
|
this.saving = false
|
||||||
|
let stat = resp.data
|
||||||
|
if (stat.status == true ) {
|
||||||
|
if (this.contract.isNew) {
|
||||||
this.$emit('contractupdate', resp.data)
|
this.$emit('contractupdate', resp.data)
|
||||||
|
} else {
|
||||||
|
this.$emit('contractupdate', resp.data)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.errors.push("Contract not saved.")
|
||||||
|
console.log("Not Saved")
|
||||||
|
}
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.log(err)
|
console.log(err)
|
||||||
this.saving = false
|
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) {
|
productCodeName(p) {
|
||||||
return p.code + ' - ' + p.name
|
return p.code + ' - ' + p.name
|
||||||
|
},
|
||||||
|
setProduct(p){
|
||||||
|
let q = this.contract.products[this.searchProdIndex]
|
||||||
|
p.price = q.price
|
||||||
|
this.contract.products[this.searchProdIndex] = p
|
||||||
|
this.search[1] = false
|
||||||
|
},
|
||||||
|
setCustomer(c){
|
||||||
|
this.contract.customer = c
|
||||||
|
this.search[0] = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,21 +7,22 @@
|
||||||
</v-tabs>
|
</v-tabs>
|
||||||
<v-window v-model="tab" >
|
<v-window v-model="tab" >
|
||||||
<v-window-item value="list">
|
<v-window-item value="list">
|
||||||
<v-responsive
|
<v-row>
|
||||||
max-width="500"
|
<v-col cols="8" xs="12" sm="12" md="12" lg="8">
|
||||||
>
|
|
||||||
<v-text-field clearable
|
<v-text-field clearable
|
||||||
label="Search"
|
label="Search"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
v-model="searchQuery"
|
v-model="searchQuery"
|
||||||
density="compact"
|
density="compact"
|
||||||
append-inner-icon="mdi-magnify"></v-text-field>
|
append-inner-icon="mdi-magnify"></v-text-field>
|
||||||
</v-responsive>
|
<v-btn color="warning"
|
||||||
<v-dialog v-model="showDialog">
|
v-if="site_info.features.addcontract"
|
||||||
</v-dialog>
|
@click="showEditContract()"
|
||||||
<v-row>
|
prepend-icon="mdi-plus"
|
||||||
<v-col cols="8" xs="12" sm="12" md="8">
|
variant="text"
|
||||||
|
>Add</v-btn>
|
||||||
<v-progress-linear indeterminate color="blue" :active="loading"></v-progress-linear>
|
<v-progress-linear indeterminate color="blue" :active="loading"></v-progress-linear>
|
||||||
|
<v-card variant="outlined">
|
||||||
<RecycleScroller class="scroller"
|
<RecycleScroller class="scroller"
|
||||||
:items="filteredContracts"
|
:items="filteredContracts"
|
||||||
:item-size="130"
|
:item-size="130"
|
||||||
|
@ -52,8 +53,7 @@
|
||||||
= {{ formatNumber(item.tonnage_per_month * item.remaining_duration,2) }} remaining<br/>
|
= {{ formatNumber(item.tonnage_per_month * item.remaining_duration,2) }} remaining<br/>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col>
|
<v-col>
|
||||||
<v-row>
|
<div class="d-flex justify-space-around align-center flex-column flex-sm-row fill-height">
|
||||||
<v-col cols="12">
|
|
||||||
<v-btn color="info">
|
<v-btn color="info">
|
||||||
More
|
More
|
||||||
<v-overlay activator="parent" class="align-center justify-center">
|
<v-overlay activator="parent" class="align-center justify-center">
|
||||||
|
@ -77,7 +77,7 @@
|
||||||
target="blank"
|
target="blank"
|
||||||
@click="getContractPrint(item.no)"
|
@click="getContractPrint(item.no)"
|
||||||
class="ma-2 pa-2"
|
class="ma-2 pa-2"
|
||||||
:loading="item.multiloading"
|
:loading="multiloading"
|
||||||
>
|
>
|
||||||
Multi
|
Multi
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
@ -85,7 +85,7 @@
|
||||||
<v-btn color="info"
|
<v-btn color="info"
|
||||||
target="blank"
|
target="blank"
|
||||||
@click="getContractPrint(item.no,true)"
|
@click="getContractPrint(item.no,true)"
|
||||||
:loading="item.totalloading"
|
:loading="totalloading"
|
||||||
class="ma-2 pa-2"
|
class="ma-2 pa-2"
|
||||||
>
|
>
|
||||||
Total
|
Total
|
||||||
|
@ -93,6 +93,7 @@
|
||||||
<v-btn color="warning"
|
<v-btn color="warning"
|
||||||
v-if="site_info.features.editcontract"
|
v-if="site_info.features.editcontract"
|
||||||
@click="showEditContract(item)"
|
@click="showEditContract(item)"
|
||||||
|
:loading="editloading"
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
@ -100,19 +101,18 @@
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-overlay>
|
</v-overlay>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-col>
|
|
||||||
<v-col cols="12">
|
|
||||||
<v-btn color="warning"
|
<v-btn color="warning"
|
||||||
v-if="site_info.features.editcontract"
|
v-if="site_info.features.editcontract"
|
||||||
@click="showEditContract(item)"
|
@click="showEditContract(item)"
|
||||||
|
:loading="editloading"
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-col>
|
</div>
|
||||||
</v-row>
|
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</RecycleScroller>
|
</RecycleScroller>
|
||||||
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-window-item>
|
</v-window-item>
|
||||||
|
@ -123,11 +123,14 @@
|
||||||
<ContractMulti :contract="selected_contract" :total="total" />
|
<ContractMulti :contract="selected_contract" :total="total" />
|
||||||
</v-window-item>
|
</v-window-item>
|
||||||
</v-window>
|
</v-window>
|
||||||
|
<v-dialog v-model="showDialog">
|
||||||
|
</v-dialog>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import ContractEdit from './ContractEdit.vue';
|
import ContractEdit from './ContractEdit.vue';
|
||||||
import ContractMulti from './ContractMulti.vue';
|
import ContractMulti from './ContractMulti.vue';
|
||||||
import methods from '@/CommonMethods.vue'
|
import methods from '@/CommonMethods.vue'
|
||||||
|
import ContractType from '@/types/ContractType.vue';
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
@ -179,25 +182,37 @@ export default {
|
||||||
edit: false,
|
edit: false,
|
||||||
report: false,
|
report: false,
|
||||||
total: false,
|
total: false,
|
||||||
content: ""
|
content: "",
|
||||||
|
editloading: false,
|
||||||
|
multiloading: false,
|
||||||
|
totalloading: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mixins: [methods],
|
mixins: [methods],
|
||||||
methods: {
|
methods: {
|
||||||
async showEditContract(contract) {
|
async showEditContract(contract) {
|
||||||
|
this.editloading = true
|
||||||
|
setTimeout(() => {
|
||||||
|
if ( contract != undefined ) {
|
||||||
this.selected_contract = contract
|
this.selected_contract = contract
|
||||||
//this.selected_contract = contract
|
} else {
|
||||||
|
this.selected_contract = new ContractType()
|
||||||
|
}
|
||||||
this.tab = "edit"
|
this.tab = "edit"
|
||||||
this.edit = true
|
this.edit = true
|
||||||
|
this.editloading = false
|
||||||
|
},1000)
|
||||||
},
|
},
|
||||||
async getContractsList() {
|
async getContractsList() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
let url = this.$api_url + "/customers/contracts/list"
|
let url = this.$api_url + "/customers/contracts/list"
|
||||||
console.log("Getting Contracts list...")
|
let c_id = this.$route.params.id || ""
|
||||||
|
console.log("Getting Contracts list..." + c_id)
|
||||||
axios.get(url, {
|
axios.get(url, {
|
||||||
params: {
|
params: {
|
||||||
limit: this.limit,
|
limit: this.limit,
|
||||||
query: this.searchQuery
|
query: this.searchQuery,
|
||||||
|
c_id: c_id
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
|
@ -207,12 +222,16 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
getContractPrint(contract, total = false) {
|
getContractPrint(contract, total = false) {
|
||||||
|
if (total){ this.totalloading = true } else { this.multiloading = true }
|
||||||
axios.get(this.$api_url + "/customers/contracts/" + contract + "/info")
|
axios.get(this.$api_url + "/customers/contracts/" + contract + "/info")
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
this.selected_contract = resp.data
|
this.selected_contract = resp.data
|
||||||
this.total = total
|
this.total = total
|
||||||
this.tab = "report"
|
this.tab = "report"
|
||||||
this.report = true
|
this.report = true
|
||||||
|
}).finally(() => {
|
||||||
|
this.totalloading = false
|
||||||
|
this.multiloading = false
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
findContract(id) {
|
findContract(id) {
|
||||||
|
@ -249,7 +268,7 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.scroller {
|
.scroller {
|
||||||
height: 600px;
|
height: 70vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<ReportLayout scope="contract">
|
<ReportLayout scope="contract" :filename="filename">
|
||||||
<div class="letter">
|
<div class="letter">
|
||||||
<p><span class="text-bold">Date: </span>{{ current_date }}</p>
|
<p><span class="text-bold">Date: </span>{{ current_date }}</p>
|
||||||
<p class="text-bold">Customer's Address:</p>
|
<p class="text-bold">Customer's Address:</p>
|
||||||
|
@ -57,7 +57,6 @@
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import '@/assets/css/reports.css';
|
|
||||||
import ReportLayout from '@/components/ReportLayout.vue'
|
import ReportLayout from '@/components/ReportLayout.vue'
|
||||||
import Common from '@/common.js';
|
import Common from '@/common.js';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
@ -72,6 +71,7 @@ export default {
|
||||||
},
|
},
|
||||||
data(){
|
data(){
|
||||||
return {
|
return {
|
||||||
|
filename: "Contract_" + this.contract.no,
|
||||||
current_date: Common.getDateNow(),
|
current_date: Common.getDateNow(),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,33 +1,76 @@
|
||||||
<template>
|
<template>
|
||||||
<h3>Customer List</h3>
|
|
||||||
<v-responsive
|
|
||||||
max-width="500"
|
|
||||||
>
|
|
||||||
<v-text-field
|
<v-text-field
|
||||||
clearable
|
|
||||||
label="Search"
|
label="Search"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
v-model="searchQuery"
|
v-model="searchQuery"
|
||||||
density="compact"
|
density="compact"
|
||||||
append-inner-icon="mdi-magnify"></v-text-field>
|
append-inner-icon="mdi-magnify"></v-text-field>
|
||||||
</v-responsive>
|
<v-row>
|
||||||
|
<v-col cols="12" sm=12 lg=6>
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>
|
||||||
|
Customer List
|
||||||
|
</v-card-title>
|
||||||
|
<v-progress-linear color="blue" :active="customers_loading" indeterminate>
|
||||||
|
</v-progress-linear>
|
||||||
|
<v-list>
|
||||||
<RecycleScroller
|
<RecycleScroller
|
||||||
class="scroller"
|
class="scroller"
|
||||||
:items="filteredCustomers"
|
:items="filteredCustomers"
|
||||||
:item-size="50"
|
:item-size="60"
|
||||||
key-field="acc_no"
|
key-field="acc_no"
|
||||||
v-slot="{ item }"
|
v-slot="{ item }"
|
||||||
>
|
>
|
||||||
<v-row class="customer">
|
<v-list-item >
|
||||||
<v-col cols="6">
|
<v-sheet class="clickable" @click="getCustomerInfo(item)" rounded :class="{ 'bg-error' : item.at_risk }">
|
||||||
{{ item.acc_no }} - {{ item.name }}
|
<v-icon v-if="item.acc_no == selected_cust.acc_no">mdi-checkbox-marked-outline</v-icon>
|
||||||
|
<v-icon v-else>mdi-checkbox-blank-outline</v-icon>
|
||||||
|
{{ item.acc_no }} - {{ item.name }}:
|
||||||
|
<span class="text-caption">{{ item.address.line_1 }}, {{ item.address.line_2 }}, <br/>
|
||||||
|
{{ item.address.city }}, {{ item.address.county}}, {{ item.address.postcode }},
|
||||||
|
</span>
|
||||||
|
</v-sheet>
|
||||||
|
<template v-slot:append>
|
||||||
|
<v-btn-toggle>
|
||||||
|
<v-btn @click.prevent="viewOrders(item)" color="blue-lighten-1" title="View Orders">
|
||||||
|
<v-icon>mdi-cart-outline</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn @click.prevent="goToContracts(item)" color="blue-lighten-1" title="View Contracts">
|
||||||
|
<v-icon>mdi-file-edit-outline</v-icon>
|
||||||
|
<v-badge v-if="item.contract_count > 0" color="blue-lighten-4" floating :content="item.contract_count">
|
||||||
|
</v-badge>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn @click.prevent="goToMedFeeds(item)" color="orange-lighten-1" title="View Med Feeds">
|
||||||
|
<v-icon>mdi-pill</v-icon>
|
||||||
|
<v-badge v-if="item.medfeed_count > 0" color="orange-lighten-4" floating :content="item.medfeed_count">
|
||||||
|
</v-badge>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn @click.prevent="goToComplaints(item)" color="orange-lighten-1" title="View Complaints">
|
||||||
|
<v-icon>mdi-exclamation-thick</v-icon>
|
||||||
|
<v-badge v-if="item.complaint_count > 0" color="red-lighten-4" floating :content="item.complaint_count">
|
||||||
|
</v-badge>
|
||||||
|
</v-btn>
|
||||||
|
</v-btn-toggle>
|
||||||
|
</template>
|
||||||
|
</v-list-item>
|
||||||
|
</RecycleScroller>
|
||||||
|
</v-list>
|
||||||
|
</v-card>
|
||||||
|
<br/>
|
||||||
|
<RecentOrders :customer="selected_cust" size_small doc_status=0></RecentOrders>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="12" sm=12 lg=6>
|
||||||
|
<CustomerComments :customer="selected_cust"></CustomerComments>
|
||||||
|
<br/>
|
||||||
|
<RecentOrders :customer="selected_cust" doc_status=2></RecentOrders>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
<hr />
|
|
||||||
</RecycleScroller>
|
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import RecentOrders from '@/components/RecentOrders.vue'
|
||||||
|
import CustomerComments from '@/components/CustomerComments.vue'
|
||||||
|
import Customer from '@/types/CustomerType.vue'
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
site_info: {},
|
site_info: {},
|
||||||
|
@ -36,15 +79,16 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
searchQuery: "",
|
searchQuery: "",
|
||||||
showSearch: true,
|
customer_list: [],
|
||||||
open: false,
|
customers_loading: null,
|
||||||
list: [],
|
selected_cust: new Customer(),
|
||||||
limit: 5000,
|
limit: 5000,
|
||||||
loading: true,
|
listreceived: false,
|
||||||
listreceived: false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
RecentOrders,
|
||||||
|
CustomerComments
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
filteredCustomers() {
|
filteredCustomers() {
|
||||||
|
@ -52,38 +96,58 @@ export default {
|
||||||
if (!this.listreceived){
|
if (!this.listreceived){
|
||||||
this.getCustomerList()
|
this.getCustomerList()
|
||||||
}
|
}
|
||||||
let clist = this.list.filter(q =>
|
let clist = this.customer_list.filter(q =>
|
||||||
q.name.toLowerCase().includes(query) ||
|
q.name.toLowerCase().includes(query) ||
|
||||||
q.acc_no.includes(query)
|
q.acc_no.includes(query) ||
|
||||||
|
q.address.line_1.toLowerCase().includes(query) ||
|
||||||
|
q.address.line_2.toLowerCase().includes(query) ||
|
||||||
|
q.address.city.toLowerCase().includes(query) ||
|
||||||
|
q.address.postcode.toLowerCase().includes(query)
|
||||||
)
|
)
|
||||||
return clist
|
return clist
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getCustomerInfo(c) {
|
||||||
|
this.selected_cust = c
|
||||||
|
},
|
||||||
|
goToContracts(c){
|
||||||
|
this.$router.push('/customers/contracts/list/' + c.id)
|
||||||
|
},
|
||||||
|
goToMedFeeds(c){
|
||||||
|
this.$router.push('/customers/medicated-feeds/list/' + c.id)
|
||||||
|
},
|
||||||
|
goToComplaints(c){
|
||||||
|
this.$router.push('/customers/complaints/list/' + c.id)
|
||||||
|
},
|
||||||
|
viewOrders(c){
|
||||||
|
this.$router.push('/customers/orders/list/' + c.id)
|
||||||
|
},
|
||||||
async getCustomerList() {
|
async getCustomerList() {
|
||||||
this.loading = true
|
this.customers_loading = true
|
||||||
let url = this.$api_url + "/customers/list"
|
let url = this.$api_url + "/customers/list"
|
||||||
axios
|
axios
|
||||||
.get(url,{
|
.get(url,{
|
||||||
params: { limit: this.limit, query: this.searchQuery }})
|
params: {
|
||||||
|
limit: this.limit,
|
||||||
|
query: "" }})
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
this.list = resp.data
|
this.customer_list = resp.data
|
||||||
this.listreceived = true
|
this.listreceived = true
|
||||||
this.loading = false
|
|
||||||
})
|
})
|
||||||
.catch(error => (console.log(error)))
|
.catch(error => (console.log(error)))
|
||||||
|
.finally(() => {
|
||||||
|
this.customers_loading = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.scroller {
|
.scroller {
|
||||||
height: 500px;
|
height: 300px;
|
||||||
}
|
}
|
||||||
.customer {
|
.scroller.small {
|
||||||
height: 32%;
|
height: 200px;
|
||||||
padding: 0 12px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,3 +1,191 @@
|
||||||
<template>
|
<template>
|
||||||
Not Implemented, yet :-)
|
<v-card :title="title" :subtitle="'Medicated Feed : ' + mf.id">
|
||||||
|
<v-card-text>
|
||||||
|
<v-container>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6">
|
||||||
|
<v-text-field label="Customer" readonly variant="outlined" prepend-inner-icon="mdi-magnify" @click="showCustomerSearch" :model-value="mf.customer.acc_no + ' - ' + mf.customer.name">
|
||||||
|
</v-text-field>
|
||||||
|
<v-text-field label="Vet" readonly variant="outlined" prepend-inner-icon="mdi-magnify" @click="showVetSearch" :model-value="mf.vet.practice">
|
||||||
|
</v-text-field>
|
||||||
|
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="6">
|
||||||
|
<v-card title="Medication :">
|
||||||
|
<v-card-text>
|
||||||
|
<v-text-field label="Medication" readonly variant="outlined" prepend-inner-icon="mdi-magnify" @click="showMedSearch" :model-value="mf.medication.name">
|
||||||
|
</v-text-field>
|
||||||
|
<template v-for="(i, idx) in mf.medication.info" :key="idx" >
|
||||||
|
<template v-if="i != ''">
|
||||||
|
{{ i }}<br/>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
Med Code : {{ mf.medication.med_code }}<br/>
|
||||||
|
Inclusion Rate : {{ mf.medication.inclusion_rate }}
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12">
|
||||||
|
<v-text-field label="Product" readonly variant="outlined" prepend-inner-icon="mdi-magnify" @click="showProductSearch" :model-value="mf.product.code + ' - ' + mf.product.name">
|
||||||
|
</v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="6">
|
||||||
|
<v-text-field label="Tonnage" variant="outlined" type="number" v-model="mf.tonnage"></v-text-field>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="6">
|
||||||
|
<label>
|
||||||
|
Date Required :
|
||||||
|
<DatePicker v-model="mf.date_required" format="dd/MM/yyyy" />
|
||||||
|
</label>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="6">
|
||||||
|
<v-switch color="blue" label="Repeat prescription" v-model="mf.repeat"></v-switch>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols="6">
|
||||||
|
<v-textarea :disabled="!mf.repeat" label="Repeat Message" v-model="mf.repeat_message" variant="outlined"></v-textarea>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</v-card-text>
|
||||||
|
<DebugPanel :data="mf"></DebugPanel>
|
||||||
|
<ErrorBanner :errors="errors" />
|
||||||
|
<v-card-actions>
|
||||||
|
<v-btn v-if="!mf.isNew" color="red-darken-1"
|
||||||
|
variant="text"
|
||||||
|
:loading="saving"
|
||||||
|
@click="saveMedFeed(mf)">Save</v-btn>
|
||||||
|
<v-btn v-if="mf.isNew" color="red-darken-1"
|
||||||
|
variant="text"
|
||||||
|
:loading="saving"
|
||||||
|
@click="saveMedFeed(mf)">Add</v-btn>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="blue-darken-1"
|
||||||
|
variant="text"
|
||||||
|
@click="close">Close</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
<v-dialog v-model="search[0]">
|
||||||
|
<CustomerSearch @returnCustomer="setCustomer"></CustomerSearch>
|
||||||
|
</v-dialog>
|
||||||
|
<v-dialog v-model="search[1]">
|
||||||
|
<ProductSearch @returnProduct="setProduct"></ProductSearch>
|
||||||
|
</v-dialog>
|
||||||
|
<v-dialog v-model="search[2]">
|
||||||
|
<VetSearch @returnVet="setVet"></VetSearch>
|
||||||
|
</v-dialog>
|
||||||
|
<v-dialog v-model="search[3]">
|
||||||
|
<MedSearch @returnMed="setMed"></MedSearch>
|
||||||
|
</v-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
<script>
|
||||||
|
import DatePicker from '@vuepic/vue-datepicker'
|
||||||
|
import axios from 'axios';
|
||||||
|
import CustomerSearch from '@/components/CustomerSearch.vue'
|
||||||
|
import ProductSearch from '@/components/ProductSearch.vue'
|
||||||
|
import VetSearch from '@/components/VetSearch.vue'
|
||||||
|
import MedSearch from '@/components/MedSearch.vue'
|
||||||
|
import ErrorBanner from '@/components/ErrorBanner.vue'
|
||||||
|
export default {
|
||||||
|
props:{
|
||||||
|
set_mf: {}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
DatePicker,
|
||||||
|
CustomerSearch,
|
||||||
|
ProductSearch,
|
||||||
|
ErrorBanner,
|
||||||
|
VetSearch,
|
||||||
|
MedSearch
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
set_mf(newval) {
|
||||||
|
this.mf = newval
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
mf: this.set_mf,
|
||||||
|
vets: [],
|
||||||
|
medications: [],
|
||||||
|
products: [],
|
||||||
|
search: [],
|
||||||
|
searching: {},
|
||||||
|
errors: [],
|
||||||
|
saving: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
title() {
|
||||||
|
if ( this.mf.isNew ) {
|
||||||
|
return "New Medicated Feed"
|
||||||
|
} else {
|
||||||
|
return "Edit Medicated Feed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
emits: ['closetab','medfeedupdated'],
|
||||||
|
methods: {
|
||||||
|
close() {
|
||||||
|
this.$emit('closetab','list')
|
||||||
|
},
|
||||||
|
saveMedFeed(medfeed) {
|
||||||
|
this.errors = []
|
||||||
|
this.saving = true
|
||||||
|
let url = this.$api_url + "/customers/medicated-feeds/" + this.mf.id + "/save"
|
||||||
|
if (this.mf.isNew) {
|
||||||
|
url = this.$api_url + "/customers/medicated-feeds/add"
|
||||||
|
}
|
||||||
|
console.log("Saving Med Feed...")
|
||||||
|
axios.post(url,{
|
||||||
|
medfeed: medfeed
|
||||||
|
})
|
||||||
|
.then(resp => {
|
||||||
|
let stat = resp.data
|
||||||
|
if (stat.status == true ) {
|
||||||
|
this.$emit('medfeedupdated')
|
||||||
|
} else {
|
||||||
|
this.errors.push("Error Saving... ")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
.finally(()=>{
|
||||||
|
this.saving = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
showCustomerSearch(){
|
||||||
|
this.search[0] = true
|
||||||
|
},
|
||||||
|
setCustomer(c){
|
||||||
|
this.mf.customer = c
|
||||||
|
this.search[0] = false
|
||||||
|
},
|
||||||
|
showProductSearch() {
|
||||||
|
this.search[1] = true
|
||||||
|
},
|
||||||
|
setProduct(p) {
|
||||||
|
this.mf.product = p
|
||||||
|
this.search[1] = false
|
||||||
|
},
|
||||||
|
showVetSearch() {
|
||||||
|
this.search[2] = true
|
||||||
|
},
|
||||||
|
setVet(v) {
|
||||||
|
this.mf.vet = v
|
||||||
|
this.search[2] = false
|
||||||
|
},
|
||||||
|
showMedSearch() {
|
||||||
|
this.search[3] = true
|
||||||
|
},
|
||||||
|
setMed(med) {
|
||||||
|
this.mf.medication = med
|
||||||
|
this.search[3] = false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
|
@ -10,38 +10,41 @@
|
||||||
<v-window-item value="list">
|
<v-window-item value="list">
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col>
|
<v-col>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="12" xs="12" sm="12" md="12" lg=8>
|
||||||
<v-text-field clearable
|
<v-text-field clearable
|
||||||
label="Search"
|
label="Search"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
v-model="searchQuery"
|
v-model="searchQuery"
|
||||||
density="compact"
|
density="compact"
|
||||||
append-inner-icon="mdi-magnify"></v-text-field>
|
append-inner-icon="mdi-magnify"></v-text-field>
|
||||||
</v-col>
|
<v-btn v-if="site_info.features.addmedfeed" color="warning" @click="editMedFeed()" prepend-icon="mdi-plus" variant="text">Add</v-btn>
|
||||||
</v-row>
|
|
||||||
<v-btn v-if="site_info.features.addmedfeed" color="warning" @click="editMedFeed({})">+ Add</v-btn>
|
|
||||||
<v-row>
|
|
||||||
<v-col cols="8" xs="12" sm="12" md="8">
|
|
||||||
<v-progress-linear indeterminate color="blue" :active="loading"></v-progress-linear>
|
<v-progress-linear indeterminate color="blue" :active="loading"></v-progress-linear>
|
||||||
|
<v-card variant="outlined">
|
||||||
<RecycleScroller class="scroller"
|
<RecycleScroller class="scroller"
|
||||||
:items="filteredMedFeeds"
|
:items="filteredMedFeeds"
|
||||||
:item-size="130"
|
:item-size="100"
|
||||||
v-slot="{ item }"
|
v-slot="{ item }"
|
||||||
key-field="id"
|
key-field="id">
|
||||||
>
|
<v-row dense class="item" :class="{ at_risk : item.customer.at_risk }">
|
||||||
<v-row class="item" :class="{ at_risk : item.customer.at_risk }">
|
|
||||||
<v-col cols="4">
|
<v-col cols="4">
|
||||||
Medicated Feed : {{ item.id }},
|
Medicated Feed : {{ item.id }}<br/>
|
||||||
{{ item.customer.acc_no }} - {{ item.customer.name }}
|
<span class="text-caption">
|
||||||
|
{{ item.customer.acc_no }} - {{ item.customer.name }}<br/>
|
||||||
|
Vet: {{ item.vet.practice }}</span>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col>
|
<v-col class="text-caption">
|
||||||
{{ item.medication.name }} {{ item.medication.inclusion_rate }}<br />
|
{{ item.medication.name }} {{ item.medication.inclusion_rate }}<br />
|
||||||
{{ item.product.name }}
|
{{ item.product.name }}
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col>
|
<v-col class="text-caption">
|
||||||
Required : {{ formatDate(item.date_required,"DD/MM/YYYY") }} <br/>
|
Required : {{ formatDate(item.date_required,"DD/MM/YYYY") }} <br/>
|
||||||
Repeat Prescription? : <v-icon v-if="item.repeat">mdi-refresh</v-icon>
|
Repeat Prescription? : <v-icon v-if="item.repeat">mdi-refresh</v-icon>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col>
|
<v-col>
|
||||||
|
<div class="d-flex justify-space-around align-center flex-column flex-sm-row fill-height">
|
||||||
<v-btn>
|
<v-btn>
|
||||||
More
|
More
|
||||||
<v-overlay activator="parent" class="align-center justify-center">
|
<v-overlay activator="parent" class="align-center justify-center">
|
||||||
|
@ -57,16 +60,18 @@
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-container>
|
</v-container>
|
||||||
</v-overlay>
|
</v-overlay>
|
||||||
</v-btn>
|
</v-btn><br/>
|
||||||
<v-btn v-if="site_info.editmedfeed" >Edit</v-btn>
|
<v-btn v-if="site_info.features.editmedfeed" color="warning" @click="editMedFeed(item)">Edit</v-btn>
|
||||||
|
</div>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</RecycleScroller>
|
</RecycleScroller>
|
||||||
|
</v-card>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-window-item>
|
</v-window-item>
|
||||||
<v-window-item value="edit">
|
<v-window-item value="edit">
|
||||||
<MedFeedsEdit />
|
<MedFeedsEdit :set_mf="selected_mf" @closetab="tab = 'list'" @medfeedupdated="medfeedUpdated" />
|
||||||
</v-window-item>
|
</v-window-item>
|
||||||
<v-window-item value="scriptreq">
|
<v-window-item value="scriptreq">
|
||||||
<ScriptReq :mf="selected_mf" :user="user_info"></ScriptReq>
|
<ScriptReq :mf="selected_mf" :user="user_info"></ScriptReq>
|
||||||
|
@ -82,6 +87,7 @@ import MedFeedsEdit from './MedFeedsEdit.vue'
|
||||||
import ScriptReq from './MedFeedsScriptReq.vue'
|
import ScriptReq from './MedFeedsScriptReq.vue'
|
||||||
import OrderForm from './MedFeedsOrderForm.vue'
|
import OrderForm from './MedFeedsOrderForm.vue'
|
||||||
import methods from '@/CommonMethods.vue'
|
import methods from '@/CommonMethods.vue'
|
||||||
|
import MedFeedType from '@/types/MedFeedType.vue';
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
site_info: {},
|
site_info: {},
|
||||||
|
@ -114,7 +120,8 @@ export default {
|
||||||
}
|
}
|
||||||
let clist = this.list.filter(q =>
|
let clist = this.list.filter(q =>
|
||||||
q.customer.name.toLowerCase().includes(query) ||
|
q.customer.name.toLowerCase().includes(query) ||
|
||||||
q.customer.acc_no.includes(query)
|
q.customer.acc_no.includes(query) ||
|
||||||
|
q.id == query
|
||||||
)
|
)
|
||||||
return clist
|
return clist
|
||||||
}
|
}
|
||||||
|
@ -124,11 +131,13 @@ export default {
|
||||||
async getMedFeedsList(){
|
async getMedFeedsList(){
|
||||||
this.loading = true
|
this.loading = true
|
||||||
let url = this.$api_url + "/customers/medicated-feeds/list"
|
let url = this.$api_url + "/customers/medicated-feeds/list"
|
||||||
console.log("Getting Medicated Feeds list...")
|
let c_id = this.$route.params.id || ""
|
||||||
|
console.log("Getting Medicated Feeds list..." + c_id)
|
||||||
axios.get(url,{
|
axios.get(url,{
|
||||||
params: {
|
params: {
|
||||||
limit: this.limit,
|
limit: this.limit,
|
||||||
query: this.searchQuery
|
query: this.searchQuery,
|
||||||
|
c_id: c_id
|
||||||
}
|
}
|
||||||
}).then(resp => {
|
}).then(resp => {
|
||||||
this.list = resp.data
|
this.list = resp.data
|
||||||
|
@ -162,9 +171,19 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async editMedFeed(mf) {
|
async editMedFeed(mf) {
|
||||||
|
console.log(mf)
|
||||||
|
if ( mf != undefined ) {
|
||||||
|
this.selected_mf = mf
|
||||||
|
} else {
|
||||||
|
this.selected_mf = new MedFeedType()
|
||||||
|
}
|
||||||
this.edit = true
|
this.edit = true
|
||||||
this.tab = "edit"
|
this.tab = "edit"
|
||||||
this.selected_mf = mf
|
},
|
||||||
|
medfeedUpdated(){
|
||||||
|
this.getMedFeedsList()
|
||||||
|
this.edit = false
|
||||||
|
this.tab = "list"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,9 +194,9 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
height: 138px;
|
height: 100px;
|
||||||
overflow-y:hidden;
|
overflow-y:hidden;
|
||||||
padding: 0 12px;
|
padding: 0 1em;
|
||||||
margin-bottom:2px;
|
margin-bottom:2px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -74,6 +74,7 @@
|
||||||
size is 2 tonnes.</p>
|
size is 2 tonnes.</p>
|
||||||
<p>If you require any further information about the veterinary
|
<p>If you require any further information about the veterinary
|
||||||
medicinal products we stock, please get in touch.</p>
|
medicinal products we stock, please get in touch.</p>
|
||||||
|
<p class="text-underline">Please ensure the prescription is filled out as per the Veterinary Medicines Regulations 2013.</p>
|
||||||
<h4>The customer requires this order on {{ formatDate(mf.date_required,"DD/MM/yyyy") }}</h4>
|
<h4>The customer requires this order on {{ formatDate(mf.date_required,"DD/MM/yyyy") }}</h4>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
34
src/views/salesorders/OrdersList.vue
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<template>
|
||||||
|
<h3>Orders List</h3>
|
||||||
|
<v-row>
|
||||||
|
<v-col cols="8" xs="12" sm="12" md="8">
|
||||||
|
<RecentOrders :customer="customer" limit=20 doc_status="0"></RecentOrders>
|
||||||
|
<br/>
|
||||||
|
<RecentOrders :customer="customer" limit=20 doc_status="2"></RecentOrders>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Customer from '@/types/CustomerType.vue'
|
||||||
|
import RecentOrders from '@/components/RecentOrders.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
site_info: {},
|
||||||
|
user_info: {}
|
||||||
|
},
|
||||||
|
components:{
|
||||||
|
RecentOrders
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
customer: new Customer()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created(){
|
||||||
|
let c_id = this.$route.params.id || 0
|
||||||
|
this.customer.id = c_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
135
src/views/salesorders/\
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
<template>
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>
|
||||||
|
{{ doc_types[doc_status] }} Orders - {{ customer.acc_no }} {{ customer.name }}
|
||||||
|
<v-btn v-if="customer.id != ''" size="smaller" icon="mdi-refresh" variant="text" color="green" @click="getCustomerRecentOrders" :loading="orders_loading"></v-btn>
|
||||||
|
|
||||||
|
</v-card-title>
|
||||||
|
<v-progress-linear color="blue" :active="orders_loading" indeterminate>
|
||||||
|
</v-progress-linear>
|
||||||
|
<v-container>
|
||||||
|
<v-row dense>
|
||||||
|
<v-col cols=12 v-for="item in sortedOrders" :key="item.doc_no">
|
||||||
|
<v-card density="compact" :class="{ 'bg-green-lighten-5' : item.doc_status == 'Live' }">
|
||||||
|
<v-row dense>
|
||||||
|
<v-col>
|
||||||
|
<v-card-title>
|
||||||
|
Order: {{ item.doc_no }}
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-subtitle>
|
||||||
|
{{ formatDate(item.doc_date,"DD/MM/YYYY") }}
|
||||||
|
<v-icon v-if="item.doc_status == 'Completed'" icon="mdi-check"></v-icon>
|
||||||
|
<v-icon v-if="item.doc_status == 'Live'" icon="mdi-play"></v-icon>
|
||||||
|
{{ item.doc_status }}
|
||||||
|
</v-card-subtitle>
|
||||||
|
<v-card-text>
|
||||||
|
{{ item.customer.acc_no }} - {{ item.customer.name }}
|
||||||
|
</v-card-text>
|
||||||
|
</v-col>
|
||||||
|
<v-col>
|
||||||
|
<v-card-text>
|
||||||
|
<!--<h5>Address :</h5>-->
|
||||||
|
<template v-if="item.del_addr.id != 0 && item.del_addr.postal_name != ''">
|
||||||
|
<h5>Delivery Address :</h5>
|
||||||
|
<template v-for="(v, k , index) in item.del_addr" :key="index">
|
||||||
|
<span class="text-caption" v-if="k != 'id' && (v != '' || v != 0)">
|
||||||
|
{{ v }}<br/>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</v-card-text>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols=5>
|
||||||
|
<v-card-text>
|
||||||
|
<h5>Items :</h5>
|
||||||
|
<span class="text-caption" v-for="(i, index) in item.products" :key="index" style="border-bottom: 1px dotted #000;">
|
||||||
|
{{ i.code }} - {{ i.name }}
|
||||||
|
<span v-if="i.quantity != 0">x {{ i.quantity }}</span><br/>
|
||||||
|
</span>
|
||||||
|
</v-card-text>
|
||||||
|
</v-col>
|
||||||
|
<v-col cols=1>
|
||||||
|
<v-btn variant="text" icon="mdi-exclamation" color="red"></v-btn>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
|
||||||
|
</v-card>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import axios from 'axios'
|
||||||
|
import Customer from '@/types/CustomerType.vue'
|
||||||
|
import methods from '@/CommonMethods.vue'
|
||||||
|
export default {
|
||||||
|
props:{
|
||||||
|
customer: new Customer(),
|
||||||
|
doc_status: Number,
|
||||||
|
limit: Number
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
customer() {
|
||||||
|
this.getCustomerRecentOrders()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mixins: [methods],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
orders_loading: false,
|
||||||
|
orders: [],
|
||||||
|
doc_types: ["Live","","Completed"],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
prog_col(){
|
||||||
|
if (this.doc_status == 2){
|
||||||
|
return "green"
|
||||||
|
} else {
|
||||||
|
return "blue"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sortedOrders() {
|
||||||
|
let sorted = this.orders
|
||||||
|
sorted.sort((a, b) => {
|
||||||
|
if (a.doc_date < b.doc_date){
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if (a.doc_date > b.doc_date){
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
return sorted
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getCustomerRecentOrders(){
|
||||||
|
this.orders_loading = true
|
||||||
|
let url = this.$api_url + "/customers/" + this.customer.id + "/orders/recent"
|
||||||
|
axios.get(url, {
|
||||||
|
params: {
|
||||||
|
doc_status: this.doc_status,
|
||||||
|
limit: this.limit || 6
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(resp => {
|
||||||
|
this.orders = resp.data
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.orders_loading = false
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (this.customer.id != "") {
|
||||||
|
this.getCustomerRecentOrders()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
34
todo.md
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
### Basic
|
||||||
|
- [ ] login screen animated logo a la calckey
|
||||||
|
- [ ] log out button under "Hi {USER]" area
|
||||||
|
- [ ] store customer, products, complaints, contracts, etc. in var and repeatedly check for updates
|
||||||
|
|
||||||
|
### Contracts
|
||||||
|
- [x] contracts list
|
||||||
|
- [x] print contract
|
||||||
|
- [x] new contract
|
||||||
|
- [x] edit contract?
|
||||||
|
|
||||||
|
### Complaints
|
||||||
|
- [x] complaints list
|
||||||
|
- [x] view complaint info (comments, 1, 2, 3, at risk, permanent, added by)
|
||||||
|
- [ ] make list recycle box
|
||||||
|
- [ ] add driver to complaint info
|
||||||
|
- [ ] edit complaint text
|
||||||
|
- [ ] add complaint
|
||||||
|
- [ ] edit complaint
|
||||||
|
|
||||||
|
### Medicated Feeds
|
||||||
|
- [x] Medicated Feeds List
|
||||||
|
- [x] more info on list?
|
||||||
|
- [x] Medicated feeds report 1
|
||||||
|
- [x] medicated feeds report 2
|
||||||
|
- [ ] add medicated feeds
|
||||||
|
- [ ] edit medicated feeds
|
||||||
|
- [ ] add/edit medicines
|
||||||
|
|
||||||
|
### Farmers Cheques
|
||||||
|
- [ ] Farmers Cheques
|
||||||
|
|
||||||
|
### Poultry Letters (?)
|
||||||
|
- [ ] Find out whatever these actually are
|