felicity-lims/webapp/views/sample/SamplesAdd.vue

304 lines
11 KiB
Vue
Raw Normal View History

2023-11-10 14:05:15 +08:00
<script setup lang="ts">
import VueMultiselect from "vue-multiselect";
import { ref, reactive, computed, defineAsyncComponent } from "vue";
import { useRouter } from "vue-router";
import { usePatientStore } from "@/stores/patient";
import { useSampleStore } from "@/stores/sample";
import { useAnalysisStore } from "@/stores/analysis";
import { useClientStore } from "@/stores/client";
2023-11-10 14:05:15 +08:00
import {
IAnalysisProfile,
IAnalysisRequest,
IAnalysisService,
ISample,
ISampleType,
} from "@/models/analysis";
import { AddAnalysisRequestDocument, AddAnalysisRequestMutation, AddAnalysisRequestMutationVariables } from "@/graphql/operations/analyses.mutations";
2023-11-10 14:05:15 +08:00
import { useField, useForm } from "vee-validate";
import { object, string, array, number } from "yup";
import { IClient } from "@/models/client";
import useNotifyToast from "@/composables/alert_toast";
import useApiUtil from "@/composables/api_util";
2024-11-18 01:39:30 +08:00
import { formatDate } from "@/utils/helpers";
2023-11-10 14:05:15 +08:00
const LoadingMessage = defineAsyncComponent(
() => import("@/components/ui/spinners/FelLoadingMessage.vue")
2023-11-10 14:05:15 +08:00
)
const sampleStore = useSampleStore();
const patientStore = usePatientStore();
const analysisStore = useAnalysisStore();
const clientStore = useClientStore();
const router = useRouter();
const { withClientMutation } = useApiUtil();
const { swalError } = useNotifyToast();
// Patient
let patient = computed(() => patientStore.getPatient);
// Clients, Cient Contacts
let clientParams = reactive({
first: undefined,
after: "",
text: "",
sortBy: ["name"],
2024-11-18 01:39:30 +08:00
contacts: true
2023-11-10 14:05:15 +08:00
});
clientStore.fetchClients(clientParams);
const clients = computed(() => clientStore.getClients);
2024-11-18 01:39:30 +08:00
const selectedClient = ref<IClient | null>(null);
const selectContact = (client: IClient) => {
selectedClient.value = client;
};
2023-11-10 14:05:15 +08:00
// Sample Types
sampleStore.fetchSampleTypes();
const sampleTypes = computed(() => sampleStore.getSampleTypes);
// Analysis Services
let analysesParams = reactive({
first: undefined,
after: "",
text: "",
sortBy: ["name"],
});
analysisStore.fetchAnalysesServices(analysesParams);
const analysesServices = computed<IAnalysisService[]>(() => {
const services: IAnalysisService[] = analysisStore.getAnalysesServicesSimple;
let s = new Set<IAnalysisService>();
services.forEach((service: IAnalysisService, index: number) => {
if (service.profiles?.length === 0) {
s.add(service);
}
});
return [...s];
});
// Analysis Profiles
analysisStore.fetchAnalysesProfiles();
const analysesProfiles = computed(() => analysisStore.getAnalysesProfiles);
// Analysis Request Form
const arSaving = ref(false);
2024-11-18 01:39:30 +08:00
const maxDate = new Date();
2023-11-10 14:05:15 +08:00
const arSchema = object({
clientRequestId: string().required("Client Request ID is Required"),
clinicalData: string().nullable(),
client: object().required("Client is Required"),
clientContactUid: number().required("Client Contact is Required"),
samples: array().required().min(1, "Add at least 1 sample"),
priority: number(),
});
const { handleSubmit, errors } = useForm({
validationSchema: arSchema,
initialValues: {
priority: 0,
2024-02-21 19:41:44 +08:00
client: patient?.value?.client,
2023-11-10 14:05:15 +08:00
samples: [],
} as any,
});
const { value: clientRequestId } = useField("clientRequestId");
const { value: clinicalData } = useField<string>("clinicalData");
const { value: client } = useField<IClient>("client");
const { value: clientContactUid } = useField("clientContactUid");
const { value: priority } = useField("priority");
const { value: samples } = useField<ISample[]>("samples");
const submitARForm = handleSubmit((values) => {
arSaving.value = true;
for (let sample of values.samples || []) {
if (typeof sample?.sampleType !== "string") {
swalError("Samples must have sample types");
return;
}
if (sample?.analyses?.length <= 0 && sample?.profiles?.length <= 0) {
swalError("Samples must have either profiles/analyses or both");
return;
}
}
addAnalysesRequest(values as any);
});
// Analysis Request Add
function addAnalysesRequest(request: IAnalysisRequest): void {
const payload = {
patientUid: patient.value?.uid,
clientRequestId: request.clientRequestId,
clinicalData: request.clinicalData,
clientUid: client?.value?.uid,
clientContactUid: request.clientContactUid,
2024-11-18 01:39:30 +08:00
samples: request.samples?.map((s: ISample) => {
return {
...s,
dateCollected: formatDate(s.dateCollected, "YYYY-MM-DD HH:mm"),
};
}),
2023-11-10 14:05:15 +08:00
};
withClientMutation<AddAnalysisRequestMutation, AddAnalysisRequestMutationVariables>(AddAnalysisRequestDocument, { payload }, "createAnalysisRequest")
2023-11-10 14:05:15 +08:00
.then((result) => {
sampleStore.addAnalysisRequest(result);
router.push({ name: "patient-detail", params: { patientUid: patient.value?.uid } });
})
.finally(() => {
arSaving.value = false;
});
}
function addSample(): void {
const sample = {
sampleType: {} as ISampleType,
2024-01-24 01:13:37 +08:00
dateCollected: "",
2023-11-10 14:05:15 +08:00
profiles: [] as IAnalysisProfile[],
analyses: [] as IAnalysisService[],
} as ISample;
samples.value.push(sample);
}
function removeSample(index: number): void {
samples.value.splice(index, 1);
}
</script>
<template>
<div class="w-3/6 mt-4 py-4">
<h5 class="mb-4">Add Analysis Request</h5>
<form action="post" class="p-4 mb-8 bg-white" @submit.prevent="submitARForm">
<div class="">
<label class="flex whitespace-nowrap mb-2 w-full">
<span class="text-gray-700 w-4/12">Client Request ID</span>
<div class="w-full">
<input class="form-input mt-1 block w-full" v-model="clientRequestId" placeholder="CRID ..." />
<div class="text-orange-600 w-4/12">{{ errors.clientRequestId }}</div>
</div>
</label>
<label class="flex whitespace-nowrap mb-2 w-full">
<span class="text-gray-700 w-4/12">Clinical Data</span>
<div class="w-full">
<textarea cols="2" class="form-input mt-1 w-full" v-model="clinicalData" placeholder="Clinical Data ..." />
<div class="text-orange-600 w-4/12">{{ errors.clinicalData }}</div>
</div>
</label>
<label class="flex whitespace-nowrap mb-2 w-full">
<span class="text-gray-700 w-4/12">Client</span>
<div class="w-full">
<VueMultiselect placeholder="Select a Client" v-model="client" :options="clients" :searchable="true"
2024-11-18 01:39:30 +08:00
label="name" track-by="uid" @select="selectContact">
2023-11-10 14:05:15 +08:00
</VueMultiselect>
</div>
</label>
<label class="flex whitespace-nowrap mb-2 w-full">
<span class="text-gray-700 w-4/12">Client Contacts</span>
<div class="w-full">
<select name="clientContacts" id="clientContacts" v-model="clientContactUid"
class="form-input mt-1 block w-full">
<option value=""></option>
2024-11-18 01:39:30 +08:00
<option v-for="contact in client?.contacts" :key="contact.uid" :value="contact.uid">
2023-11-10 14:05:15 +08:00
{{ contact.firstName }} {{ contact.lastName }}
</option>
</select>
<div class="text-orange-600 w-4/12">{{ errors.clientContactUid }}</div>
</div>
</label>
<label class="flex whitespace-nowrap mb-2 w-full">
<span class="text-gray-700 w-4/12">Priority</span>
<div class="w-full">
2024-11-18 01:39:30 +08:00
<select name="clientContacts" id="clientContacts" v-model="priority"
class="form-input mt-1 block w-full">
<option value=0>Low</option>
<option value=1>Medium</option>
<option value=2>High</option>
</select>
2023-11-10 14:05:15 +08:00
<div class="text-orange-600 w-4/12">{{ errors.priority }}</div>
</div>
</label>
</div>
<section id="samples">
<hr />
<div class="flex justify-between items-center py-2">
<h5>Samples</h5>
<span class="text-orange-600">{{ errors.samples }}</span>
<button v-if="samples?.length !== 20" @click.prevent="addSample()"
class="px-2 py-1 mr-2 border-sky-800 border text-sky-800 rounded-sm transition duration-300 hover:bg-sky-800 hover:text-white focus:outline-none">
2023-11-10 14:05:15 +08:00
Add Sample
</button>
</div>
<hr class="mb-4" />
<div v-for="(sample, index) in samples" :key="index">
<div class="flex items-center justify-between">
<div class="flex items-top gap-x-4">
<label class="flex flex-col whitespace-nowrap mb-2">
<span class="text-gray-700">Sample Type</span>
<select name="sampleTypes" id="sampleTypes" v-model="sample.sampleType" class="form-input mt-1">
<option value=""></option>
<option v-for="sampleType in sampleTypes" :key="sampleType.uid" :value="sampleType.uid">
{{ sampleType.name }}
</option>
</select>
</label>
2024-01-24 01:13:37 +08:00
<label class="flex flex-col whitespace-nowrap mb-2">
<span class="text-gray-700">Date Collected</span>
2024-11-18 01:39:30 +08:00
<VueDatePicker
class="z-60 disabled:bg-slate-200"
v-model="sample.dateCollected"
:max-date="maxDate" time-picker-inline></VueDatePicker>
2024-01-24 01:13:37 +08:00
</label>
2023-11-10 14:05:15 +08:00
<label class="flex flex-col whitespace-nowrap mb-2">
<span class="text-gray-700">Analysis Profiles</span>
<select name="analysisProfiles" id="analysisProfiles" v-model="sample.profiles" class="form-input mt-1"
multiple>
<option value=""></option>
2024-11-18 01:39:30 +08:00
<option v-for="profile in analysesProfiles" :key="profile.uid" :value="profile.uid">
2023-11-10 14:05:15 +08:00
{{ profile.name }}
</option>
</select>
</label>
<label class="flex flex-col whitespace-nowrap mb-2">
<span class="text-gray-700">Analysis Services</span>
<select name="analysesServices" id="analysesServices" v-model="sample.analyses" class="form-input mt-1"
multiple>
<option value=""></option>
2024-11-18 01:39:30 +08:00
<option v-for="service in analysesServices" :key="service.uid" :value="service.uid">
2023-11-10 14:05:15 +08:00
{{ service.name }}
</option>
</select>
</label>
</div>
<div class="">
<button @click.prevent="removeSample(index)"
class="px-2 py-1 mr-2 border-orange-600 border text-orange-600rounded-smtransition duration-300 hover:bg-orange-600 hover:text-white focus:outline-none">
Remove
</button>
</div>
</div>
<hr />
</div>
</section>
<hr />
<button v-if="!arSaving" type="submit"
class="-mb-4 w-full border border-sky-800 bg-sky-800 text-white rounded-sm px-4 py-2 m-2 transition-colors duration-500 ease select-none hover:bg-sky-800 focus:outline-none focus:shadow-outline">
Add Sample(s)
</button>
<div v-else class="py-4 text-center">
<LoadingMessage message="Adding Samples ..." />
</div>
</form>
</div>
</template>
<style src="vue-multiselect/dist/vue-multiselect.css"></style>