I keep getting an error that my first function in actions doesn't exist. I've been following along with the Vue video series, and trying to apply it to an existing vue app I'd like to improve upon. When I look at the vue devtools, it shows this info for the dashStore from the DocDash component. I have no idea what I am doing wrong that it won't recognize my function, so I can assign data to the days array in the pinia store. Can someone help me understand?
{"$id":"dashStore","$onAction":{"_custom":{"type":"function","display":"<span style=\"opacity:.5;\">function</span> bound addSubscription()","tooltip":"<pre>function () { [native code] }</pre>","_reviveId":0}},"$patch":{"_custom":{"type":"function","display":"<span style=\"opacity:.5;\">function</span> $patch(partialStateOrMutator)","tooltip":"<pre>function $patch(partialStateOrMutator) {\n let subscriptionMutation;\n isListening = isSyncListening = false;\n if (true) {\n debuggerEvents = [];\n }\n if (typeof partialStateOrMutator === \"function\") {\n partialStateOrMutator(pinia.state.value[$id]);\n subscriptionMutation = {\n type: MutationType.patchFunction,\n storeId: $id,\n events: debuggerEvents\n };\n } else {\n mergeReactiveObjects(pinia.state.value[$id], partialStateOrMutator);\n subscriptionMutation = {\n type: MutationType.patchObject,\n payload: partialStateOrMutator,\n storeId: $id,\n events: debuggerEvents\n };\n }\n const myListenerId = activeListener = Symbol();\n nextTick().then(() => {\n if (activeListener === myListenerId) {\n isListening = true;\n }\n });\n isSyncListening = true;\n triggerSubscriptions(subscriptions, subscriptionMutation, pinia.state.value[$id]);\n }</pre>","_reviveId":1}},"$reset":{"_custom":{"type":"function","display":"<span style=\"opacity:.5;\">function</span> $reset2()","tooltip":"<pre>function $reset2() {\n const { state } = options;\n const newState = state ? state() : {};\n this.$patch(($state) => {\n assign($state, newState);\n });\n }</pre>","_reviveId":2}},"$subscribe":{"_custom":{"type":"function","display":"<span style=\"opacity:.5;\">function</span> $subscribe(callback, options2 = {})","tooltip":"<pre>$subscribe(callback, options2 = {}) {\n const removeSubscription = addSubscription(subscriptions, callback, options2.detached, () => stopWatcher());\n const stopWatcher = scope.run(() => watch(() => pinia.state.value[$id], (state) => {\n if (options2.flush === \"sync\" ? isSyncListening : isListening) {\n callback({\n storeId: $id,\n type: MutationType.direct,\n events: debuggerEvents\n }, state);\n }\n }, assign({}, $subscribeOptions, options2)));\n return removeSubscription;\n }</pre>","_reviveId":3}},"$dispose":{"_custom":{"type":"function","display":"<span style=\"opacity:.5;\">function</span> ()","tooltip":"<pre>() => {\n $dispose();\n api.notifyComponentUpdate();\n api.sendInspectorTree(INSPECTOR_ID);\n api.sendInspectorState(INSPECTOR_ID);\n api.getSettings().logStoreChanges && toastMessage(`Disposed \"${store.$id}\" store \u{1F5D1}`);\n }</pre>","_reviveId":4}},"count":"__vue_devtool_undefined__","_hotUpdate":{"_custom":{"type":"function","display":"<span style=\"opacity:.5;\">function</span> (newStore)","tooltip":"<pre>(newStore) => {\n hotUpdate(newStore);\n api.addTimelineEvent({\n layerId: MUTATIONS_LAYER_ID,\n event: {\n time: now(),\n title: \"\u{1F525} \" + store.$id,\n subtitle: \"HMR update\",\n data: {\n store: formatDisplay(store.$id),\n info: formatDisplay(`HMR update`)\n }\n }\n });\n api.notifyComponentUpdate();\n api.sendInspectorTree(INSPECTOR_ID);\n api.sendInspectorState(INSPECTOR_ID);\n }</pre>","_reviveId":5}},"_isOptionsAPI":true,"appts":[{"id":29980,"patient":{"id":95,"state":"","doctor_id":1,"first":"John","last":"Doe","nickname":"DoeJ","home":"","work":"","cell":"1235551212","email":null,"address":"","city":"","zip":"","dob":null,"startdate":"2014-05-06","list":"","active":1,"referred_by":null,"referral_tag":null,"created_at":null,"updated_at":"2022-09-10T11:34:28.000000Z","cc":"Adjustment","deleted_at":null,"recall":null,"recall_note":null,"plan_id":14,"parent_id":95,"payment_day":1,"secondary_id":null},"patient_id":95,"nickname":"DoeJ","parent_id":null,"date_time":"2024-01-01T21:10:00.000000Z","payments":[],"parentpayments":[],"pmts":[],"appt_status_id":9,"appt_type_id":12,"appt_note":null,"subjective":"the patient is here to be checked for chiropractic subluxation.","appt_reminder":true,"objective_text":null,"assessment":"clear upper cervical check, no adjustment needed today","plan":"recheck next visit","clearUC":0,"doctor_id":1,"officehour_id":1},{"id":29888,"patient":{"id":234,"state":"","doctor_id":1,"first":"Jane","last":"Doe","nickname":"DoeJa","home":null,"work":"","cell":"5556661212","email":null,"address":"","city":"","zip":"","dob":null,"startdate":"2018-03-30","list":"","active":1,"referred_by":null,"referral_tag":null,"created_at":null,"updated_at":"2021-12-31T16:51:10.000000Z","cc":null,"deleted_at":null,"recall":1,"recall_note":"03-09-2021","plan_id":1,"parent_id":234,"payment_day":15,"secondary_id":null},"patient_id":234,"nickname":"DoeJa","parent_id":null,"date_time":"2024-01-01T22:00:00.000000Z","payments":[],"parentpayments":[],"pmts":[],"appt_status_id":6,"appt_type_id":2,"appt_note":null,"subjective":null,"appt_reminder":false,"objective_text":null,"assessment":null,"plan":null,"clearUC":0,"doctor_id":1,"officehour_id":1}]}
DocDash.vue
<template class="w-screen">
<!-- MESSAGES -->
<loading v-if="dash.isLoading" name="Appointments"></loading>
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 w-90" v-else>
<!-- APPT DASH -->
<div class="md:col-span-3 p-2">
<ApptDash></ApptDash>
<!-- Appt Modal -->
</div>
</div>
</template>
<script setup>
import {onMounted} from "vue"
import {useRoute} from "vue-router";
import { useDocDashStore } from "../../stores/DocDashStore";
import Loading from "@/components/Loading.vue";
import ApptDash from "@/components/ApptDash.vue";
const route = useRoute();
const day = route.params.day
const dash = useDocDashStore()
onMounted(async()=> {
await dash.loadAppts(day)
})
</script>
ApptDash.vue
<script setup>
import moment from "moment";
import {useDocDashStore} from "../../stores/DocDashStore";
import DashActions from "@/components/DashActions.vue";
const dash = useDocDashStore()
</script>
<template>
<DashActions :viewFinished="dash.viewFinished"></DashActions>
<table class="table">
<thead>
<tr>
<td>Select All
<input class="h-6 w-6" type="checkbox" @click="dash.selectAll()" :checked="dash.allSelected">
</td>
<td class="flex text-lg font-semibold"> {{ dash.appts.length + ' ' }}
<span class="flex text-lg font-semibold uppercase" v-if="!dash.viewFinished"> On Deck <svg @click="dash.viewFinished = !dash.viewFinished" class="ml-2 h-6 w-6 text-green-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <rect x="1" y="5" width="22" height="14" rx="7" ry="7" /> <circle cx="8" cy="12" r="3" /></svg></span>
<span class="flex text-lg font-semibold uppercase" v-if="dash.viewFinished"> Checked Out<svg @click="dash.viewFinished = !dash.viewFinished" class="ml-2 h-6 w-6 text-red-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <rect x="1" y="5" width="22" height="14" rx="7" ry="7" /> <circle cx="16" cy="12" r="3" /></svg></span>
</td>
<td>Time </td>
<td>Patient</td>
<td>Type</td>
</tr>
</thead>
<tbody v-if="dash.getOnDeck">
<tr v-for="(appt, index) in dash.getOnDeck" :key="appt.id"
class="odd:bg-gray-300 hover:bg-blue-300"
v-bind:class="{
'bg-amber-300 text-red-700 font-bold tracking-wider odd:bg-amber-300': appt.mark_due && appt.appt_status_id === 2,
'bg-red-600 odd:bg-red-800' : appt.appt_status_id === 4 || appt.appt_status_id === 6,
'bg-gray-300 odd:bg-gray-500': appt.appt_status_id === 9 || appt.appt_status_id === 3,
'text-amber-200 font-bold tracking-wide': appt.mark_due && appt.appt_status_id !== 2}" >
<td class="p-4">
<input class="h-6 w-6" type="checkbox" @click="dash.select(index)" :checked="dash.selected.indexOf(index) > -1">
</td>
<td class="flex p-4">
<a :href="'/appts/'+ appt.id +'/edit'" class="mr-2">
<svg class="h-6 w-6 stroke-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
</svg>
</a>
<a :href="'/appts/'+ appt.id" class="ml-2">
<svg class="h-6 w-6 text-blue-800" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" /> <circle cx="12" cy="12" r="3" />
</svg>
</a>
</td>
<td class="p-4">{{ moment(appt.date_time).format('h:k A') }}</td>
<td v-if="appt.patient" class="p-4">{{ appt.patient.nickname }}</td>
<td class="p-4">
<span v-if="!dash.viewFinished">{{ appt.appt_type ? appt.appt_type.appt_abbr : appt.appt_type_id }}</span>
<span v-else>{{ appt.appt_status ? appt.appt_status.status : appt.appt_status_id }}</span>
</td>
</tr>
</tbody>
<tbody v-else>
<tr class="bg-indigo-400">
<td colspan="5">No appointments {{ dash.viewFinished ? 'Checked Out' : 'On Deck' }}</td>
</tr>
</tbody>
</table>
<!-- <appt-modal :appt="selectedAppt"></appt-modal>-->
</template>
DocDashStore.js
import { defineStore } from "pinia"
import {useSwal} from "../composables/swal"
import moment from "moment"
import Toaster from "@meforma/vue-toaster"
import {isString} from "lodash";
export let useDocDashStore = defineStore('DocDashStore', {
state() {
return {
appts: [],
days: [],
viewFinished: false,
selected: [],
selectedAppt: {},
day: null,
apptModal: false,
apptModalType: null,
isLoading: true
}
},
actions: {
async loadDays() {
console.log("Init...");
this.isLoading = true
const res = await axios.get('/api/cal');
console.log(res.data.data)
this.days = res.data.data // Assuming the API returns the data directly
this.isLoading = false
},
async loadAppts(day) {
this.isLoading = true
const response = await axios.get('/api/dash/' + day);
console.log(response.data)
this.appts = response.data.data // Assuming the API returns the data directly
this.isLoading = false
},
select(index) {
this.selected.indexOf(index) > -1
? this.selected.splice(this.selected.indexOf(index), 1)
: this.selected.push(index);
console.log('selected', this.selected, 'appt',this.getOnDeck[index].patient_id);
},
isSelected() {
if(this.selected.length < 1) {
useSwal("","Select at least 1 appointment!",alert)
return false;
} else {
return true;
}
},
selectAll(){
this.selected = this.getOnDeck.map(function(a,index){ return index });
console.log('selected', this.selected);
},
loadApptModal(index = null) {
console.log('load appt modal');
// set the modal type, assign appt, clear errors
this.selectedAppt = this.appts[index];
this.apptModalType = 'update';
// show modal
this.apptModal = true;
},
bookNextAppt(date, hours) {
// check if selected
let hasPatients = this.isSelected()
// if patients selected, book the appts
if(hasPatients) {
this.selected.forEach((id) => {
let req = {
// patients are passed as an array on the admin side
patient_id: [this.getOnDeck[id].patient_id],
date_time: this.getOnDeck[id].date_time,
appt_time: this.getOnDeck[id].appt_time,
appt_date: new moment(date).format("YYYY-MM-DD"),
appt_type_id: this.getOnDeck[id].appt_type_id === 2 ? 2 : 4,
appt_reminder: 1,
appt_status_id: 2,
officehour_id: hours.id,
doctor_id: hours.doctor_id,
sendJson: true
};
if(!date) {
this.$toast.error('The office is CLOSED that day!');
}
// add the appointment to the db
axios.post('/api/appt', req).then(response => {
console.log(response.data);
// handle message responses
this.handleResponse(response)
}).catch(error => {
console.log(error);
this.handleError(error)
})
});
}
this.resetData();
},
updateApptStatus(status) {
// select appointment(s)
let $selected = this.isSelected();
// update each appt
if($selected) {
this.selected.forEach((id) => {
this.appt = this.getOnDeck[id];
let updated = {
appt_status_id: status,
appt_type_id: status === 2 ? 2 : this.appt.appt_type_id
};
// update the server
this.updateAppt(id, this.appt, updated);
});
this.resetData();
}
},
updateApptType(type) {
console.log('update appt type', type);
let $selected = this.isSelected();
if(type === 11) {
console.log('type is 11, open modal ', type);
console.log(this.selected);
let index = this.selected[0];
this.appt = this.getOnDeck[index];
console.log(index, this.appt);
this.loadApptModal(index);
} else {
console.log('type is not 11, mark clear', type);
if($selected) {
this.selected.forEach((id) => {
this.appt = this.getOnDeck[id];
let newData = {
appt_status_id: 9,
appt_type_id: type,
subjective: this.appt.subjective ? this.appt.subjective : this.addSubjective(),
assessment: 'clear upper cervical check, no adjustment needed today',
plan: 'recheck next visit'
};
this.sendBoxMessage(this.appt)
this.updateAppt(id, this.appt, newData)
});
this.resetData();
}
}
},
updateAppt(index, appt, updated) {
axios.patch('/api/appts/' + appt.id, updated)
.then(response => {
// find the index of the appointment
let pos = this.appts.findIndex(i => i.id === appt.id);
// remove the old one
this.appts.splice(pos, 1);
// push in the updated version
this.appts.push({...appt, ...updated});
// display success message
Toaster.info(response.data.message ? response.data.message : response.data.error);
// close the modal
$("#apptModal").modal("hide");
this.resetData();
})
.catch(error => {
Toaster.error(error);
})
},
sendMessage() {
let message = prompt('What message would you like to send?')
let hasPatients = this.isSelected()
// if patients selected, book the appts
if(hasPatients) {
let config = {
headers: {
'Accept': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
}
}
this.selected.forEach((id) => {
let req = {
appt: this.getOnDeck[id].id,
message: message
}
axios.post('/api/appt/message', req, config)
.then(response => {
console.log(response.data);
this.handleResponse(response)
})
.catch(error => {
console.log(error);
this.handleError(error)
});
})
}
this.resetData()
},
sendBoxMessage(appt) {
let config = {
headers: {
'Accept': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
}
}
let req = {
appt: appt.id
}
axios.post('/api/appt/boxMessage', req, config)
.then(response => {
console.log(response.data);
this.handleResponse(response)
})
.catch(error => {
console.log(error);
this.handleError(error)
});
},
resetData(index = null) {
console.log('Reset Data Index: ' + index);
// set the type of modal
this.modal = index === null ? 'create' : 'update';
// reset the appt to blank or assign selected
this.appt = index === null
? {} : this.appt = this.appts[index];
this.index = '';
this.selected = [];
},
paidInFull(payment) {
console.log('DocDash Paid In Full: payment', payment);
let pos = this.appts.findIndex(i => i.patient_id === payment.patient_id);
this.appt = this.appts[pos];
console.log(this.appt);
// update appt.due and mark_due fields
if(Array.isArray(this.appt.due)) {
// find the payment
let pmtIndex = this.appt.due.findIndex(i => i.id === payment.id);
// remove the payment from list of due
this.appt.due.splice(pmtIndex, 1);
} else {
this.appt.due = null;
this.appt.mark_due = false;
}
//update appts list
this.appts.splice(pos, 1);
this.appts.push(this.appt);
},
handleResponse(response) {
if(response.data.message && !response.data.messages) {
if(response.data.message.message) {
this.$toast.info(response.data.message.message);
} else {
this.$toast.info(response.data.message);
}
}
if(response.data.success) {
this.$toast.success(response.data.success);
}
if(response.data.error && !response.data.errors) {
this.$toast.error(response.data.error, { duration: false });
}
if(response.data.errors) {
console.log(response.data.errors);
response.data.errors.forEach((error) => {
this.$toast.error(error,{ duration: false });
});
}
if(response.data.messages) {
console.log(response.data.messages);
response.data.messages.forEach((msg) => {
this.$toast.success(msg, {duration: false})
})
}
if(isString(response.data)) {
this.$toast.info(response.data)
}
},
handleError(error) {
this.$toast.error(error, {duration: false});
}
},
// AKA computed properties
getters: {
countOnDeck() {
return this.viewFinished ?
this.appts.filter((appt) => appt.appt_status_id !== 2).length :
this.appts.filter((appt) => appt.appt_status_id === 2).length;
},
getOnDeck() {
return this.viewFinished ?
this.appts.filter((appt) => appt.appt_status_id !== 2) :
this.appts.filter((appt) => appt.appt_status_id === 2);
},
getFinished() {
return this.viewFinished && this.value < 1 ?
!this.viewFinished :
this.viewFinished;
},
allSelected() {
return this.selected.length === this.appts.length
}
}
})
App.vue
<template>
<router-view />
</template>
<script setup>
import { useDocDashStore } from "../stores/DocDashStore";
const dash = useDocDashStore()
await dash.loadDays()
</script>
app.js
import { createApp } from "vue";
import App from "./App.vue"
import router from "@/router";
import Toaster from "@meforma/vue-toaster";
import { createPinia } from 'pinia';
const app = createApp(App);
const pinia = createPinia();
app.use(router).use(pinia).use(Toaster).mount("#app");