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
|
||||
*.sln
|
||||
*.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",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.10",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
|
@ -9,35 +9,37 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@mdi/font": "5.9.55",
|
||||
"@vuepic/vue-datepicker": "^3.6.4",
|
||||
"axios": "^1.2.2",
|
||||
"core-js": "^3.8.3",
|
||||
"@vuepic/vue-datepicker": "^3.6.8",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.3.4",
|
||||
"core-js": "^3.30.0",
|
||||
"html2pdf.js": "^0.10.1",
|
||||
"moment": "^2.29.4",
|
||||
"roboto-fontface": "*",
|
||||
"sass": "^1.57.1",
|
||||
"sass-loader": "^13.2.0",
|
||||
"roboto-fontface": "^0.10.0",
|
||||
"sass": "^1.60.0",
|
||||
"sass-loader": "^13.2.2",
|
||||
"scss": "^0.2.4",
|
||||
"vue": "^3.2.13",
|
||||
"vue": "^3.2.47",
|
||||
"vue-datepicker": "^1.3.0",
|
||||
"vue-meta": "^2.4.0",
|
||||
"vue-router": "^4.0.3",
|
||||
"vue-virtual-scroller": "^2.0.0-beta.7",
|
||||
"vue3-html2pdf": "^1.1.2",
|
||||
"vue-router": "^4.1.6",
|
||||
"vue-splash": "^1.2.1",
|
||||
"vue-virtual-scroller": "2.0.0-beta.8",
|
||||
"vue3-print-nb": "^0.1.4",
|
||||
"vuetify": "^3.0.0-beta.0",
|
||||
"webfontloader": "^1.0.0"
|
||||
"vuetify": "^3.1.12",
|
||||
"webfontloader": "^1.6.28"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.16",
|
||||
"@babel/eslint-parser": "^7.12.16",
|
||||
"@vue/cli-plugin-babel": "~5.0.0",
|
||||
"@vue/cli-plugin-eslint": "~5.0.0",
|
||||
"@vue/cli-plugin-router": "~5.0.0",
|
||||
"@vue/cli-service": "~5.0.0",
|
||||
"@babel/core": "^7.21.4",
|
||||
"@babel/eslint-parser": "^7.21.3",
|
||||
"@vue/cli-plugin-babel": "~5.0.8",
|
||||
"@vue/cli-plugin-eslint": "~5.0.8",
|
||||
"@vue/cli-plugin-router": "~5.0.8",
|
||||
"@vue/cli-service": "~5.0.8",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-plugin-vue": "^8.0.3",
|
||||
"eslint-plugin-vue": "^8.7.1",
|
||||
"vue-cli-plugin-vuetify": "~2.5.8",
|
||||
"webpack-plugin-vuetify": "^2.0.0-alpha.0"
|
||||
"webpack-plugin-vuetify": "^2.0.1"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"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 http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<link rel="icon" href="<%= BASE_URL %>images/logo_fields.png">
|
||||
<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>
|
||||
<body>
|
||||
<noscript>
|
||||
|
|
34
src/App.vue
|
@ -1,12 +1,40 @@
|
|||
<template>
|
||||
<CMCApp />
|
||||
<CMCApp @ready="isReady" :class="{ fadein : !isLoading }" />
|
||||
<LoadingScreen :isLoading="isLoading" />
|
||||
</template>
|
||||
<script>
|
||||
import CMCApp from './CMCApp.vue'
|
||||
import LoadingScreen from "./components/LoadingScreen.vue"
|
||||
export default {
|
||||
data(){
|
||||
return {
|
||||
isLoading: true
|
||||
}
|
||||
},
|
||||
components: {
|
||||
CMCApp
|
||||
}
|
||||
CMCApp,
|
||||
LoadingScreen
|
||||
},
|
||||
methods: {
|
||||
isReady() {
|
||||
this.isLoading = false
|
||||
}
|
||||
},
|
||||
}
|
||||
</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>
|
||||
<v-app>
|
||||
<MyNav :user="user" :site_info="site_info" />
|
||||
<v-main class="ma-4">
|
||||
<MyNav :user="user" :site_info="site_info" v-if="!isLoading" />
|
||||
<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>
|
||||
</v-main>
|
||||
<v-footer>
|
||||
<sub>{{ site_info.name }} v{{ site_info.version }}</sub>
|
||||
</v-footer>
|
||||
</v-main>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
|
@ -17,13 +19,16 @@ import axios from 'axios'
|
|||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
MyNav,
|
||||
MyNav
|
||||
},
|
||||
emits: ["ready"],
|
||||
data() {
|
||||
return {
|
||||
isLoading: true,
|
||||
site_info: {
|
||||
name: "Loading...",
|
||||
features: {}
|
||||
name: "",
|
||||
features: {},
|
||||
backend_connected: false
|
||||
},
|
||||
user: {
|
||||
first_name: "",
|
||||
|
@ -44,6 +49,14 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
checkIfReady(){
|
||||
if (this.site_info.name != "") {
|
||||
setTimeout(() => {
|
||||
this.isLoading = false
|
||||
this.$emit("ready")
|
||||
},1000)
|
||||
}
|
||||
},
|
||||
checkLoginStatus() {
|
||||
let url = this.$api_url + "/users/check_login"
|
||||
console.log("Checking login status...")
|
||||
|
@ -70,10 +83,24 @@ export default {
|
|||
|
||||
},
|
||||
async getSiteInfo() {
|
||||
console.log("Trying to get site Info...")
|
||||
axios
|
||||
.get(this.$api_url + "/info")
|
||||
.then(response => {this.site_info = response.data})
|
||||
.catch(error => (console.log(error)))
|
||||
.then(response => {
|
||||
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() {
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
<script>
|
||||
import moment from 'moment'
|
||||
import axios from 'axios'
|
||||
import Error from '@/types/ErrorType.vue'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
customer_list: [],
|
||||
errors: {}
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
formatDate(d,f) {
|
||||
return moment(String(d)).format(f)
|
||||
|
@ -26,6 +34,35 @@ export default {
|
|||
minute: '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>
|
||||
|
|
|
@ -66,12 +66,12 @@ table tr.at_risk:hover {
|
|||
background:lightgrey;
|
||||
color: grey;
|
||||
}
|
||||
.scroller {
|
||||
border: 1px dotted #333;
|
||||
}
|
||||
.scroller {
|
||||
height: 600px;
|
||||
}
|
||||
.scroller.small {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.item {
|
||||
height: 138px;
|
||||
|
@ -82,3 +82,6 @@ table tr.at_risk:hover {
|
|||
align-items: center;
|
||||
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-toolbar-title>{{ site_info.name }}</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn variant="text" v-if="user.logged_in">Hi {{ user.first_name }}</v-btn>
|
||||
<v-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-navigation-drawer
|
||||
v-if="user.logged_in"
|
||||
|
@ -28,11 +46,22 @@
|
|||
</v-list-item>
|
||||
</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-navigation-drawer>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import { version } from "@/../package.json"
|
||||
export default{
|
||||
name: "MyNav",
|
||||
props: {
|
||||
|
@ -49,12 +78,19 @@ export default{
|
|||
} else {
|
||||
this.items = []
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
user_items() {
|
||||
return this.items.filter(x => x.isUserMgmt == true)
|
||||
}
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
appVersion: version,
|
||||
items: [],
|
||||
drawer: null
|
||||
drawer: null,
|
||||
drawer2: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
@ -65,7 +101,6 @@ export default{
|
|||
methods:{
|
||||
get_menu() {
|
||||
let items = []
|
||||
items.push({title: "Dashboard", value:"/about"})
|
||||
items.push({title: "Customers",
|
||||
value:"/customers",
|
||||
children:[{title:"List",
|
||||
|
@ -84,7 +119,7 @@ export default{
|
|||
value:"/sop/printed"}]
|
||||
})
|
||||
|
||||
items.push({title: "Logout", value:"/logout"})
|
||||
items.push({title: "Logout", value:"/logout", isUserMgmt: true})
|
||||
return items
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
<template>
|
||||
<v-container class="button-container">
|
||||
<v-btn color="primary" @click="generatePdf()" class="mr-2">Create PDF</v-btn>
|
||||
<v-btn color="grey" v-print="printObj">Print</v-btn>
|
||||
<v-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-container>
|
||||
</template>
|
||||
<script>
|
||||
import html2pdf from 'html2pdf.js';
|
||||
export default {
|
||||
props: {
|
||||
scope: String
|
||||
scope: String,
|
||||
filename: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
generating: false,
|
||||
printObj: {
|
||||
id: this.scope,
|
||||
previewTitle: "Report Print",
|
||||
|
@ -20,8 +22,16 @@ export default {
|
|||
}
|
||||
},
|
||||
methods:{
|
||||
async generatePdf() {
|
||||
html2pdf(document.getElementById(this.scope))
|
||||
generatePdf() {
|
||||
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>
|
||||
<PrintButtons :scope="scope" />
|
||||
<PrintButtons :scope="scope" :filename="filename" />
|
||||
<v-container>
|
||||
<v-card class="a4 page">
|
||||
<div :id="scope" class="pdf-scope">
|
||||
<div :id="scope" class="pdf-scope" >
|
||||
<slot></slot>
|
||||
</div>
|
||||
</v-card>
|
||||
|
@ -10,9 +10,11 @@
|
|||
</template>
|
||||
<script>
|
||||
import PrintButtons from './PrintButtons.vue'
|
||||
import '@/assets/css/reports.css';
|
||||
export default {
|
||||
props: {
|
||||
scope: String
|
||||
scope: String,
|
||||
filename: String
|
||||
},
|
||||
components: {
|
||||
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 axios from 'axios'
|
||||
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')}`;
|
||||
|
||||
|
@ -19,6 +21,8 @@ const app = createApp(App).use(router)
|
|||
.use(vuetify)
|
||||
.use(print)
|
||||
.use(VueVirtualScroller)
|
||||
.use(DebugPanel)
|
||||
.use(ErrorBanner)
|
||||
.component('DatePicker', Datepicker)
|
||||
|
||||
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 HomeView from '../views/HomeView.vue'
|
||||
const AboutView = () => import('../views/AboutView.vue')
|
||||
const LoginPage = () => import('../views/LoginPage.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 ComplaintsList = () => import('../views/complaints/ComplaintsList.vue')
|
||||
const MedFeedsList = () => import('../views/medfeeds/MedFeedsList.vue')
|
||||
const OrderList = () => import('../views/salesorders/OrdersList.vue')
|
||||
const SOPPrintedList = () => import('../views/salesorders/SOPPrinted.vue')
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: HomeView
|
||||
component: CustomerList
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
|
@ -40,16 +40,41 @@ const routes = [
|
|||
name: 'contractlist',
|
||||
component: ContractList
|
||||
},
|
||||
{
|
||||
path: '/customers/contracts/list/:id',
|
||||
name: 'contractlistid',
|
||||
component: ContractList
|
||||
},
|
||||
{
|
||||
path: '/customers/medicated-feeds/list',
|
||||
name: 'medfeedslist',
|
||||
component: MedFeedsList
|
||||
},
|
||||
{
|
||||
path: '/customers/medicated-feeds/list/:id',
|
||||
name: 'medfeedslistid',
|
||||
component: MedFeedsList
|
||||
},
|
||||
{
|
||||
path: '/customers/complaints/list',
|
||||
name: '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',
|
||||
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>
|
||||
<v-card width="500" height="300"
|
||||
<v-card width="500" min-height="350"
|
||||
class="mx-auto my-12"
|
||||
id="login-box"
|
||||
title="Welcome!"
|
||||
subtitle="Please Log In">
|
||||
<v-form>
|
||||
<v-form :disabled="!my_site_info.backend_connected">
|
||||
<v-responsive class="mx-auto" max-width="475">
|
||||
<v-text-field label="Email" clearable v-model="user.email"></v-text-field>
|
||||
<v-text-field label="Password" v-model="user.password" clearable
|
||||
type="password"></v-text-field>
|
||||
<v-btn color="blue" @click="submitLogin">Login</v-btn>
|
||||
<v-text-field label="Username"
|
||||
@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>
|
||||
<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-form>
|
||||
</v-card>
|
||||
|
@ -18,18 +27,35 @@
|
|||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
site_info: {}
|
||||
},
|
||||
name: 'LoginPage',
|
||||
data() {
|
||||
return {
|
||||
my_site_info: this.site_info,
|
||||
user: {
|
||||
email: "",
|
||||
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: {
|
||||
submitLogin() {
|
||||
this.error_message = ""
|
||||
this.show_error = false
|
||||
let url = this.$api_url + "/users/login"
|
||||
this.logging_in = true
|
||||
console.log("Logging in...")
|
||||
axios
|
||||
.post(url, {
|
||||
|
@ -38,6 +64,7 @@ export default {
|
|||
})
|
||||
.then(resp => {
|
||||
let data = resp.data
|
||||
console.log(data)
|
||||
if (data.logged_in) {
|
||||
let token = data.token
|
||||
localStorage.setItem('access_token', token.content)
|
||||
|
@ -47,10 +74,34 @@ export default {
|
|||
console.log("Logged in")
|
||||
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>
|
||||
<h3>Complaints</h3>
|
||||
<v-tabs v-model="tab" fixed-tabs>
|
||||
<v-tab title="List" v-model="list" />
|
||||
<v-tab title="Edit" v-model="edit" v-if="edit" />
|
||||
<v-tab title="Report" v-model="report" v-if="report" />
|
||||
<v-tabs v-model="tab" >
|
||||
<v-tab title="List" value="isList"></v-tab>
|
||||
<v-tab title="Edit" value="edit" v-if="edit"></v-tab>
|
||||
<v-tab title="Report" value="report" v-if="report" ></v-tab>
|
||||
</v-tabs>
|
||||
<v-window v-model="tab">
|
||||
<v-window-item v-model="list">
|
||||
<v-responsive
|
||||
max-width="500"
|
||||
>
|
||||
<v-text-field
|
||||
clearable
|
||||
label="Search"
|
||||
variant="outlined"
|
||||
v-model="searchQuery"
|
||||
density="compact"
|
||||
append-inner-icon="mdi-magnify"></v-text-field>
|
||||
<v-switch color="blue" label="Show Only Active" v-model="showActive"></v-switch>
|
||||
<v-btn v-if="site_info.features.addcomplaint" color="warning">+ Add</v-btn>
|
||||
</v-responsive>
|
||||
<v-table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Comp No</th>
|
||||
<th>Date</th>
|
||||
<th>Order</th>
|
||||
<th>Acc No</th>
|
||||
<th>Name</th>
|
||||
<th>Reason</th>
|
||||
<th>Active</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="loading">
|
||||
<td colspan=7>
|
||||
<img class="loading" src="/images/icons/loading.gif"/>
|
||||
</td>
|
||||
</tr>
|
||||
<template v-for="complaint in filteredComplaints" :key="complaint.id">
|
||||
<tr :class="{ at_risk : complaint.at_risk, cust_at_risk: complaint.customer.at_risk }">
|
||||
<td>{{ complaint.id }}</td>
|
||||
<td>{{ complaint.complaint_date }}</td>
|
||||
<td>{{ complaint.sop.doc_no }}</td>
|
||||
<td>{{ complaint.customer.acc_no }}</td>
|
||||
<td>{{ complaint.customer.name }}
|
||||
</td>
|
||||
<td>{{ complaint.reason }}</td>
|
||||
<td>
|
||||
<img class="icon" v-if="complaint.active" v-bind:alt="complaint.active" v-bind:title="complaint.active" src="/images/icons/Live.png"/>
|
||||
</td>
|
||||
<td>
|
||||
<v-btn @click="showInfo(complaint)">
|
||||
<span v-if="complaint.info_shown">
|
||||
Hide
|
||||
</span>
|
||||
<span v-else>
|
||||
View
|
||||
</span>
|
||||
</v-btn>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="complaint.info_shown">
|
||||
<template v-if="complaint.info">
|
||||
<template v-if="complaint.info_loaded">
|
||||
<td colspan=4>
|
||||
{{ complaint.info.comments }}
|
||||
<br />
|
||||
<sub>- {{ complaint.info.added_by }}</sub>
|
||||
</td>
|
||||
<td colspan=2>
|
||||
<v-container>
|
||||
<v-form :disabled="complaint.info.loading">
|
||||
<v-row dense nogutters>
|
||||
<v-checkbox label="1" type="checkbox" v-model="complaint.info.one" @change="changeComplaintCheckbox(complaint)" />
|
||||
<v-checkbox label="2" v-model="complaint.info.two" @change="changeComplaintCheckbox(complaint)"/>
|
||||
<v-checkbox label="3" v-model="complaint.info.three" @change="changeComplaintCheckbox(complaint)"/>
|
||||
</v-row>
|
||||
<v-row dense nogutters>
|
||||
<v-checkbox label="At Risk" v-model="complaint.at_risk" @change="changeComplaintCheckbox(complaint)" />
|
||||
<v-checkbox label="Permanent" v-model="complaint.info.permanent" @change="changeComplaintCheckbox(complaint)" />
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-container>
|
||||
</td>
|
||||
<td>
|
||||
<img v-if="complaint.info.loading" class="loading" src="/images/icons/loading.gif"/>
|
||||
<template v-else>
|
||||
<v-btn v-if="site_info.features.editcomplaint" color="warning">Edit</v-btn>
|
||||
</template>
|
||||
</td>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<td colspan=6>
|
||||
<img class="loading" src="/images/icons/loading.gif"/>
|
||||
</td>
|
||||
</template>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</v-table>
|
||||
<v-window-item value="isList">
|
||||
<v-col cols="12" xs="12" sm="12" md="12" lg=8>
|
||||
<v-text-field label="Search"
|
||||
variant="outlined"
|
||||
v-model="searchQuery"
|
||||
density="compact"
|
||||
append-inner-icon="mdi-magnify"></v-text-field>
|
||||
<v-row justify="space-between">
|
||||
<v-col cols=4>
|
||||
<v-btn v-if="site_info.features.addcomplaint" color="warning" prepend-icon="mdi-plus" variant="text" @click="showAddComplaint">Add</v-btn>
|
||||
</v-col>
|
||||
<v-col cols=3>
|
||||
<v-btn align="end" variant="text" @click="showActive = !showActive">
|
||||
Showing <span v-if="showActive">Active Only</span><span v-else>Inactive</span>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-progress-linear indeterminate color="orange" :active="loading"></v-progress-linear>
|
||||
<v-card variant="outlined">
|
||||
<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-btn @click="showInfo(item)" color="blue-lighten-1">View</v-btn>
|
||||
<v-btn v-if="site_info.features.editcomplaint" color="orange-lighten-2" @click="showEditComplaint(item)">Edit</v-btn>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</RecycleScroller>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-window-item>
|
||||
<v-window-item value="edit">
|
||||
<ComplaintEdit ref="edit" :setcomplaint="selected_complaint" @closetab="tab = 'isList'" @complaintupdate="complaintUpdated"></ComplaintEdit>
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
<v-dialog v-model="showComplaintInfo">
|
||||
<ComplaintInfo :in_complaint="selected_complaint" @return="doInfoReturn" />
|
||||
</v-dialog>
|
||||
</template>
|
||||
<script>
|
||||
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 {
|
||||
props: {
|
||||
site_info:{},
|
||||
user_info:{}
|
||||
},
|
||||
components: {
|
||||
ComplaintInfo,
|
||||
ComplaintEdit
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tab: "list",
|
||||
tab: "isList",
|
||||
list: [],
|
||||
listreceived: false,
|
||||
showActive: true,
|
||||
|
@ -121,7 +90,9 @@ export default {
|
|||
limit: 300,
|
||||
searchQuery: "",
|
||||
edit: false,
|
||||
report: false
|
||||
report: false,
|
||||
showComplaintInfo: false,
|
||||
selected_complaint: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -134,7 +105,7 @@ export default {
|
|||
q.customer.name.toLowerCase().includes(query) ||
|
||||
q.customer.acc_no.includes(query) ||
|
||||
q.id == query ||
|
||||
q.reason.toLowerCase().includes(query)
|
||||
q.reason.reason.toLowerCase().includes(query)
|
||||
)
|
||||
if (this.showActive) {
|
||||
clist = clist.filter(q =>
|
||||
|
@ -145,64 +116,65 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
async getComplaintsList(){
|
||||
getComplaintsList(){
|
||||
this.loading = true
|
||||
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,{
|
||||
params: {
|
||||
limit: this.limit,
|
||||
query: this.searchQuery
|
||||
query: this.searchQuery,
|
||||
c_id: c_id
|
||||
}
|
||||
}).then(resp => {
|
||||
this.list = resp.data
|
||||
this.loading = false
|
||||
this.listreceived = true
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
async getComplaintInfo(complaint) {
|
||||
complaint.info.loading = true
|
||||
let url = this.$api_url + "/customers/complaints/" + complaint.id + "/info"
|
||||
console.log("Getting Complaint Info...")
|
||||
axios.get(url)
|
||||
.then(resp =>{
|
||||
complaint.info = resp.data
|
||||
complaint.info.loading = false
|
||||
})
|
||||
},
|
||||
async showInfo(complaint) {
|
||||
console.log(complaint.id)
|
||||
showInfo(complaint) {
|
||||
this.showComplaintInfo = true
|
||||
this.selected_complaint = complaint
|
||||
complaint.info_loaded = false
|
||||
complaint.info_shown = !complaint.info_shown
|
||||
if (complaint.info_shown) {
|
||||
complaint.info = await this.getComplaintInfo(complaint)
|
||||
complaint.info_loaded = true
|
||||
}
|
||||
},
|
||||
async changeComplaintCheckbox(complaint) {
|
||||
let set = await this.setComplaintStatus(complaint)
|
||||
if (complaint.at_risk && set && complaint.info.one && complaint.info.two && complaint.info.three) {
|
||||
console.log("Contract no longer at risk")
|
||||
complaint.at_risk = false
|
||||
}
|
||||
doInfoReturn(code) {
|
||||
console.log(code)
|
||||
},
|
||||
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
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
</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,16 +1,17 @@
|
|||
<template>
|
||||
<v-card title="Edit Contract" :subtitle="'Contract : ' + contract.no">
|
||||
<v-card :title="title" :subtitle="'Contract : ' + contract.no">
|
||||
<v-card-text>
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col cols="6">
|
||||
<v-text-field label="Customer" v-model="contract.customer.name" readonly></v-text-field>
|
||||
<v-text-field type="number" label="Tonnage Per Month" v-model="contract.tonnage_per_month"></v-text-field>
|
||||
<v-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>
|
||||
<v-text-field type="number" variant="outlined" label="Tonnage Per Month" v-model="contract.tonnage_per_month"></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="6">
|
||||
<label>
|
||||
Start Date
|
||||
<DatePicker v-model="contract.start_date" format="dd/MM/yyyy" />
|
||||
<DatePicker v-model="contract.start_date" format="dd/MM/yyyy" />
|
||||
</label><br />
|
||||
<label>
|
||||
Finish Date
|
||||
|
@ -18,33 +19,36 @@
|
|||
</label><br />
|
||||
</v-col>
|
||||
</v-row>
|
||||
<template v-for="p in contract.products" :key="p.code">
|
||||
<v-row>
|
||||
<v-col cols="6">
|
||||
<v-autocomplete v-model="p.code"
|
||||
v-model:search="product_search"
|
||||
:loading="products_loading"
|
||||
:items="products"
|
||||
cache-items
|
||||
hide-no-data
|
||||
hide-details
|
||||
solo-inverted
|
||||
label="Code"
|
||||
no-data-text="No Products Found"
|
||||
item-title="code"
|
||||
item-value="code" ></v-autocomplete>
|
||||
</v-col>
|
||||
<v-col cols="4">
|
||||
<v-text-field type="number" label="Price" v-model="p.price"></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="2">
|
||||
<v-btn size="small" color="error" title="Remove" variant="plain" icon @click="contract.products.pop()"><v-icon>mdi-minus</v-icon></v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<v-table density="compact">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:65%;">
|
||||
Product
|
||||
</th>
|
||||
<th style="width:20%">
|
||||
Price
|
||||
</th>
|
||||
<th>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(p, index) in contract.products" :key="index">
|
||||
<td>
|
||||
<v-text-field readonly variant="outlined" prepend-inner-icon="mdi-magnify" @click="showProductSearch(index)" :model-value="p.code + ' - ' + p.name"></v-text-field>
|
||||
</td>
|
||||
<td>
|
||||
<v-text-field prepend-icon="mdi-currency-gbp" type="number" density="compact" variant="outlined" v-model="p.price"></v-text-field>
|
||||
</td>
|
||||
<td>
|
||||
<v-btn size="small" color="error" title="Remove" variant="plain" @click="removeProduct(p.code)" icon="mdi-minus"></v-btn>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
<v-row>
|
||||
<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-row>
|
||||
<v-row>
|
||||
|
@ -55,107 +59,145 @@
|
|||
</label>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-textarea rows=3 label="Comments" v-model="contract.comments"></v-textarea>
|
||||
<v-textarea rows=3 label="Office Comments" v-model="contract.office_comments"></v-textarea>
|
||||
<v-textarea rows=3 label="Comments" variant="outlined" v-model="contract.comments"></v-textarea>
|
||||
<v-textarea rows=3 label="Office Comments" variant="outlined" v-model="contract.office_comments"></v-textarea>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
<DebugPanel :data="contract"></DebugPanel>
|
||||
<ErrorBanner :errors="errors" />
|
||||
<v-card-actions>
|
||||
<v-btn color="red-darken-1"
|
||||
variant="text"
|
||||
@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)">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-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]">
|
||||
<ProductSearch @returnProduct="setProduct"></ProductSearch>
|
||||
</v-dialog>
|
||||
</template>
|
||||
<script>
|
||||
import axios from 'axios'
|
||||
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 {
|
||||
props: {
|
||||
setcontract: {
|
||||
no: Number,
|
||||
customer: {
|
||||
acc_no: String,
|
||||
name: String,
|
||||
at_risk: Boolean,
|
||||
},
|
||||
products: [{
|
||||
code: String,
|
||||
name: String,
|
||||
price: Number,
|
||||
}],
|
||||
start_date: String,
|
||||
finish_date: String,
|
||||
agree_date: String,
|
||||
tonnage_per_month: Number,
|
||||
comments: String,
|
||||
office_comments: String,
|
||||
active: Boolean
|
||||
}
|
||||
setcontract: {}
|
||||
},
|
||||
components: {
|
||||
DatePicker
|
||||
DatePicker,
|
||||
ErrorBanner,
|
||||
ProductSearch,
|
||||
CustomerSearch
|
||||
},
|
||||
watch: {
|
||||
setcontract(newval) {
|
||||
this.contract = newval
|
||||
},
|
||||
product_search(val) {
|
||||
if (val && val.length > 1) {
|
||||
this.searchProducts(val)
|
||||
}
|
||||
}
|
||||
},
|
||||
mixins: [methods],
|
||||
data() {
|
||||
return {
|
||||
contract: this.setcontract,
|
||||
dialog: this.opendialog,
|
||||
saving: false,
|
||||
search: [],
|
||||
searchProdIndex: null,
|
||||
product_search: null,
|
||||
products_loading: false,
|
||||
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: {
|
||||
close() {
|
||||
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(){
|
||||
this.errors = []
|
||||
this.saving = true
|
||||
let url = this.$api_url + "/customers/contracts/" + this.contract.no + "/save"
|
||||
console.log("Saving Contract : ", this.contract.no)
|
||||
console.log(this.contract)
|
||||
if (this.contract.isNew) {
|
||||
url = this.$api_url + "/customers/contracts/add"
|
||||
}
|
||||
axios.post(url, {
|
||||
contract: this.contract
|
||||
}).then(resp => {
|
||||
console.log("Saved Contract : " + JSON.stringify(resp.data))
|
||||
this.saving = false
|
||||
this.$emit('contractupdate', resp.data)
|
||||
let stat = resp.data
|
||||
if (stat.status == true ) {
|
||||
if (this.contract.isNew) {
|
||||
this.$emit('contractupdate', resp.data)
|
||||
} else {
|
||||
this.$emit('contractupdate', resp.data)
|
||||
}
|
||||
} else {
|
||||
this.errors.push("Contract not saved.")
|
||||
console.log("Not Saved")
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
this.saving = false
|
||||
})
|
||||
},
|
||||
searchProducts(code) {
|
||||
let url = this.$api_url + "/products/search/" + code
|
||||
console.log(url)
|
||||
axios.get(url)
|
||||
.then(resp => {
|
||||
console.log(resp)
|
||||
this.products = resp.data
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
this.products = [{code:"NoProductsFound", name:"No Products Found"}]
|
||||
})
|
||||
},
|
||||
productCodeName(p) {
|
||||
return p.code + ' - ' + p.name
|
||||
},
|
||||
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,114 +7,114 @@
|
|||
</v-tabs>
|
||||
<v-window v-model="tab" >
|
||||
<v-window-item value="list">
|
||||
<v-responsive
|
||||
max-width="500"
|
||||
>
|
||||
<v-row>
|
||||
<v-col cols="8" xs="12" sm="12" md="12" lg="8">
|
||||
<v-text-field clearable
|
||||
label="Search"
|
||||
variant="outlined"
|
||||
v-model="searchQuery"
|
||||
density="compact"
|
||||
append-inner-icon="mdi-magnify"></v-text-field>
|
||||
</v-responsive>
|
||||
<v-dialog v-model="showDialog">
|
||||
</v-dialog>
|
||||
<v-row>
|
||||
<v-col cols="8" xs="12" sm="12" md="8">
|
||||
<v-progress-linear indeterminate color="blue" :active="loading"></v-progress-linear>
|
||||
<RecycleScroller class="scroller"
|
||||
:items="filteredContracts"
|
||||
:item-size="130"
|
||||
v-slot="{ item }"
|
||||
key-field="no"
|
||||
>
|
||||
<v-row :class="[{inactive : !item.active}]" class="item">
|
||||
<v-col cols="4" >
|
||||
<h3>Contract : {{ item.no }}</h3>
|
||||
{{ item.customer.acc_no }} - {{ item.customer.name }}
|
||||
<v-switch v-model="item.active" color="green" @change="setContractInactive(item)" label="Active"></v-switch>
|
||||
</v-col>
|
||||
<v-col >
|
||||
<template v-for="p in item.products" :key="p.code">
|
||||
<span v-if="p.code != ''">
|
||||
{{ p.code }} @ {{ p.price }}<br/>
|
||||
</span>
|
||||
</template>
|
||||
</v-col>
|
||||
<v-col >
|
||||
<v-icon title="Start">mdi-play</v-icon>: {{ formatDate(item.start_date,"DD/MM/YYYY") }}<br/>
|
||||
<v-icon title="Finish">mdi-flag-checkered</v-icon>: {{ formatDate(item.finish_date,"DD/MM/YYYY") }}<br/>
|
||||
<v-icon title="Duration">mdi-clock</v-icon>: {{ item.duration }} months
|
||||
</v-col>
|
||||
<v-col>
|
||||
{{ item.tonnage_per_month }} per month<br/>
|
||||
= {{ formatNumber(item.tonnage_per_month * item.duration,2) }} total<br/>
|
||||
= {{ formatNumber(item.tonnage_per_month * item.remaining_duration,2) }} remaining<br/>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-btn color="info">
|
||||
More
|
||||
<v-overlay activator="parent" class="align-center justify-center">
|
||||
<v-card title="Info" width="600">
|
||||
<v-card-subtitle>Comments</v-card-subtitle>
|
||||
<v-card-text>{{ item.comments}} </v-card-text>
|
||||
<v-card-subtitle>Office</v-card-subtitle>
|
||||
<v-card-text>{{ item.office_comments}}</v-card-text>
|
||||
<v-card-subtitle>Agreed</v-card-subtitle>
|
||||
<v-card-text>{{ formatDate(item.agree_date,"DD/MM/YYYY") }}</v-card-text>
|
||||
<v-card-subtitle>Products</v-card-subtitle>
|
||||
<v-card-text>
|
||||
<template v-for="p in item.products" :key="p.code">
|
||||
<span v-if="p.code != ''">
|
||||
{{ p.code }} - {{ p.name }} @ {{ p.price }}<br/>
|
||||
</span>
|
||||
</template>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn color="info"
|
||||
target="blank"
|
||||
@click="getContractPrint(item.no)"
|
||||
class="ma-2 pa-2"
|
||||
:loading="item.multiloading"
|
||||
>
|
||||
Multi
|
||||
</v-btn>
|
||||
|
||||
<v-btn color="warning"
|
||||
v-if="site_info.features.addcontract"
|
||||
@click="showEditContract()"
|
||||
prepend-icon="mdi-plus"
|
||||
variant="text"
|
||||
>Add</v-btn>
|
||||
<v-progress-linear indeterminate color="blue" :active="loading"></v-progress-linear>
|
||||
<v-card variant="outlined">
|
||||
<RecycleScroller class="scroller"
|
||||
:items="filteredContracts"
|
||||
:item-size="130"
|
||||
v-slot="{ item }"
|
||||
key-field="no"
|
||||
>
|
||||
<v-row :class="[{inactive : !item.active}]" class="item">
|
||||
<v-col cols="4" >
|
||||
<h3>Contract : {{ item.no }}</h3>
|
||||
{{ item.customer.acc_no }} - {{ item.customer.name }}
|
||||
<v-switch v-model="item.active" color="green" @change="setContractInactive(item)" label="Active"></v-switch>
|
||||
</v-col>
|
||||
<v-col >
|
||||
<template v-for="p in item.products" :key="p.code">
|
||||
<span v-if="p.code != ''">
|
||||
{{ p.code }} @ {{ p.price }}<br/>
|
||||
</span>
|
||||
</template>
|
||||
</v-col>
|
||||
<v-col >
|
||||
<v-icon title="Start">mdi-play</v-icon>: {{ formatDate(item.start_date,"DD/MM/YYYY") }}<br/>
|
||||
<v-icon title="Finish">mdi-flag-checkered</v-icon>: {{ formatDate(item.finish_date,"DD/MM/YYYY") }}<br/>
|
||||
<v-icon title="Duration">mdi-clock</v-icon>: {{ item.duration }} months
|
||||
</v-col>
|
||||
<v-col>
|
||||
{{ item.tonnage_per_month }} per month<br/>
|
||||
= {{ formatNumber(item.tonnage_per_month * item.duration,2) }} total<br/>
|
||||
= {{ formatNumber(item.tonnage_per_month * item.remaining_duration,2) }} remaining<br/>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<div class="d-flex justify-space-around align-center flex-column flex-sm-row fill-height">
|
||||
<v-btn color="info">
|
||||
More
|
||||
<v-overlay activator="parent" class="align-center justify-center">
|
||||
<v-card title="Info" width="600">
|
||||
<v-card-subtitle>Comments</v-card-subtitle>
|
||||
<v-card-text>{{ item.comments}} </v-card-text>
|
||||
<v-card-subtitle>Office</v-card-subtitle>
|
||||
<v-card-text>{{ item.office_comments}}</v-card-text>
|
||||
<v-card-subtitle>Agreed</v-card-subtitle>
|
||||
<v-card-text>{{ formatDate(item.agree_date,"DD/MM/YYYY") }}</v-card-text>
|
||||
<v-card-subtitle>Products</v-card-subtitle>
|
||||
<v-card-text>
|
||||
<template v-for="p in item.products" :key="p.code">
|
||||
<span v-if="p.code != ''">
|
||||
{{ p.code }} - {{ p.name }} @ {{ p.price }}<br/>
|
||||
</span>
|
||||
</template>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn color="info"
|
||||
target="blank"
|
||||
@click="getContractPrint(item.no,true)"
|
||||
:loading="item.totalloading"
|
||||
@click="getContractPrint(item.no)"
|
||||
class="ma-2 pa-2"
|
||||
:loading="multiloading"
|
||||
>
|
||||
Total
|
||||
Multi
|
||||
</v-btn>
|
||||
<v-btn color="warning"
|
||||
v-if="site_info.features.editcontract"
|
||||
@click="showEditContract(item)"
|
||||
|
||||
<v-btn color="info"
|
||||
target="blank"
|
||||
@click="getContractPrint(item.no,true)"
|
||||
:loading="totalloading"
|
||||
class="ma-2 pa-2"
|
||||
>
|
||||
Edit
|
||||
Total
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-overlay>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-btn color="warning"
|
||||
v-if="site_info.features.editcontract"
|
||||
@click="showEditContract(item)"
|
||||
>
|
||||
Edit
|
||||
</v-btn>
|
||||
<v-btn color="warning"
|
||||
v-if="site_info.features.editcontract"
|
||||
@click="showEditContract(item)"
|
||||
:loading="editloading"
|
||||
>
|
||||
Edit
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-overlay>
|
||||
</v-btn>
|
||||
<v-btn color="warning"
|
||||
v-if="site_info.features.editcontract"
|
||||
@click="showEditContract(item)"
|
||||
:loading="editloading"
|
||||
>
|
||||
Edit
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</RecycleScroller>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</RecycleScroller>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-window-item>
|
||||
<v-window-item value="edit">
|
||||
<ContractEdit ref="edit" :setcontract="selected_contract" @closetab="tab = 'list'" @contractupdate="contractUpdated"/>
|
||||
|
@ -123,11 +123,14 @@
|
|||
<ContractMulti :contract="selected_contract" :total="total" />
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
<v-dialog v-model="showDialog">
|
||||
</v-dialog>
|
||||
</template>
|
||||
<script>
|
||||
import ContractEdit from './ContractEdit.vue';
|
||||
import ContractMulti from './ContractMulti.vue';
|
||||
import methods from '@/CommonMethods.vue'
|
||||
import ContractType from '@/types/ContractType.vue';
|
||||
import axios from 'axios'
|
||||
export default {
|
||||
props: {
|
||||
|
@ -179,25 +182,37 @@ export default {
|
|||
edit: false,
|
||||
report: false,
|
||||
total: false,
|
||||
content: ""
|
||||
content: "",
|
||||
editloading: false,
|
||||
multiloading: false,
|
||||
totalloading: false
|
||||
}
|
||||
},
|
||||
mixins: [methods],
|
||||
methods: {
|
||||
async showEditContract(contract) {
|
||||
this.selected_contract = contract
|
||||
//this.selected_contract = contract
|
||||
this.tab = "edit"
|
||||
this.edit = true
|
||||
this.editloading = true
|
||||
setTimeout(() => {
|
||||
if ( contract != undefined ) {
|
||||
this.selected_contract = contract
|
||||
} else {
|
||||
this.selected_contract = new ContractType()
|
||||
}
|
||||
this.tab = "edit"
|
||||
this.edit = true
|
||||
this.editloading = false
|
||||
},1000)
|
||||
},
|
||||
async getContractsList() {
|
||||
this.loading = true
|
||||
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, {
|
||||
params: {
|
||||
limit: this.limit,
|
||||
query: this.searchQuery
|
||||
query: this.searchQuery,
|
||||
c_id: c_id
|
||||
}
|
||||
})
|
||||
.then(resp => {
|
||||
|
@ -207,12 +222,16 @@ export default {
|
|||
})
|
||||
},
|
||||
getContractPrint(contract, total = false) {
|
||||
if (total){ this.totalloading = true } else { this.multiloading = true }
|
||||
axios.get(this.$api_url + "/customers/contracts/" + contract + "/info")
|
||||
.then(resp => {
|
||||
this.selected_contract = resp.data
|
||||
this.total = total
|
||||
this.tab = "report"
|
||||
this.report = true
|
||||
}).finally(() => {
|
||||
this.totalloading = false
|
||||
this.multiloading = false
|
||||
})
|
||||
},
|
||||
findContract(id) {
|
||||
|
@ -249,7 +268,7 @@ export default {
|
|||
</script>
|
||||
<style scoped>
|
||||
.scroller {
|
||||
height: 600px;
|
||||
height: 70vw;
|
||||
}
|
||||
|
||||
.item {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<ReportLayout scope="contract">
|
||||
<ReportLayout scope="contract" :filename="filename">
|
||||
<div class="letter">
|
||||
<p><span class="text-bold">Date: </span>{{ current_date }}</p>
|
||||
<p class="text-bold">Customer's Address:</p>
|
||||
|
@ -57,7 +57,6 @@
|
|||
</template>
|
||||
<script>
|
||||
|
||||
import '@/assets/css/reports.css';
|
||||
import ReportLayout from '@/components/ReportLayout.vue'
|
||||
import Common from '@/common.js';
|
||||
import moment from 'moment';
|
||||
|
@ -72,6 +71,7 @@ export default {
|
|||
},
|
||||
data(){
|
||||
return {
|
||||
filename: "Contract_" + this.contract.no,
|
||||
current_date: Common.getDateNow(),
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,33 +1,76 @@
|
|||
<template>
|
||||
<h3>Customer List</h3>
|
||||
<v-responsive
|
||||
max-width="500"
|
||||
>
|
||||
<v-text-field
|
||||
clearable
|
||||
label="Search"
|
||||
variant="outlined"
|
||||
v-model="searchQuery"
|
||||
density="compact"
|
||||
append-inner-icon="mdi-magnify"></v-text-field>
|
||||
</v-responsive>
|
||||
<RecycleScroller
|
||||
class="scroller"
|
||||
:items="filteredCustomers"
|
||||
:item-size="50"
|
||||
key-field="acc_no"
|
||||
v-slot="{ item }"
|
||||
>
|
||||
<v-row class="customer">
|
||||
<v-col cols="6">
|
||||
{{ item.acc_no }} - {{ item.name }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
<hr />
|
||||
</RecycleScroller>
|
||||
<v-text-field
|
||||
label="Search"
|
||||
variant="outlined"
|
||||
v-model="searchQuery"
|
||||
density="compact"
|
||||
append-inner-icon="mdi-magnify"></v-text-field>
|
||||
<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
|
||||
class="scroller"
|
||||
:items="filteredCustomers"
|
||||
:item-size="60"
|
||||
key-field="acc_no"
|
||||
v-slot="{ item }"
|
||||
>
|
||||
<v-list-item >
|
||||
<v-sheet class="clickable" @click="getCustomerInfo(item)" rounded :class="{ 'bg-error' : item.at_risk }">
|
||||
<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-row>
|
||||
</template>
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import RecentOrders from '@/components/RecentOrders.vue'
|
||||
import CustomerComments from '@/components/CustomerComments.vue'
|
||||
import Customer from '@/types/CustomerType.vue'
|
||||
export default {
|
||||
props: {
|
||||
site_info: {},
|
||||
|
@ -36,15 +79,16 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
searchQuery: "",
|
||||
showSearch: true,
|
||||
open: false,
|
||||
list: [],
|
||||
customer_list: [],
|
||||
customers_loading: null,
|
||||
selected_cust: new Customer(),
|
||||
limit: 5000,
|
||||
loading: true,
|
||||
listreceived: false
|
||||
listreceived: false,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
RecentOrders,
|
||||
CustomerComments
|
||||
},
|
||||
computed: {
|
||||
filteredCustomers() {
|
||||
|
@ -52,38 +96,58 @@ export default {
|
|||
if (!this.listreceived){
|
||||
this.getCustomerList()
|
||||
}
|
||||
let clist = this.list.filter(q =>
|
||||
let clist = this.customer_list.filter(q =>
|
||||
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
|
||||
},
|
||||
},
|
||||
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() {
|
||||
this.loading = true
|
||||
this.customers_loading = true
|
||||
let url = this.$api_url + "/customers/list"
|
||||
axios
|
||||
.get(url,{
|
||||
params: { limit: this.limit, query: this.searchQuery }})
|
||||
params: {
|
||||
limit: this.limit,
|
||||
query: "" }})
|
||||
.then(resp => {
|
||||
this.list = resp.data
|
||||
this.customer_list = resp.data
|
||||
this.listreceived = true
|
||||
this.loading = false
|
||||
})
|
||||
.catch(error => (console.log(error)))
|
||||
.finally(() => {
|
||||
this.customers_loading = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.scroller {
|
||||
height: 500px;
|
||||
height: 300px;
|
||||
}
|
||||
.customer {
|
||||
height: 32%;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.scroller.small {
|
||||
height: 200px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,3 +1,191 @@
|
|||
<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>
|
||||
<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,63 +10,68 @@
|
|||
<v-window-item value="list">
|
||||
<v-row>
|
||||
<v-col>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col cols="12" xs="12" sm="12" md="12" lg=8>
|
||||
<v-text-field clearable
|
||||
label="Search"
|
||||
variant="outlined"
|
||||
v-model="searchQuery"
|
||||
density="compact"
|
||||
append-inner-icon="mdi-magnify"></v-text-field>
|
||||
</v-col>
|
||||
</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-btn v-if="site_info.features.addmedfeed" color="warning" @click="editMedFeed()" prepend-icon="mdi-plus" variant="text">Add</v-btn>
|
||||
<v-progress-linear indeterminate color="blue" :active="loading"></v-progress-linear>
|
||||
<RecycleScroller class="scroller"
|
||||
:items="filteredMedFeeds"
|
||||
:item-size="130"
|
||||
v-slot="{ item }"
|
||||
key-field="id"
|
||||
>
|
||||
<v-row class="item" :class="{ at_risk : item.customer.at_risk }">
|
||||
<v-col cols="4">
|
||||
Medicated Feed : {{ item.id }},
|
||||
{{ item.customer.acc_no }} - {{ item.customer.name }}
|
||||
</v-col>
|
||||
<v-col>
|
||||
{{ item.medication.name }} {{ item.medication.inclusion_rate }}<br />
|
||||
{{ item.product.name }}
|
||||
</v-col>
|
||||
<v-col>
|
||||
Required : {{ formatDate(item.date_required,"DD/MM/YYYY") }} <br/>
|
||||
Repeat Prescription? : <v-icon v-if="item.repeat">mdi-refresh</v-icon>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-btn>
|
||||
More
|
||||
<v-overlay activator="parent" class="align-center justify-center">
|
||||
<v-container>
|
||||
<v-card width="600">
|
||||
<v-card-title>Info</v-card-title>
|
||||
<v-card-subtitle>Repeat Message</v-card-subtitle>
|
||||
<v-card-text>{{ item.repeat_message }}</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn class="mb-2 mr-2" color="blue" @click="reportScriptReq(item)">Script Request</v-btn>
|
||||
<v-btn class="mb-2 mr-2" color="blue" @click="reportOrderForm(item)">Order Form</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-container>
|
||||
</v-overlay>
|
||||
</v-btn>
|
||||
<v-btn v-if="site_info.editmedfeed" >Edit</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</RecycleScroller>
|
||||
<v-card variant="outlined">
|
||||
<RecycleScroller class="scroller"
|
||||
:items="filteredMedFeeds"
|
||||
:item-size="100"
|
||||
v-slot="{ item }"
|
||||
key-field="id">
|
||||
<v-row dense class="item" :class="{ at_risk : item.customer.at_risk }">
|
||||
<v-col cols="4">
|
||||
Medicated Feed : {{ item.id }}<br/>
|
||||
<span class="text-caption">
|
||||
{{ item.customer.acc_no }} - {{ item.customer.name }}<br/>
|
||||
Vet: {{ item.vet.practice }}</span>
|
||||
</v-col>
|
||||
<v-col class="text-caption">
|
||||
{{ item.medication.name }} {{ item.medication.inclusion_rate }}<br />
|
||||
{{ item.product.name }}
|
||||
</v-col>
|
||||
<v-col class="text-caption">
|
||||
Required : {{ formatDate(item.date_required,"DD/MM/YYYY") }} <br/>
|
||||
Repeat Prescription? : <v-icon v-if="item.repeat">mdi-refresh</v-icon>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<div class="d-flex justify-space-around align-center flex-column flex-sm-row fill-height">
|
||||
<v-btn>
|
||||
More
|
||||
<v-overlay activator="parent" class="align-center justify-center">
|
||||
<v-container>
|
||||
<v-card width="600">
|
||||
<v-card-title>Info</v-card-title>
|
||||
<v-card-subtitle>Repeat Message</v-card-subtitle>
|
||||
<v-card-text>{{ item.repeat_message }}</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn class="mb-2 mr-2" color="blue" @click="reportScriptReq(item)">Script Request</v-btn>
|
||||
<v-btn class="mb-2 mr-2" color="blue" @click="reportOrderForm(item)">Order Form</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-container>
|
||||
</v-overlay>
|
||||
</v-btn><br/>
|
||||
<v-btn v-if="site_info.features.editmedfeed" color="warning" @click="editMedFeed(item)">Edit</v-btn>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</RecycleScroller>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-window-item>
|
||||
<v-window-item value="edit">
|
||||
<MedFeedsEdit />
|
||||
<MedFeedsEdit :set_mf="selected_mf" @closetab="tab = 'list'" @medfeedupdated="medfeedUpdated" />
|
||||
</v-window-item>
|
||||
<v-window-item value="scriptreq">
|
||||
<ScriptReq :mf="selected_mf" :user="user_info"></ScriptReq>
|
||||
|
@ -82,6 +87,7 @@ import MedFeedsEdit from './MedFeedsEdit.vue'
|
|||
import ScriptReq from './MedFeedsScriptReq.vue'
|
||||
import OrderForm from './MedFeedsOrderForm.vue'
|
||||
import methods from '@/CommonMethods.vue'
|
||||
import MedFeedType from '@/types/MedFeedType.vue';
|
||||
export default {
|
||||
props: {
|
||||
site_info: {},
|
||||
|
@ -114,7 +120,8 @@ export default {
|
|||
}
|
||||
let clist = this.list.filter(q =>
|
||||
q.customer.name.toLowerCase().includes(query) ||
|
||||
q.customer.acc_no.includes(query)
|
||||
q.customer.acc_no.includes(query) ||
|
||||
q.id == query
|
||||
)
|
||||
return clist
|
||||
}
|
||||
|
@ -124,11 +131,13 @@ export default {
|
|||
async getMedFeedsList(){
|
||||
this.loading = true
|
||||
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,{
|
||||
params: {
|
||||
limit: this.limit,
|
||||
query: this.searchQuery
|
||||
query: this.searchQuery,
|
||||
c_id: c_id
|
||||
}
|
||||
}).then(resp => {
|
||||
this.list = resp.data
|
||||
|
@ -162,9 +171,19 @@ export default {
|
|||
})
|
||||
},
|
||||
async editMedFeed(mf) {
|
||||
console.log(mf)
|
||||
if ( mf != undefined ) {
|
||||
this.selected_mf = mf
|
||||
} else {
|
||||
this.selected_mf = new MedFeedType()
|
||||
}
|
||||
this.edit = true
|
||||
this.tab = "edit"
|
||||
this.selected_mf = mf
|
||||
},
|
||||
medfeedUpdated(){
|
||||
this.getMedFeedsList()
|
||||
this.edit = false
|
||||
this.tab = "list"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -175,9 +194,9 @@ export default {
|
|||
}
|
||||
|
||||
.item {
|
||||
height: 138px;
|
||||
height: 100px;
|
||||
overflow-y:hidden;
|
||||
padding: 0 12px;
|
||||
padding: 0 1em;
|
||||
margin-bottom:2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
|
@ -74,6 +74,7 @@
|
|||
size is 2 tonnes.</p>
|
||||
<p>If you require any further information about the veterinary
|
||||
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>
|
||||
</main>
|
||||
</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
|