Compare commits

..

No commits in common. "main" and "v1.0.0" have entirely different histories.
main ... v1.0.0

61 changed files with 7345 additions and 22566 deletions

1
.gitignore vendored
View file

@ -21,4 +21,3 @@ pnpm-debug.log*
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?

Binary file not shown.

View file

@ -1,17 +0,0 @@
#!/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

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{ {
"name": "cmc_fe", "name": "cmc_fe",
"version": "0.1.10", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
@ -9,37 +9,35 @@
}, },
"dependencies": { "dependencies": {
"@mdi/font": "5.9.55", "@mdi/font": "5.9.55",
"@vuepic/vue-datepicker": "^3.6.8", "@vuepic/vue-datepicker": "^3.6.4",
"animate.css": "^4.1.1", "axios": "^1.2.2",
"axios": "^1.3.4", "core-js": "^3.8.3",
"core-js": "^3.30.0",
"html2pdf.js": "^0.10.1",
"moment": "^2.29.4", "moment": "^2.29.4",
"roboto-fontface": "^0.10.0", "roboto-fontface": "*",
"sass": "^1.60.0", "sass": "^1.57.1",
"sass-loader": "^13.2.2", "sass-loader": "^13.2.0",
"scss": "^0.2.4", "scss": "^0.2.4",
"vue": "^3.2.47", "vue": "^3.2.13",
"vue-datepicker": "^1.3.0", "vue-datepicker": "^1.3.0",
"vue-meta": "^2.4.0", "vue-meta": "^2.4.0",
"vue-router": "^4.1.6", "vue-router": "^4.0.3",
"vue-splash": "^1.2.1", "vue-virtual-scroller": "^2.0.0-beta.7",
"vue-virtual-scroller": "2.0.0-beta.8", "vue3-html2pdf": "^1.1.2",
"vue3-print-nb": "^0.1.4", "vue3-print-nb": "^0.1.4",
"vuetify": "^3.1.12", "vuetify": "^3.0.0-beta.0",
"webfontloader": "^1.6.28" "webfontloader": "^1.0.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.21.4", "@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.21.3", "@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.8", "@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~5.0.8", "@vue/cli-plugin-eslint": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.8", "@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-service": "~5.0.8", "@vue/cli-service": "~5.0.0",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-plugin-vue": "^8.7.1", "eslint-plugin-vue": "^8.0.3",
"vue-cli-plugin-vuetify": "~2.5.8", "vue-cli-plugin-vuetify": "~2.5.8",
"webpack-plugin-vuetify": "^2.0.1" "webpack-plugin-vuetify": "^2.0.0-alpha.0"
}, },
"eslintConfig": { "eslintConfig": {
"root": true, "root": true,

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

View file

@ -4,12 +4,8 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>images/logo_fields.png"> <link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title> <title><%= htmlWebpackPlugin.options.title %></title>
<link rel="icon" href="<%= BASE_URL %>images/favicon32.png" sizes="32x32" />
<link rel="icon" href="<%= BASE_URL %>images/favicon192.png" sizes="192x192" />
<link rel="apple-touch-icon" href="<%= BASE_URL %>images/favicon180.png" />
<meta name="msapplication-TileImage" content="<%= BASE_URL %>images/favicon270.png" />
</head> </head>
<body> <body>
<noscript> <noscript>

View file

@ -1,40 +1,12 @@
<template> <template>
<CMCApp @ready="isReady" :class="{ fadein : !isLoading }" /> <CMCApp />
<LoadingScreen :isLoading="isLoading" />
</template> </template>
<script> <script>
import CMCApp from './CMCApp.vue' import CMCApp from './CMCApp.vue'
import LoadingScreen from "./components/LoadingScreen.vue"
export default { export default {
data(){
return {
isLoading: true
}
},
components: { components: {
CMCApp, CMCApp
LoadingScreen
},
methods: {
isReady() {
this.isLoading = false
} }
},
} }
</script> </script>
<style>
.fadein {
animation: fadein 1s forwards;
}
@keyframes fadein {
from {
opacity:0;
visibility:hidden;
}
to {
opacity:1;
visibility:visible;
}
}
</style>

View file

@ -1,14 +1,12 @@
<template> <template>
<v-app> <v-app>
<MyNav :user="user" :site_info="site_info" v-if="!isLoading" /> <MyNav :user="user" :site_info="site_info" />
<v-main class="ma-4"> <v-main class="ma-4">
<v-banner v-if="!site_info.backend_connected"
icon="mdi-exclamation"
color="error"
text="Cannot connect to the data service. Please contact support." >
</v-banner>
<router-view :site_info="site_info" :user_info="user_info"></router-view> <router-view :site_info="site_info" :user_info="user_info"></router-view>
</v-main> </v-main>
<v-footer>
<sub>{{ site_info.name }} v{{ site_info.version }}</sub>
</v-footer>
</v-app> </v-app>
</template> </template>
@ -19,16 +17,13 @@ import axios from 'axios'
export default { export default {
name: 'App', name: 'App',
components: { components: {
MyNav MyNav,
}, },
emits: ["ready"],
data() { data() {
return { return {
isLoading: true,
site_info: { site_info: {
name: "", name: "Loading...",
features: {}, features: {}
backend_connected: false
}, },
user: { user: {
first_name: "", first_name: "",
@ -49,14 +44,6 @@ export default {
} }
}, },
methods: { methods: {
checkIfReady(){
if (this.site_info.name != "") {
setTimeout(() => {
this.isLoading = false
this.$emit("ready")
},1000)
}
},
checkLoginStatus() { checkLoginStatus() {
let url = this.$api_url + "/users/check_login" let url = this.$api_url + "/users/check_login"
console.log("Checking login status...") console.log("Checking login status...")
@ -83,24 +70,10 @@ export default {
}, },
async getSiteInfo() { async getSiteInfo() {
console.log("Trying to get site Info...")
axios axios
.get(this.$api_url + "/info") .get(this.$api_url + "/info")
.then(response => { .then(response => {this.site_info = response.data})
this.site_info = response.data .catch(error => (console.log(error)))
console.log(this.site_info)
this.site_info.backend_connected = true
})
.catch(error => {
this.site_info.name = "Error getting data connection"
this.site_info.backend_connected = false
console.log(error)
setTimeout(() => {
this.getSiteInfo()
},5000)
}).finally(() => {
this.checkIfReady()
})
}, },
async getUserInfo() { async getUserInfo() {

View file

@ -1,14 +1,6 @@
<script> <script>
import moment from 'moment' import moment from 'moment'
import axios from 'axios'
import Error from '@/types/ErrorType.vue'
export default { export default {
data() {
return {
customer_list: [],
errors: {}
}
},
methods:{ methods:{
formatDate(d,f) { formatDate(d,f) {
return moment(String(d)).format(f) return moment(String(d)).format(f)
@ -34,35 +26,6 @@ export default {
minute: '2-digit', minute: '2-digit',
second: '2-digit'}) second: '2-digit'})
}, },
async error(msg) {
let e = new Error()
e.msg = msg
e.id = crypto.randomUUID()
this.errors[e.id] = e
setTimeout(() => {
delete this.errors[e.id]
}, 10000)
},
async getCustomerList(name) {
console.log("searching customers...")
if (this.customer_list.length == 0) {
console.log("getting new customers...")
let url = this.$api_url + "/customers/full_list"
axios.get(url)
.then(resp => {
this.customer_list = resp.data
console.log(this.customer_list)
return this.customer_list.filter(x => {
x.name.contains(name) || x.acc_no.contains(name)
})
})
.catch(err => {
console.log(err)
})
} else {
return this.customer_list
}
}
} }
} }
</script> </script>

View file

@ -67,10 +67,10 @@ table tr.at_risk:hover {
color: grey; color: grey;
} }
.scroller { .scroller {
height: 600px; border: 1px dotted #333;
} }
.scroller.small { .scroller {
height: 300px; height: 600px;
} }
.item { .item {
@ -82,6 +82,3 @@ table tr.at_risk:hover {
align-items: center; align-items: center;
border-bottom: 1px solid #000; border-bottom: 1px solid #000;
} }
.clickable {
cursor:pointer;
}

View file

@ -1,50 +0,0 @@
<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>

View file

@ -1,156 +0,0 @@
<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>

View file

@ -1,113 +0,0 @@
<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>

View file

@ -1,66 +0,0 @@
<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>

View file

@ -1,25 +0,0 @@
<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>

View file

@ -1,75 +0,0 @@
<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>

View file

@ -1,20 +0,0 @@
<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>

View file

@ -1,49 +0,0 @@
<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>

View file

@ -1,93 +0,0 @@
<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>

View file

@ -4,25 +4,7 @@
<v-app-bar-nav-icon v-if="user.logged_in" variant="text" @click.stop="drawer = !drawer"></v-app-bar-nav-icon> <v-app-bar-nav-icon v-if="user.logged_in" variant="text" @click.stop="drawer = !drawer"></v-app-bar-nav-icon>
<v-toolbar-title>{{ site_info.name }}</v-toolbar-title> <v-toolbar-title>{{ site_info.name }}</v-toolbar-title>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-menu> <v-btn variant="text" v-if="user.logged_in">Hi {{ user.first_name }}</v-btn>
<template v-slot:activator="{ props }">
<v-btn
variant="text"
v-if="user.logged_in"
v-bind="props">
Hi {{ user.first_name }}
</v-btn>
</template>
<v-list theme="dark">
<v-list-item
v-for="(item, index) in user_items"
:key="index"
:title="item.title"
:to="item.value"
>
</v-list-item>
</v-list>
</v-menu>
</v-app-bar> </v-app-bar>
<v-navigation-drawer <v-navigation-drawer
v-if="user.logged_in" v-if="user.logged_in"
@ -46,22 +28,11 @@
</v-list-item> </v-list-item>
</template> </template>
</template> </template>
<v-divider></v-divider>
<v-footer>
<v-banner>
<v-banner-text>
<sub>{{ site_info.name }}<br/>
BE v{{ site_info.version }}<br/>
FE v{{ appVersion }}
</sub>
</v-banner-text>
</v-banner>
</v-footer>
</v-list> </v-list>
</v-navigation-drawer> </v-navigation-drawer>
</template> </template>
<script> <script>
import { version } from "@/../package.json"
export default{ export default{
name: "MyNav", name: "MyNav",
props: { props: {
@ -78,19 +49,12 @@ export default{
} else { } else {
this.items = [] this.items = []
} }
},
},
computed: {
user_items() {
return this.items.filter(x => x.isUserMgmt == true)
} }
}, },
data(){ data(){
return { return {
appVersion: version,
items: [], items: [],
drawer: null, drawer: null
drawer2: false
} }
}, },
created() { created() {
@ -101,6 +65,7 @@ export default{
methods:{ methods:{
get_menu() { get_menu() {
let items = [] let items = []
items.push({title: "Dashboard", value:"/about"})
items.push({title: "Customers", items.push({title: "Customers",
value:"/customers", value:"/customers",
children:[{title:"List", children:[{title:"List",
@ -119,7 +84,7 @@ export default{
value:"/sop/printed"}] value:"/sop/printed"}]
}) })
items.push({title: "Logout", value:"/logout", isUserMgmt: true}) items.push({title: "Logout", value:"/logout"})
return items return items
} }
} }

View file

@ -1,6 +1,6 @@
<template> <template>
<v-container class="button-container"> <v-container class="button-container">
<v-btn :loading="generating" color="primary" @click="generatePdf()" class="mr-2">Create PDF</v-btn> <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 color="grey" v-print="printObj">Print</v-btn>
</v-container> </v-container>
</template> </template>
@ -8,12 +8,10 @@
import html2pdf from 'html2pdf.js'; import html2pdf from 'html2pdf.js';
export default { export default {
props: { props: {
scope: String, scope: String
filename: String
}, },
data() { data() {
return { return {
generating: false,
printObj: { printObj: {
id: this.scope, id: this.scope,
previewTitle: "Report Print", previewTitle: "Report Print",
@ -22,16 +20,8 @@ export default {
} }
}, },
methods:{ methods:{
generatePdf() { async generatePdf() {
this.generating = true html2pdf(document.getElementById(this.scope))
setTimeout(() => {
let el = document.getElementById(this.scope)
let opts = {
filename: (this.filename || 'file' ) + ".pdf"
}
html2pdf().set(opts).from(el).save()
this.generating = false
},500)
} }
} }
} }

View file

@ -1,64 +0,0 @@
<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>

View file

@ -1,152 +0,0 @@
<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>

View file

@ -1,5 +1,5 @@
<template> <template>
<PrintButtons :scope="scope" :filename="filename" /> <PrintButtons :scope="scope" />
<v-container> <v-container>
<v-card class="a4 page"> <v-card class="a4 page">
<div :id="scope" class="pdf-scope"> <div :id="scope" class="pdf-scope">
@ -10,11 +10,9 @@
</template> </template>
<script> <script>
import PrintButtons from './PrintButtons.vue' import PrintButtons from './PrintButtons.vue'
import '@/assets/css/reports.css';
export default { export default {
props: { props: {
scope: String, scope: String
filename: String
}, },
components: { components: {
PrintButtons PrintButtons

View file

@ -1,73 +0,0 @@
<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>

View file

@ -10,8 +10,6 @@ import './assets/css/app.scss'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css' import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import axios from 'axios' import axios from 'axios'
import VueVirtualScroller from 'vue-virtual-scroller' import VueVirtualScroller from 'vue-virtual-scroller'
import DebugPanel from '@/components/DebugPanel.vue'
import ErrorBanner from '@/components/ErrorBanner.vue'
axios.defaults.headers.common['X-Authentication'] = `Bearer ${localStorage.getItem('access_token')}`; axios.defaults.headers.common['X-Authentication'] = `Bearer ${localStorage.getItem('access_token')}`;
@ -21,8 +19,6 @@ const app = createApp(App).use(router)
.use(vuetify) .use(vuetify)
.use(print) .use(print)
.use(VueVirtualScroller) .use(VueVirtualScroller)
.use(DebugPanel)
.use(ErrorBanner)
.component('DatePicker', Datepicker) .component('DatePicker', Datepicker)
var url = window.location.protocol + "//" + window.location.host + "/api/v1" var url = window.location.protocol + "//" + window.location.host + "/api/v1"

View file

@ -1,17 +0,0 @@
<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>

View file

@ -1,4 +1,5 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const AboutView = () => import('../views/AboutView.vue') const AboutView = () => import('../views/AboutView.vue')
const LoginPage = () => import('../views/LoginPage.vue') const LoginPage = () => import('../views/LoginPage.vue')
const LogOut = () => import('../components/LogOut.vue') const LogOut = () => import('../components/LogOut.vue')
@ -6,14 +7,13 @@ const CustomerList = () => import('../views/customers/CustomerList.vue')
const ContractList = () => import('../views/contracts/ContractList.vue') const ContractList = () => import('../views/contracts/ContractList.vue')
const ComplaintsList = () => import('../views/complaints/ComplaintsList.vue') const ComplaintsList = () => import('../views/complaints/ComplaintsList.vue')
const MedFeedsList = () => import('../views/medfeeds/MedFeedsList.vue') const MedFeedsList = () => import('../views/medfeeds/MedFeedsList.vue')
const OrderList = () => import('../views/salesorders/OrdersList.vue')
const SOPPrintedList = () => import('../views/salesorders/SOPPrinted.vue') const SOPPrintedList = () => import('../views/salesorders/SOPPrinted.vue')
const routes = [ const routes = [
{ {
path: '/', path: '/',
name: 'home', name: 'home',
component: CustomerList component: HomeView
}, },
{ {
path: '/about', path: '/about',
@ -40,41 +40,16 @@ const routes = [
name: 'contractlist', name: 'contractlist',
component: ContractList component: ContractList
}, },
{
path: '/customers/contracts/list/:id',
name: 'contractlistid',
component: ContractList
},
{ {
path: '/customers/medicated-feeds/list', path: '/customers/medicated-feeds/list',
name: 'medfeedslist', name: 'medfeedslist',
component: MedFeedsList component: MedFeedsList
}, },
{
path: '/customers/medicated-feeds/list/:id',
name: 'medfeedslistid',
component: MedFeedsList
},
{ {
path: '/customers/complaints/list', path: '/customers/complaints/list',
name: 'complaintslist', name: 'complaintslist',
component: ComplaintsList component: ComplaintsList
}, },
{
path: '/customers/complaints/list/:id',
name: 'complaintslistid',
component: ComplaintsList
},
{
path: '/customers/orders/list',
name: 'orderslist',
component: OrderList
},
{
path: '/customers/orders/list/:id',
name: 'orderslistid',
component: OrderList
},
{ {
path: '/sop/printed', path: '/sop/printed',
name: 'sopprinted', name: 'sopprinted',

View file

@ -1,7 +0,0 @@
<script>
export default class ComplaintInfo {
checks = [false, false, false, false]
permanent = false
comments = ""
}
</script>

View file

@ -1,16 +0,0 @@
<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>

View file

@ -1,18 +0,0 @@
<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>

View file

@ -1,19 +0,0 @@
<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>

View file

@ -1,13 +0,0 @@
<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>

View file

@ -1,6 +0,0 @@
<script>
export default class Error {
id = "";
msg = "";
}
</script>

View file

@ -1,23 +0,0 @@
<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>

View file

@ -1,9 +0,0 @@
<script>
export default class Medication {
id = 0;
name = "";
info = [];
med_code = "";
inclusion_rate = "";
}
</script>

View file

@ -1,7 +0,0 @@
<script>
export default class Product {
code = "";
name = "";
price = "";
}
</script>

View file

@ -1,7 +0,0 @@
<script>
export default class Vet {
id = 0;
practice = "";
contacts = [];
}
</script>

View file

@ -1,23 +1,14 @@
<template> <template>
<v-card width="500" min-height="350" <v-card width="500" height="300"
class="mx-auto my-12" class="mx-auto my-12"
id="login-box"
title="Welcome!" title="Welcome!"
subtitle="Please Log In"> subtitle="Please Log In">
<v-form :disabled="!my_site_info.backend_connected"> <v-form>
<v-responsive class="mx-auto" max-width="475"> <v-responsive class="mx-auto" max-width="475">
<v-text-field label="Username" <v-text-field label="Email" clearable v-model="user.email"></v-text-field>
@keyup.enter="submitLogin" <v-text-field label="Password" v-model="user.password" clearable
clearable
v-model="user.email"></v-text-field>
<v-text-field label="Password"
@keyup.enter="submitLogin"
v-model="user.password" clearable
type="password"></v-text-field> type="password"></v-text-field>
<v-btn :disabled="!my_site_info.backend_connected" color="blue" :loading="logging_in" @click="submitLogin">Login</v-btn> <v-btn color="blue" @click="submitLogin">Login</v-btn>
<v-banner v-if="show_error" color="error" icon="mdi-exclamation-thick" theme="dark">
<v-banner-text>{{ error_message }}</v-banner-text>
</v-banner>
</v-responsive> </v-responsive>
</v-form> </v-form>
</v-card> </v-card>
@ -27,35 +18,18 @@
import axios from 'axios' import axios from 'axios'
export default { export default {
props: {
site_info: {}
},
name: 'LoginPage', name: 'LoginPage',
data() { data() {
return { return {
my_site_info: this.site_info,
user: { user: {
email: "", email: "",
password: "" password: ""
},
show_error: false,
error_message: "",
error_count: 0,
logging_in: false
} }
},
watch: {
'site_info.backend_connected'(new_val) {
console.log(new_val)
this.my_site_info.backend_connected = new_val
} }
}, },
methods: { methods: {
submitLogin() { submitLogin() {
this.error_message = ""
this.show_error = false
let url = this.$api_url + "/users/login" let url = this.$api_url + "/users/login"
this.logging_in = true
console.log("Logging in...") console.log("Logging in...")
axios axios
.post(url, { .post(url, {
@ -64,7 +38,6 @@ export default {
}) })
.then(resp => { .then(resp => {
let data = resp.data let data = resp.data
console.log(data)
if (data.logged_in) { if (data.logged_in) {
let token = data.token let token = data.token
localStorage.setItem('access_token', token.content) localStorage.setItem('access_token', token.content)
@ -74,34 +47,10 @@ export default {
console.log("Logged in") console.log("Logged in")
window.location.href = '/' window.location.href = '/'
} }
} else {
this.error_message = "Login failed. Invalid username or password."
this.error_count += 1
} }
}) })
.catch(error => { .catch(error => { console.log(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)
})
} }
} }
} }

View file

@ -1,175 +0,0 @@
<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>

View file

@ -1,88 +1,119 @@
<template> <template>
<h3>Complaints</h3> <h3>Complaints</h3>
<v-tabs v-model="tab" > <v-tabs v-model="tab" fixed-tabs>
<v-tab title="List" value="isList"></v-tab> <v-tab title="List" v-model="list" />
<v-tab title="Edit" value="edit" v-if="edit"></v-tab> <v-tab title="Edit" v-model="edit" v-if="edit" />
<v-tab title="Report" value="report" v-if="report" ></v-tab> <v-tab title="Report" v-model="report" v-if="report" />
</v-tabs> </v-tabs>
<v-window v-model="tab"> <v-window v-model="tab">
<v-window-item value="isList"> <v-window-item v-model="list">
<v-col cols="12" xs="12" sm="12" md="12" lg=8> <v-responsive
<v-text-field label="Search" max-width="500"
>
<v-text-field
clearable
label="Search"
variant="outlined" variant="outlined"
v-model="searchQuery" v-model="searchQuery"
density="compact" density="compact"
append-inner-icon="mdi-magnify"></v-text-field> append-inner-icon="mdi-magnify"></v-text-field>
<v-row justify="space-between"> <v-switch color="blue" label="Show Only Active" v-model="showActive"></v-switch>
<v-col cols=4> <v-btn v-if="site_info.features.addcomplaint" color="warning">+ Add</v-btn>
<v-btn v-if="site_info.features.addcomplaint" color="warning" prepend-icon="mdi-plus" variant="text" @click="showAddComplaint">Add</v-btn> </v-responsive>
</v-col> <v-table>
<v-col cols=3> <thead>
<v-btn align="end" variant="text" @click="showActive = !showActive"> <tr>
Showing <span v-if="showActive">Active Only</span><span v-else>Inactive</span> <th>Comp No</th>
</v-btn> <th>Date</th>
</v-col> <th>Order</th>
</v-row> <th>Acc No</th>
<v-progress-linear indeterminate color="orange" :active="loading"></v-progress-linear> <th>Name</th>
<v-card variant="outlined"> <th>Reason</th>
<RecycleScroller class="scroller" <th>Active</th>
:items="filteredComplaints" <th></th>
:item-size="100" </tr>
v-slot="{ item }" </thead>
key-field="id"> <tbody>
<v-row dense class="item" :class="{ 'bg-red-lighten-4' : item.at_risk }"> <tr v-if="loading">
<v-col cols="4"> <td colspan=7>
Complaint : {{ item.id }}<br/> <img class="loading" src="/images/icons/loading.gif"/>
<span class="text-caption"> </td>
Complaint Date : {{ item.complaint_date }}<br/> </tr>
Sales Order: {{ item.sop.doc_no }}<br/> <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-col> <span v-else>
<v-col cols="4" class="text-body-2"> View
{{ item.customer.acc_no }} - {{ item.customer.name }}<br/> </span>
Reason : {{ item.reason.reason }}<br/> </v-btn>
Driver : {{ item.driver.name }} </td>
</v-col> </tr>
<v-col cols=4> <tr v-if="complaint.info_shown">
<div class="d-flex justify-space-around align-center flex-column flex-sm-row fill-height"> <template v-if="complaint.info">
<v-row> <template v-if="complaint.info_loaded">
<v-icon icon="mdi-exclamation" v-if="item.at_risk" color="red" title="At Risk"></v-icon> <td colspan=4>
<v-icon icon="mdi-play" v-if="item.active" title="Active"></v-icon> {{ complaint.info.comments }}
<br />
<sub>- {{ complaint.info.added_by }}</sub>
</td>
<td colspan=2>
<v-container>
<v-form :disabled="complaint.info.loading">
<v-row dense nogutters>
<v-checkbox label="1" type="checkbox" v-model="complaint.info.one" @change="changeComplaintCheckbox(complaint)" />
<v-checkbox label="2" v-model="complaint.info.two" @change="changeComplaintCheckbox(complaint)"/>
<v-checkbox label="3" v-model="complaint.info.three" @change="changeComplaintCheckbox(complaint)"/>
</v-row> </v-row>
<v-btn @click="showInfo(item)" color="blue-lighten-1">View</v-btn> <v-row dense nogutters>
<v-btn v-if="site_info.features.editcomplaint" color="orange-lighten-2" @click="showEditComplaint(item)">Edit</v-btn> <v-checkbox label="At Risk" v-model="complaint.at_risk" @change="changeComplaintCheckbox(complaint)" />
</div> <v-checkbox label="Permanent" v-model="complaint.info.permanent" @change="changeComplaintCheckbox(complaint)" />
</v-col>
</v-row> </v-row>
</RecycleScroller> </v-form>
</v-card> </v-container>
</v-col> </td>
</v-window-item> <td>
<v-window-item value="edit"> <img v-if="complaint.info.loading" class="loading" src="/images/icons/loading.gif"/>
<ComplaintEdit ref="edit" :setcomplaint="selected_complaint" @closetab="tab = 'isList'" @complaintupdate="complaintUpdated"></ComplaintEdit> <template v-else>
<v-btn v-if="site_info.features.editcomplaint" color="warning">Edit</v-btn>
</template>
</td>
</template>
</template>
<template v-else>
<td colspan=6>
<img class="loading" src="/images/icons/loading.gif"/>
</td>
</template>
</tr>
</template>
</tbody>
</v-table>
</v-window-item> </v-window-item>
</v-window> </v-window>
<v-dialog v-model="showComplaintInfo">
<ComplaintInfo :in_complaint="selected_complaint" @return="doInfoReturn" />
</v-dialog>
</template> </template>
<script> <script>
import axios from 'axios' import axios from 'axios'
import Complaint from '@/types/ComplaintType.vue'
import ComplaintInfo from '@/components/ComplaintInfo.vue'
import ComplaintEdit from '@/views/complaints/ComplaintEdit.vue'
export default { export default {
props: { props: {
site_info:{}, site_info:{},
user_info:{}
},
components: {
ComplaintInfo,
ComplaintEdit
}, },
data() { data() {
return { return {
tab: "isList", tab: "list",
list: [], list: [],
listreceived: false, listreceived: false,
showActive: true, showActive: true,
@ -90,9 +121,7 @@ export default {
limit: 300, limit: 300,
searchQuery: "", searchQuery: "",
edit: false, edit: false,
report: false, report: false
showComplaintInfo: false,
selected_complaint: {}
} }
}, },
computed: { computed: {
@ -105,7 +134,7 @@ export default {
q.customer.name.toLowerCase().includes(query) || q.customer.name.toLowerCase().includes(query) ||
q.customer.acc_no.includes(query) || q.customer.acc_no.includes(query) ||
q.id == query || q.id == query ||
q.reason.reason.toLowerCase().includes(query) q.reason.toLowerCase().includes(query)
) )
if (this.showActive) { if (this.showActive) {
clist = clist.filter(q => clist = clist.filter(q =>
@ -116,65 +145,64 @@ export default {
} }
}, },
methods: { methods: {
getComplaintsList(){ async getComplaintsList(){
this.loading = true this.loading = true
let url = this.$api_url + "/customers/complaints/list" let url = this.$api_url + "/customers/complaints/list"
let c_id = this.$route.params.id || "" console.log("Getting Complaint list...")
console.log("Getting Contracts list..." + c_id)
axios.get(url,{ axios.get(url,{
params: { params: {
limit: this.limit, limit: this.limit,
query: this.searchQuery, query: this.searchQuery
c_id: c_id
} }
}).then(resp => { }).then(resp => {
this.list = resp.data this.list = resp.data
this.listreceived = true
}).catch(err => {
console.log(err)
}).finally(() => {
this.loading = false this.loading = false
this.listreceived = true
}) })
}, },
showInfo(complaint) { async getComplaintInfo(complaint) {
this.showComplaintInfo = true complaint.info.loading = true
this.selected_complaint = complaint let url = this.$api_url + "/customers/complaints/" + complaint.id + "/info"
console.log("Getting Complaint Info...")
axios.get(url)
.then(resp =>{
complaint.info = resp.data
complaint.info.loading = false
})
},
async showInfo(complaint) {
console.log(complaint.id)
complaint.info_loaded = false complaint.info_loaded = false
}, complaint.info_shown = !complaint.info_shown
doInfoReturn(code) { if (complaint.info_shown) {
console.log(code) complaint.info = await this.getComplaintInfo(complaint)
}, complaint.info_loaded = true
showAddComplaint() {
this.selected_complaint = new Complaint()
this.selected_complaint.isNew = true
this.edit = true
this.tab = "edit"
},
showEditComplaint(cmp) {
this.selected_complaint = cmp
this.selected_complaint.isNew = false
this.edit = true
this.tab = "edit"
},
complaintUpdated(cmp) {
console.log(cmp)
this.tab = "isList"
} }
},
async changeComplaintCheckbox(complaint) {
let set = await this.setComplaintStatus(complaint)
if (complaint.at_risk && set && complaint.info.one && complaint.info.two && complaint.info.three) {
console.log("Contract no longer at risk")
complaint.at_risk = false
}
},
async setComplaintStatus(complaint) {
complaint.info.loading = true
let url = this.$api_url + "/customers/complaints/" + complaint.id + "/info/set"
console.log("Getting Complaint Info...")
axios.post(url, {
one: complaint.info.one,
two: complaint.info.two,
three: complaint.info.three,
at_risk: complaint.at_risk,
permanent: complaint.info.permanent
}).then(resp => {
console.log(resp.data)
this.getComplaintInfo(complaint)
complaint.info_loaded = true
})
return true
},
} }
} }
</script> </script>
<style>
.scroller {
height:600px;
}
.item {
height: 100px;
overflow-y:hidden;
padding: 0 1em;
margin-bottom:2px;
display: flex;
align-items: center;
border-bottom: 1px solid #000;
}
</style>

View file

@ -1,12 +1,11 @@
<template> <template>
<v-card :title="title" :subtitle="'Contract : ' + contract.no"> <v-card title="Edit Contract" :subtitle="'Contract : ' + contract.no">
<v-card-text> <v-card-text>
<v-container> <v-container>
<v-row> <v-row>
<v-col cols="6"> <v-col cols="6">
<v-text-field readonly variant="outlined" prepend-inner-icon="mdi-magnify" @click="showCustomerSearch" label="Customer" :model-value="contract.customer.acc_no + ' - ' + contract.customer.name"> <v-text-field label="Customer" v-model="contract.customer.name" readonly></v-text-field>
</v-text-field> <v-text-field type="number" label="Tonnage Per Month" v-model="contract.tonnage_per_month"></v-text-field>
<v-text-field type="number" variant="outlined" label="Tonnage Per Month" v-model="contract.tonnage_per_month"></v-text-field>
</v-col> </v-col>
<v-col cols="6"> <v-col cols="6">
<label> <label>
@ -19,36 +18,33 @@
</label><br /> </label><br />
</v-col> </v-col>
</v-row> </v-row>
<v-table density="compact"> <template v-for="p in contract.products" :key="p.code">
<thead> <v-row>
<tr> <v-col cols="6">
<th style="width:65%;"> <v-autocomplete v-model="p.code"
Product v-model:search="product_search"
</th> :loading="products_loading"
<th style="width:20%"> :items="products"
Price cache-items
</th> hide-no-data
<th> hide-details
</th> solo-inverted
</tr> label="Code"
</thead> no-data-text="No Products Found"
<tbody> item-title="code"
<tr v-for="(p, index) in contract.products" :key="index"> item-value="code" ></v-autocomplete>
<td> </v-col>
<v-text-field readonly variant="outlined" prepend-inner-icon="mdi-magnify" @click="showProductSearch(index)" :model-value="p.code + ' - ' + p.name"></v-text-field> <v-col cols="4">
</td> <v-text-field type="number" label="Price" v-model="p.price"></v-text-field>
<td> </v-col>
<v-text-field prepend-icon="mdi-currency-gbp" type="number" density="compact" variant="outlined" v-model="p.price"></v-text-field> <v-col cols="2">
</td> <v-btn size="small" color="error" title="Remove" variant="plain" icon @click="contract.products.pop()"><v-icon>mdi-minus</v-icon></v-btn>
<td> </v-col>
<v-btn size="small" color="error" title="Remove" variant="plain" @click="removeProduct(p.code)" icon="mdi-minus"></v-btn> </v-row>
</td> </template>
</tr>
</tbody>
</v-table>
<v-row> <v-row>
<v-col cols="4"> <v-col cols="4">
<v-btn @click="addProduct()" v-if="contract.products.length < 4">+ Add Product</v-btn> <v-btn @click="contract.products.push({})" v-if="contract.products.length < 4">+ Add Product</v-btn>
</v-col> </v-col>
</v-row> </v-row>
<v-row> <v-row>
@ -59,145 +55,107 @@
</label> </label>
</v-col> </v-col>
<v-col cols="12"> <v-col cols="12">
<v-textarea rows=3 label="Comments" variant="outlined" v-model="contract.comments"></v-textarea> <v-textarea rows=3 label="Comments" v-model="contract.comments"></v-textarea>
<v-textarea rows=3 label="Office Comments" variant="outlined" v-model="contract.office_comments"></v-textarea> <v-textarea rows=3 label="Office Comments" v-model="contract.office_comments"></v-textarea>
</v-col> </v-col>
</v-row> </v-row>
</v-container> </v-container>
</v-card-text> </v-card-text>
<DebugPanel :data="contract"></DebugPanel>
<ErrorBanner :errors="errors" />
<v-card-actions> <v-card-actions>
<v-btn v-if="!contract.isNew" color="red-darken-1" <v-btn color="red-darken-1"
variant="text" variant="text"
:loading="saving"
@click="saveContract(selected_contract)">Save</v-btn> @click="saveContract(selected_contract)">Save</v-btn>
<v-btn v-if="contract.isNew" color="red-darken-1"
variant="text"
:loading="saving"
@click="saveContract(selected_contract)">Add</v-btn>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn color="blue-darken-1" <v-btn color="blue-darken-1"
variant="text" variant="text"
@click="close">Close</v-btn> @click="close">Close</v-btn>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
<v-dialog v-model="search[0]" scrollable>
<CustomerSearch @returnCustomer="setCustomer"></CustomerSearch>
</v-dialog>
<v-dialog v-model="search[1]">
<ProductSearch @returnProduct="setProduct"></ProductSearch>
</v-dialog>
</template> </template>
<script> <script>
import axios from 'axios' import axios from 'axios'
import DatePicker from '@vuepic/vue-datepicker' import DatePicker from '@vuepic/vue-datepicker'
import ErrorBanner from '@/components/ErrorBanner.vue'
import methods from '@/CommonMethods.vue'
import Product from '@/types/ProductType.vue'
import ProductSearch from '@/components/ProductSearch.vue'
import CustomerSearch from '@/components/CustomerSearch.vue'
export default { export default {
props: { props: {
setcontract: {} setcontract: {
no: Number,
customer: {
acc_no: String,
name: String,
at_risk: Boolean,
},
products: [{
code: String,
name: String,
price: Number,
}],
start_date: String,
finish_date: String,
agree_date: String,
tonnage_per_month: Number,
comments: String,
office_comments: String,
active: Boolean
}
}, },
components: { components: {
DatePicker, DatePicker
ErrorBanner,
ProductSearch,
CustomerSearch
}, },
watch: { watch: {
setcontract(newval) { setcontract(newval) {
this.contract = newval this.contract = newval
}, },
product_search(val) {
if (val && val.length > 1) {
this.searchProducts(val)
}
}
}, },
mixins: [methods],
data() { data() {
return { return {
contract: this.setcontract, contract: this.setcontract,
dialog: this.opendialog, dialog: this.opendialog,
saving: false, saving: false,
search: [],
searchProdIndex: null,
product_search: null, product_search: null,
products_loading: false, products_loading: false,
products: [], products: [],
customer_search: null,
customers_loading: false,
customers: [],
errors: []
} }
}, },
computed: {
title() {
if ( this.contract.isNew ) {
return "New Contract"
} else {
return "Edit Contract"
}
}
},
emits: ['closetab','contractupdate'],
methods: { methods: {
close() { close() {
this.$emit('closetab','list') this.$emit('closetab','list')
}, },
addProduct() {
this.contract.products.push(new Product())
},
removeProduct(code){
this.contract.products = this.contract.products.filter((c) => {
return c.code !== code
})
},
showCustomerSearch() {
this.search[0] = true
},
showProductSearch(num) {
this.search[1] = true
this.searchProdIndex = num
},
async saveContract(){ async saveContract(){
this.errors = []
this.saving = true this.saving = true
let url = this.$api_url + "/customers/contracts/" + this.contract.no + "/save" let url = this.$api_url + "/customers/contracts/" + this.contract.no + "/save"
if (this.contract.isNew) { console.log("Saving Contract : ", this.contract.no)
url = this.$api_url + "/customers/contracts/add" console.log(this.contract)
}
axios.post(url, { axios.post(url, {
contract: this.contract contract: this.contract
}).then(resp => { }).then(resp => {
console.log("Saved Contract : " + JSON.stringify(resp.data)) console.log("Saved Contract : " + JSON.stringify(resp.data))
this.saving = false this.saving = false
let stat = resp.data
if (stat.status == true ) {
if (this.contract.isNew) {
this.$emit('contractupdate', resp.data) this.$emit('contractupdate', resp.data)
} else {
this.$emit('contractupdate', resp.data)
}
} else {
this.errors.push("Contract not saved.")
console.log("Not Saved")
}
}).catch(err => { }).catch(err => {
console.log(err) console.log(err)
this.saving = false this.saving = false
}) })
}, },
searchProducts(code) {
let url = this.$api_url + "/products/search/" + code
console.log(url)
axios.get(url)
.then(resp => {
console.log(resp)
this.products = resp.data
})
.catch(err => {
console.log(err)
this.products = [{code:"NoProductsFound", name:"No Products Found"}]
})
},
productCodeName(p) { productCodeName(p) {
return p.code + ' - ' + p.name return p.code + ' - ' + p.name
},
setProduct(p){
let q = this.contract.products[this.searchProdIndex]
p.price = q.price
this.contract.products[this.searchProdIndex] = p
this.search[1] = false
},
setCustomer(c){
this.contract.customer = c
this.search[0] = false
} }
} }
} }

View file

@ -7,22 +7,21 @@
</v-tabs> </v-tabs>
<v-window v-model="tab" > <v-window v-model="tab" >
<v-window-item value="list"> <v-window-item value="list">
<v-row> <v-responsive
<v-col cols="8" xs="12" sm="12" md="12" lg="8"> max-width="500"
>
<v-text-field clearable <v-text-field clearable
label="Search" label="Search"
variant="outlined" variant="outlined"
v-model="searchQuery" v-model="searchQuery"
density="compact" density="compact"
append-inner-icon="mdi-magnify"></v-text-field> append-inner-icon="mdi-magnify"></v-text-field>
<v-btn color="warning" </v-responsive>
v-if="site_info.features.addcontract" <v-dialog v-model="showDialog">
@click="showEditContract()" </v-dialog>
prepend-icon="mdi-plus" <v-row>
variant="text" <v-col cols="8" xs="12" sm="12" md="8">
>Add</v-btn>
<v-progress-linear indeterminate color="blue" :active="loading"></v-progress-linear> <v-progress-linear indeterminate color="blue" :active="loading"></v-progress-linear>
<v-card variant="outlined">
<RecycleScroller class="scroller" <RecycleScroller class="scroller"
:items="filteredContracts" :items="filteredContracts"
:item-size="130" :item-size="130"
@ -53,7 +52,8 @@
= {{ formatNumber(item.tonnage_per_month * item.remaining_duration,2) }} remaining<br/> = {{ formatNumber(item.tonnage_per_month * item.remaining_duration,2) }} remaining<br/>
</v-col> </v-col>
<v-col> <v-col>
<div class="d-flex justify-space-around align-center flex-column flex-sm-row fill-height"> <v-row>
<v-col cols="12">
<v-btn color="info"> <v-btn color="info">
More More
<v-overlay activator="parent" class="align-center justify-center"> <v-overlay activator="parent" class="align-center justify-center">
@ -77,7 +77,7 @@
target="blank" target="blank"
@click="getContractPrint(item.no)" @click="getContractPrint(item.no)"
class="ma-2 pa-2" class="ma-2 pa-2"
:loading="multiloading" :loading="item.multiloading"
> >
Multi Multi
</v-btn> </v-btn>
@ -85,7 +85,7 @@
<v-btn color="info" <v-btn color="info"
target="blank" target="blank"
@click="getContractPrint(item.no,true)" @click="getContractPrint(item.no,true)"
:loading="totalloading" :loading="item.totalloading"
class="ma-2 pa-2" class="ma-2 pa-2"
> >
Total Total
@ -93,7 +93,6 @@
<v-btn color="warning" <v-btn color="warning"
v-if="site_info.features.editcontract" v-if="site_info.features.editcontract"
@click="showEditContract(item)" @click="showEditContract(item)"
:loading="editloading"
> >
Edit Edit
</v-btn> </v-btn>
@ -101,18 +100,19 @@
</v-card> </v-card>
</v-overlay> </v-overlay>
</v-btn> </v-btn>
</v-col>
<v-col cols="12">
<v-btn color="warning" <v-btn color="warning"
v-if="site_info.features.editcontract" v-if="site_info.features.editcontract"
@click="showEditContract(item)" @click="showEditContract(item)"
:loading="editloading"
> >
Edit Edit
</v-btn> </v-btn>
</div> </v-col>
</v-row>
</v-col> </v-col>
</v-row> </v-row>
</RecycleScroller> </RecycleScroller>
</v-card>
</v-col> </v-col>
</v-row> </v-row>
</v-window-item> </v-window-item>
@ -123,14 +123,11 @@
<ContractMulti :contract="selected_contract" :total="total" /> <ContractMulti :contract="selected_contract" :total="total" />
</v-window-item> </v-window-item>
</v-window> </v-window>
<v-dialog v-model="showDialog">
</v-dialog>
</template> </template>
<script> <script>
import ContractEdit from './ContractEdit.vue'; import ContractEdit from './ContractEdit.vue';
import ContractMulti from './ContractMulti.vue'; import ContractMulti from './ContractMulti.vue';
import methods from '@/CommonMethods.vue' import methods from '@/CommonMethods.vue'
import ContractType from '@/types/ContractType.vue';
import axios from 'axios' import axios from 'axios'
export default { export default {
props: { props: {
@ -182,37 +179,25 @@ export default {
edit: false, edit: false,
report: false, report: false,
total: false, total: false,
content: "", content: ""
editloading: false,
multiloading: false,
totalloading: false
} }
}, },
mixins: [methods], mixins: [methods],
methods: { methods: {
async showEditContract(contract) { async showEditContract(contract) {
this.editloading = true
setTimeout(() => {
if ( contract != undefined ) {
this.selected_contract = contract this.selected_contract = contract
} else { //this.selected_contract = contract
this.selected_contract = new ContractType()
}
this.tab = "edit" this.tab = "edit"
this.edit = true this.edit = true
this.editloading = false
},1000)
}, },
async getContractsList() { async getContractsList() {
this.loading = true this.loading = true
let url = this.$api_url + "/customers/contracts/list" let url = this.$api_url + "/customers/contracts/list"
let c_id = this.$route.params.id || "" console.log("Getting Contracts list...")
console.log("Getting Contracts list..." + c_id)
axios.get(url, { axios.get(url, {
params: { params: {
limit: this.limit, limit: this.limit,
query: this.searchQuery, query: this.searchQuery
c_id: c_id
} }
}) })
.then(resp => { .then(resp => {
@ -222,16 +207,12 @@ export default {
}) })
}, },
getContractPrint(contract, total = false) { getContractPrint(contract, total = false) {
if (total){ this.totalloading = true } else { this.multiloading = true }
axios.get(this.$api_url + "/customers/contracts/" + contract + "/info") axios.get(this.$api_url + "/customers/contracts/" + contract + "/info")
.then(resp => { .then(resp => {
this.selected_contract = resp.data this.selected_contract = resp.data
this.total = total this.total = total
this.tab = "report" this.tab = "report"
this.report = true this.report = true
}).finally(() => {
this.totalloading = false
this.multiloading = false
}) })
}, },
findContract(id) { findContract(id) {
@ -268,7 +249,7 @@ export default {
</script> </script>
<style scoped> <style scoped>
.scroller { .scroller {
height: 70vw; height: 600px;
} }
.item { .item {

View file

@ -1,5 +1,5 @@
<template> <template>
<ReportLayout scope="contract" :filename="filename"> <ReportLayout scope="contract">
<div class="letter"> <div class="letter">
<p><span class="text-bold">Date: </span>{{ current_date }}</p> <p><span class="text-bold">Date: </span>{{ current_date }}</p>
<p class="text-bold">Customer's Address:</p> <p class="text-bold">Customer's Address:</p>
@ -57,6 +57,7 @@
</template> </template>
<script> <script>
import '@/assets/css/reports.css';
import ReportLayout from '@/components/ReportLayout.vue' import ReportLayout from '@/components/ReportLayout.vue'
import Common from '@/common.js'; import Common from '@/common.js';
import moment from 'moment'; import moment from 'moment';
@ -71,7 +72,6 @@ export default {
}, },
data(){ data(){
return { return {
filename: "Contract_" + this.contract.no,
current_date: Common.getDateNow(), current_date: Common.getDateNow(),
} }
}, },

View file

@ -1,76 +1,33 @@
<template> <template>
<h3>Customer List</h3>
<v-responsive
max-width="500"
>
<v-text-field <v-text-field
clearable
label="Search" label="Search"
variant="outlined" variant="outlined"
v-model="searchQuery" v-model="searchQuery"
density="compact" density="compact"
append-inner-icon="mdi-magnify"></v-text-field> append-inner-icon="mdi-magnify"></v-text-field>
<v-row> </v-responsive>
<v-col cols="12" sm=12 lg=6>
<v-card>
<v-card-title>
Customer List
</v-card-title>
<v-progress-linear color="blue" :active="customers_loading" indeterminate>
</v-progress-linear>
<v-list>
<RecycleScroller <RecycleScroller
class="scroller" class="scroller"
:items="filteredCustomers" :items="filteredCustomers"
:item-size="60" :item-size="50"
key-field="acc_no" key-field="acc_no"
v-slot="{ item }" v-slot="{ item }"
> >
<v-list-item > <v-row class="customer">
<v-sheet class="clickable" @click="getCustomerInfo(item)" rounded :class="{ 'bg-error' : item.at_risk }"> <v-col cols="6">
<v-icon v-if="item.acc_no == selected_cust.acc_no">mdi-checkbox-marked-outline</v-icon> {{ item.acc_no }} - {{ item.name }}
<v-icon v-else>mdi-checkbox-blank-outline</v-icon>
{{ item.acc_no }} - {{ item.name }}:
<span class="text-caption">{{ item.address.line_1 }}, {{ item.address.line_2 }}, <br/>
{{ item.address.city }}, {{ item.address.county}}, {{ item.address.postcode }},
</span>
</v-sheet>
<template v-slot:append>
<v-btn-toggle>
<v-btn @click.prevent="viewOrders(item)" color="blue-lighten-1" title="View Orders">
<v-icon>mdi-cart-outline</v-icon>
</v-btn>
<v-btn @click.prevent="goToContracts(item)" color="blue-lighten-1" title="View Contracts">
<v-icon>mdi-file-edit-outline</v-icon>
<v-badge v-if="item.contract_count > 0" color="blue-lighten-4" floating :content="item.contract_count">
</v-badge>
</v-btn>
<v-btn @click.prevent="goToMedFeeds(item)" color="orange-lighten-1" title="View Med Feeds">
<v-icon>mdi-pill</v-icon>
<v-badge v-if="item.medfeed_count > 0" color="orange-lighten-4" floating :content="item.medfeed_count">
</v-badge>
</v-btn>
<v-btn @click.prevent="goToComplaints(item)" color="orange-lighten-1" title="View Complaints">
<v-icon>mdi-exclamation-thick</v-icon>
<v-badge v-if="item.complaint_count > 0" color="red-lighten-4" floating :content="item.complaint_count">
</v-badge>
</v-btn>
</v-btn-toggle>
</template>
</v-list-item>
</RecycleScroller>
</v-list>
</v-card>
<br/>
<RecentOrders :customer="selected_cust" size_small doc_status=0></RecentOrders>
</v-col>
<v-col cols="12" sm=12 lg=6>
<CustomerComments :customer="selected_cust"></CustomerComments>
<br/>
<RecentOrders :customer="selected_cust" doc_status=2></RecentOrders>
</v-col> </v-col>
</v-row> </v-row>
<hr />
</RecycleScroller>
</template> </template>
<script> <script>
import axios from 'axios'; import axios from 'axios';
import RecentOrders from '@/components/RecentOrders.vue'
import CustomerComments from '@/components/CustomerComments.vue'
import Customer from '@/types/CustomerType.vue'
export default { export default {
props: { props: {
site_info: {}, site_info: {},
@ -79,16 +36,15 @@ export default {
data() { data() {
return { return {
searchQuery: "", searchQuery: "",
customer_list: [], showSearch: true,
customers_loading: null, open: false,
selected_cust: new Customer(), list: [],
limit: 5000, limit: 5000,
listreceived: false, loading: true,
listreceived: false
} }
}, },
components: { components: {
RecentOrders,
CustomerComments
}, },
computed: { computed: {
filteredCustomers() { filteredCustomers() {
@ -96,58 +52,38 @@ export default {
if (!this.listreceived){ if (!this.listreceived){
this.getCustomerList() this.getCustomerList()
} }
let clist = this.customer_list.filter(q => let clist = this.list.filter(q =>
q.name.toLowerCase().includes(query) || q.name.toLowerCase().includes(query) ||
q.acc_no.includes(query) || q.acc_no.includes(query)
q.address.line_1.toLowerCase().includes(query) ||
q.address.line_2.toLowerCase().includes(query) ||
q.address.city.toLowerCase().includes(query) ||
q.address.postcode.toLowerCase().includes(query)
) )
return clist return clist
}, },
}, },
methods: { methods: {
getCustomerInfo(c) {
this.selected_cust = c
},
goToContracts(c){
this.$router.push('/customers/contracts/list/' + c.id)
},
goToMedFeeds(c){
this.$router.push('/customers/medicated-feeds/list/' + c.id)
},
goToComplaints(c){
this.$router.push('/customers/complaints/list/' + c.id)
},
viewOrders(c){
this.$router.push('/customers/orders/list/' + c.id)
},
async getCustomerList() { async getCustomerList() {
this.customers_loading = true this.loading = true
let url = this.$api_url + "/customers/list" let url = this.$api_url + "/customers/list"
axios axios
.get(url,{ .get(url,{
params: { params: { limit: this.limit, query: this.searchQuery }})
limit: this.limit,
query: "" }})
.then(resp => { .then(resp => {
this.customer_list = resp.data this.list = resp.data
this.listreceived = true this.listreceived = true
this.loading = false
}) })
.catch(error => (console.log(error))) .catch(error => (console.log(error)))
.finally(() => {
this.customers_loading = false
})
} }
} }
} }
</script> </script>
<style scoped> <style scoped>
.scroller { .scroller {
height: 300px; height: 500px;
} }
.scroller.small { .customer {
height: 200px; height: 32%;
padding: 0 12px;
display: flex;
align-items: center;
} }
</style> </style>

View file

@ -1,191 +1,3 @@
<template> <template>
<v-card :title="title" :subtitle="'Medicated Feed : ' + mf.id"> Not Implemented, yet :-)
<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>
</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>

View file

@ -10,41 +10,38 @@
<v-window-item value="list"> <v-window-item value="list">
<v-row> <v-row>
<v-col> <v-col>
</v-col>
</v-row>
<v-row>
<v-col cols="12" xs="12" sm="12" md="12" lg=8>
<v-text-field clearable <v-text-field clearable
label="Search" label="Search"
variant="outlined" variant="outlined"
v-model="searchQuery" v-model="searchQuery"
density="compact" density="compact"
append-inner-icon="mdi-magnify"></v-text-field> append-inner-icon="mdi-magnify"></v-text-field>
<v-btn v-if="site_info.features.addmedfeed" color="warning" @click="editMedFeed()" prepend-icon="mdi-plus" variant="text">Add</v-btn> </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-progress-linear indeterminate color="blue" :active="loading"></v-progress-linear> <v-progress-linear indeterminate color="blue" :active="loading"></v-progress-linear>
<v-card variant="outlined">
<RecycleScroller class="scroller" <RecycleScroller class="scroller"
:items="filteredMedFeeds" :items="filteredMedFeeds"
:item-size="100" :item-size="130"
v-slot="{ item }" v-slot="{ item }"
key-field="id"> key-field="id"
<v-row dense class="item" :class="{ at_risk : item.customer.at_risk }"> >
<v-row class="item" :class="{ at_risk : item.customer.at_risk }">
<v-col cols="4"> <v-col cols="4">
Medicated Feed : {{ item.id }}<br/> Medicated Feed : {{ item.id }},
<span class="text-caption"> {{ item.customer.acc_no }} - {{ item.customer.name }}
{{ item.customer.acc_no }} - {{ item.customer.name }}<br/>
Vet: {{ item.vet.practice }}</span>
</v-col> </v-col>
<v-col class="text-caption"> <v-col>
{{ item.medication.name }} {{ item.medication.inclusion_rate }}<br /> {{ item.medication.name }} {{ item.medication.inclusion_rate }}<br />
{{ item.product.name }} {{ item.product.name }}
</v-col> </v-col>
<v-col class="text-caption"> <v-col>
Required : {{ formatDate(item.date_required,"DD/MM/YYYY") }} <br/> Required : {{ formatDate(item.date_required,"DD/MM/YYYY") }} <br/>
Repeat Prescription? : <v-icon v-if="item.repeat">mdi-refresh</v-icon> Repeat Prescription? : <v-icon v-if="item.repeat">mdi-refresh</v-icon>
</v-col> </v-col>
<v-col> <v-col>
<div class="d-flex justify-space-around align-center flex-column flex-sm-row fill-height">
<v-btn> <v-btn>
More More
<v-overlay activator="parent" class="align-center justify-center"> <v-overlay activator="parent" class="align-center justify-center">
@ -60,18 +57,16 @@
</v-card> </v-card>
</v-container> </v-container>
</v-overlay> </v-overlay>
</v-btn><br/> </v-btn>
<v-btn v-if="site_info.features.editmedfeed" color="warning" @click="editMedFeed(item)">Edit</v-btn> <v-btn v-if="site_info.editmedfeed" >Edit</v-btn>
</div>
</v-col> </v-col>
</v-row> </v-row>
</RecycleScroller> </RecycleScroller>
</v-card>
</v-col> </v-col>
</v-row> </v-row>
</v-window-item> </v-window-item>
<v-window-item value="edit"> <v-window-item value="edit">
<MedFeedsEdit :set_mf="selected_mf" @closetab="tab = 'list'" @medfeedupdated="medfeedUpdated" /> <MedFeedsEdit />
</v-window-item> </v-window-item>
<v-window-item value="scriptreq"> <v-window-item value="scriptreq">
<ScriptReq :mf="selected_mf" :user="user_info"></ScriptReq> <ScriptReq :mf="selected_mf" :user="user_info"></ScriptReq>
@ -87,7 +82,6 @@ import MedFeedsEdit from './MedFeedsEdit.vue'
import ScriptReq from './MedFeedsScriptReq.vue' import ScriptReq from './MedFeedsScriptReq.vue'
import OrderForm from './MedFeedsOrderForm.vue' import OrderForm from './MedFeedsOrderForm.vue'
import methods from '@/CommonMethods.vue' import methods from '@/CommonMethods.vue'
import MedFeedType from '@/types/MedFeedType.vue';
export default { export default {
props: { props: {
site_info: {}, site_info: {},
@ -120,8 +114,7 @@ export default {
} }
let clist = this.list.filter(q => let clist = this.list.filter(q =>
q.customer.name.toLowerCase().includes(query) || q.customer.name.toLowerCase().includes(query) ||
q.customer.acc_no.includes(query) || q.customer.acc_no.includes(query)
q.id == query
) )
return clist return clist
} }
@ -131,13 +124,11 @@ export default {
async getMedFeedsList(){ async getMedFeedsList(){
this.loading = true this.loading = true
let url = this.$api_url + "/customers/medicated-feeds/list" let url = this.$api_url + "/customers/medicated-feeds/list"
let c_id = this.$route.params.id || "" console.log("Getting Medicated Feeds list...")
console.log("Getting Medicated Feeds list..." + c_id)
axios.get(url,{ axios.get(url,{
params: { params: {
limit: this.limit, limit: this.limit,
query: this.searchQuery, query: this.searchQuery
c_id: c_id
} }
}).then(resp => { }).then(resp => {
this.list = resp.data this.list = resp.data
@ -171,19 +162,9 @@ export default {
}) })
}, },
async editMedFeed(mf) { async editMedFeed(mf) {
console.log(mf)
if ( mf != undefined ) {
this.selected_mf = mf
} else {
this.selected_mf = new MedFeedType()
}
this.edit = true this.edit = true
this.tab = "edit" this.tab = "edit"
}, this.selected_mf = mf
medfeedUpdated(){
this.getMedFeedsList()
this.edit = false
this.tab = "list"
} }
} }
} }
@ -194,9 +175,9 @@ export default {
} }
.item { .item {
height: 100px; height: 138px;
overflow-y:hidden; overflow-y:hidden;
padding: 0 1em; padding: 0 12px;
margin-bottom:2px; margin-bottom:2px;
display: flex; display: flex;
align-items: center; align-items: center;

View file

@ -74,7 +74,6 @@
size is 2 tonnes.</p> size is 2 tonnes.</p>
<p>If you require any further information about the veterinary <p>If you require any further information about the veterinary
medicinal products we stock, please get in touch.</p> medicinal products we stock, please get in touch.</p>
<p class="text-underline">Please ensure the prescription is filled out as per the Veterinary Medicines Regulations 2013.</p>
<h4>The customer requires this order on {{ formatDate(mf.date_required,"DD/MM/yyyy") }}</h4> <h4>The customer requires this order on {{ formatDate(mf.date_required,"DD/MM/yyyy") }}</h4>
</main> </main>
</div> </div>

View file

@ -1,34 +0,0 @@
<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>

View file

@ -1,135 +0,0 @@
<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
View file

@ -1,34 +0,0 @@
### 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

6861
yarn.lock Normal file

File diff suppressed because it is too large Load diff