|
|
@@ -21,6 +21,22 @@
|
|
|
<q-card-section>
|
|
|
<q-form ref="formRef" @submit="submitAppointment">
|
|
|
<div class="row q-col-gutter-sm">
|
|
|
+ <div class="col-12">
|
|
|
+ <div class="q-pl-xs q-mb-sm field-label">{{ $t("agendamento.tipo") }}</div>
|
|
|
+ <q-btn-toggle
|
|
|
+ v-model="form.type"
|
|
|
+ no-caps
|
|
|
+ unelevated
|
|
|
+ toggle-color="primary"
|
|
|
+ color="white"
|
|
|
+ text-color="primary"
|
|
|
+ :options="[
|
|
|
+ { label: $t('agendamento.tipo_consulta'), value: 'consulta' },
|
|
|
+ { label: $t('agendamento.tipo_exame'), value: 'exame' },
|
|
|
+ ]"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
<AssociadoSelect
|
|
|
v-model="form.associado"
|
|
|
:label="$t('agendamento.associado')"
|
|
|
@@ -48,29 +64,64 @@
|
|
|
:rules="[inputRules.required]"
|
|
|
class="col-12 input-violet"
|
|
|
/>
|
|
|
- <PartnerAgreementServiceSelect
|
|
|
- v-model="form.service"
|
|
|
- :partner-agreement-id="form.partner?.value"
|
|
|
- :label="$t('associado.service')"
|
|
|
- :rules="[inputRules.required]"
|
|
|
- class="col-12 input-violet"
|
|
|
- />
|
|
|
- <DefaultInputDatePicker
|
|
|
- v-model:untreated-date="form.date"
|
|
|
- :label="$t('common.terms.date')"
|
|
|
- :rules="[inputRules.required]"
|
|
|
- placeholder="dd/mm/aaaa"
|
|
|
- lazy-rules
|
|
|
- class="col-12 col-md-6"
|
|
|
- />
|
|
|
- <DefaultInput
|
|
|
- v-model="form.time"
|
|
|
- :label="$t('common.terms.hour2')"
|
|
|
- :rules="[inputRules.required]"
|
|
|
- mask="##:##"
|
|
|
- placeholder="HH:MM"
|
|
|
- class="col-12 col-md-6"
|
|
|
- />
|
|
|
+ <template v-if="isConsulta">
|
|
|
+ <PartnerAgreementServiceSelect
|
|
|
+ v-model="form.service"
|
|
|
+ :partner-agreement-id="form.partner?.value"
|
|
|
+ type="consulta"
|
|
|
+ :label="$t('associado.service')"
|
|
|
+ :rules="[inputRules.required]"
|
|
|
+ class="col-12 input-violet"
|
|
|
+ />
|
|
|
+
|
|
|
+ <div v-if="form.partner" class="col-12">
|
|
|
+ <ClinicScheduleNotice :partner="form.partner.data" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <DefaultInputDatePicker
|
|
|
+ v-model:untreated-date="form.date"
|
|
|
+ :label="$t('common.terms.date')"
|
|
|
+ :rules="[inputRules.required, inputRules.exactAdvanceDays(ADVANCE_DAYS)]"
|
|
|
+ :hint="$t('agendamento.data_disponivel', { date: allowedDateLabel })"
|
|
|
+ :date-options="allowedDateOptions"
|
|
|
+ placeholder="dd/mm/aaaa"
|
|
|
+ lazy-rules
|
|
|
+ class="col-12 col-md-6"
|
|
|
+ />
|
|
|
+ <DefaultInput
|
|
|
+ v-model="form.time"
|
|
|
+ :label="$t('common.terms.hour2')"
|
|
|
+ :rules="[inputRules.required]"
|
|
|
+ mask="##:##"
|
|
|
+ placeholder="HH:MM"
|
|
|
+ class="col-12 col-md-6"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template v-else>
|
|
|
+ <PartnerAgreementExamsSelect
|
|
|
+ v-model="form.exams"
|
|
|
+ :partner-agreement-id="form.partner?.value"
|
|
|
+ :label="$t('agendamento.exames')"
|
|
|
+ :rules="[inputRules.required]"
|
|
|
+ class="col-12 input-violet"
|
|
|
+ />
|
|
|
+
|
|
|
+ <DefaultInputDatePicker
|
|
|
+ v-model:untreated-date="form.date"
|
|
|
+ :label="$t('agendamento.data_opcional')"
|
|
|
+ :date-options="allowedDateOptions"
|
|
|
+ placeholder="dd/mm/aaaa"
|
|
|
+ class="col-12 col-md-6"
|
|
|
+ />
|
|
|
+ <DefaultInput
|
|
|
+ v-model="form.time"
|
|
|
+ :label="$t('agendamento.hora_opcional')"
|
|
|
+ mask="##:##"
|
|
|
+ placeholder="HH:MM"
|
|
|
+ class="col-12 col-md-6"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
<DefaultInput
|
|
|
v-model="form.observations"
|
|
|
:label="$t('associado.notes')"
|
|
|
@@ -79,11 +130,18 @@
|
|
|
class="col-12"
|
|
|
/>
|
|
|
</div>
|
|
|
+ <q-banner v-if="!isConsulta" dense rounded class="q-mt-md acceptance-hint">
|
|
|
+ <template #avatar>
|
|
|
+ <q-icon name="mdi-information-outline" color="primary" />
|
|
|
+ </template>
|
|
|
+ {{ $t("agendamento.aviso_aceite_convenio") }}
|
|
|
+ </q-banner>
|
|
|
+
|
|
|
<div class="q-mt-md flex justify-end">
|
|
|
<q-btn
|
|
|
color="primary"
|
|
|
type="submit"
|
|
|
- :label="$t('agendamento.solicitar')"
|
|
|
+ :label="isConsulta ? $t('agendamento.solicitar') : $t('agendamento.gerar_guia_exames')"
|
|
|
:loading="submitting"
|
|
|
/>
|
|
|
</div>
|
|
|
@@ -133,6 +191,16 @@
|
|
|
:add-item="false"
|
|
|
:show-search-field="true"
|
|
|
>
|
|
|
+ <template #body-cell-type="{ row }">
|
|
|
+ <q-td class="text-center">
|
|
|
+ <q-chip
|
|
|
+ outline
|
|
|
+ size="sm"
|
|
|
+ :color="row.type === 'exame' ? 'info' : 'primary'"
|
|
|
+ :label="$t(`agendamento.tipo_${row.type}`)"
|
|
|
+ />
|
|
|
+ </q-td>
|
|
|
+ </template>
|
|
|
<template #body-cell-service_name="{ row }">
|
|
|
<q-td>
|
|
|
{{ excerpt(row.service_name || '—', 35) }}
|
|
|
@@ -146,7 +214,11 @@
|
|
|
:color="statusColor(row.status)"
|
|
|
:label="$t(`agendamento.status.${row.status}`)"
|
|
|
size="sm"
|
|
|
- />
|
|
|
+ >
|
|
|
+ <q-tooltip v-if="row.refusal_reason">
|
|
|
+ {{ $t("agendamento.motivo_recusa") }}: {{ row.refusal_reason }}
|
|
|
+ </q-tooltip>
|
|
|
+ </q-chip>
|
|
|
</q-td>
|
|
|
</template>
|
|
|
<template #body-cell-dependent_name="{ row }">
|
|
|
@@ -163,6 +235,7 @@
|
|
|
size="sm"
|
|
|
:unelevated="row.status === 'confirmado'"
|
|
|
:outline="row.status !== 'confirmado'"
|
|
|
+ :disable="!canModerate(row)"
|
|
|
:loading="actionId === row.id && actionType === 'approve'"
|
|
|
@click.prevent.stop="onApprove(row)"
|
|
|
/>
|
|
|
@@ -174,6 +247,7 @@
|
|
|
size="sm"
|
|
|
:unelevated="row.status === 'recusado' || row.status === 'cancelado'"
|
|
|
:outline="row.status !== 'recusado' && row.status !== 'cancelado'"
|
|
|
+ :disable="!canModerate(row)"
|
|
|
:loading="actionId === row.id && actionType === 'reject'"
|
|
|
@click.prevent.stop="onReject(row)"
|
|
|
/>
|
|
|
@@ -204,6 +278,16 @@
|
|
|
:add-item="false"
|
|
|
:show-search-field="true"
|
|
|
>
|
|
|
+ <template #body-cell-type="{ row }">
|
|
|
+ <q-td class="text-center">
|
|
|
+ <q-chip
|
|
|
+ outline
|
|
|
+ size="sm"
|
|
|
+ :color="row.type === 'exame' ? 'info' : 'primary'"
|
|
|
+ :label="$t(`agendamento.tipo_${row.type}`)"
|
|
|
+ />
|
|
|
+ </q-td>
|
|
|
+ </template>
|
|
|
<template #body-cell-service_name="{ row }">
|
|
|
<q-td>
|
|
|
{{ excerpt(row.service_name || '—', 35) }}
|
|
|
@@ -250,7 +334,7 @@ import { ref, computed, onMounted, useTemplateRef, nextTick, watch } from "vue";
|
|
|
import { useQuasar } from "quasar";
|
|
|
import { useI18n } from "vue-i18n";
|
|
|
import { useInputRules } from "src/composables/useInputRules";
|
|
|
-import { excerpt } from "src/helpers/utils";
|
|
|
+import { excerpt, addDaysToToday, formatDateBR } from "src/helpers/utils";
|
|
|
|
|
|
import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
|
|
|
import DefaultTableServerSide from "src/components/defaults/DefaultTableServerSide.vue";
|
|
|
@@ -260,12 +344,15 @@ import AssociadoSelect from "src/components/selects/AssociadoSelect.vue";
|
|
|
import DependenteSelect from "src/components/selects/DependenteSelect.vue";
|
|
|
import PartnerAgreementSelect from "src/components/selects/PartnerAgreementSelect.vue";
|
|
|
import PartnerAgreementServiceSelect from "src/components/selects/PartnerAgreementServiceSelect.vue";
|
|
|
+import PartnerAgreementExamsSelect from "src/components/selects/PartnerAgreementExamsSelect.vue";
|
|
|
import ApproveAppointmentDialog from "src/components/ApproveAppointmentDialog.vue";
|
|
|
+import ClinicScheduleNotice from "src/components/ClinicScheduleNotice.vue";
|
|
|
|
|
|
import {
|
|
|
getAdminCounters,
|
|
|
getAdminAppointmentsPaginated,
|
|
|
createAppointment,
|
|
|
+ createAdminExam,
|
|
|
approveAppointment,
|
|
|
rejectAppointment,
|
|
|
downloadAppointmentGuide,
|
|
|
@@ -288,17 +375,32 @@ const counters = ref({
|
|
|
recusados: undefined,
|
|
|
});
|
|
|
|
|
|
+const ADVANCE_DAYS = 2;
|
|
|
+
|
|
|
const form = ref({
|
|
|
+ type: "consulta",
|
|
|
associado: null,
|
|
|
forDependent: false,
|
|
|
dependent: null,
|
|
|
partner: null,
|
|
|
service: null,
|
|
|
+ exams: [],
|
|
|
date: "",
|
|
|
time: "",
|
|
|
observations: "",
|
|
|
});
|
|
|
|
|
|
+const isConsulta = computed(() => form.value.type === "consulta");
|
|
|
+
|
|
|
+const allowedDateLabel = computed(() => formatDateBR(addDaysToToday(ADVANCE_DAYS)));
|
|
|
+
|
|
|
+const allowedDateOptions = computed(() => {
|
|
|
+ const date = addDaysToToday(ADVANCE_DAYS);
|
|
|
+ const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
|
+ const day = String(date.getDate()).padStart(2, "0");
|
|
|
+ return [`${date.getFullYear()}/${month}/${day}`];
|
|
|
+});
|
|
|
+
|
|
|
watch(
|
|
|
() => form.value.forDependent,
|
|
|
(forDependent) => {
|
|
|
@@ -306,6 +408,16 @@ watch(
|
|
|
},
|
|
|
);
|
|
|
|
|
|
+watch(
|
|
|
+ () => form.value.type,
|
|
|
+ () => {
|
|
|
+ form.value.service = null;
|
|
|
+ form.value.exams = [];
|
|
|
+ form.value.date = "";
|
|
|
+ form.value.time = "";
|
|
|
+ },
|
|
|
+);
|
|
|
+
|
|
|
watch(
|
|
|
() => form.value.associado,
|
|
|
() => {
|
|
|
@@ -337,6 +449,7 @@ const tabs = computed(() => [
|
|
|
|
|
|
const columnsVisaoGeral = computed(() => [
|
|
|
{ name: "order_number", label: t("agendamento.col.pedido"), field: "order_number", align: "left" },
|
|
|
+ { name: "type", label: t("agendamento.tipo"), field: "type", align: "center" },
|
|
|
{ name: "cracha", label: t("associado.cracha"), field: "registration", align: "left" },
|
|
|
{ name: "user_name", label: t("common.terms.name"), field: "user_name", align: "left" },
|
|
|
{ name: "dependent_name", label: t("associado.dependent"), field: "dependent_name", align: "left" },
|
|
|
@@ -349,6 +462,7 @@ const columnsVisaoGeral = computed(() => [
|
|
|
|
|
|
const columnsAprovados = computed(() => [
|
|
|
{ name: "order_number", label: t("agendamento.col.pedido"), field: "order_number", align: "left" },
|
|
|
+ { name: "type", label: t("agendamento.tipo"), field: "type", align: "center" },
|
|
|
{ name: "user_name", label: t("common.terms.name"), field: "user_name", align: "left" },
|
|
|
{ name: "dependent_name", label: t("associado.dependent"), field: "dependent_name", align: "left" },
|
|
|
{ name: "partner_name", label: t("agendamento.col.parceiro"), field: "partner_name", align: "left" },
|
|
|
@@ -361,6 +475,7 @@ const columnsAprovados = computed(() => [
|
|
|
const statusColor = (status) => {
|
|
|
const map = {
|
|
|
pendente: "warning-light",
|
|
|
+ aguardando_aceite: "warning-light",
|
|
|
confirmado: "positive",
|
|
|
recusado: "negative",
|
|
|
cancelado: "grey-6",
|
|
|
@@ -369,6 +484,8 @@ const statusColor = (status) => {
|
|
|
return map[status] ?? "grey-6";
|
|
|
};
|
|
|
|
|
|
+const canModerate = (row) => row.status === "pendente";
|
|
|
+
|
|
|
const apiFetchAll = (params) => getAdminAppointmentsPaginated(params);
|
|
|
const apiFetchAprovados = (params) =>
|
|
|
getAdminAppointmentsPaginated({ ...params, status: "confirmado" });
|
|
|
@@ -388,11 +505,13 @@ const loadCounters = async () => {
|
|
|
|
|
|
const resetForm = async () => {
|
|
|
form.value = {
|
|
|
+ type: form.value.type,
|
|
|
associado: null,
|
|
|
forDependent: false,
|
|
|
dependent: null,
|
|
|
partner: null,
|
|
|
service: null,
|
|
|
+ exams: [],
|
|
|
date: "",
|
|
|
time: "",
|
|
|
observations: "",
|
|
|
@@ -404,21 +523,33 @@ const resetForm = async () => {
|
|
|
const submitAppointment = async () => {
|
|
|
const valid = await formRef.value?.validate();
|
|
|
if (!valid) return;
|
|
|
+
|
|
|
+ const base = {
|
|
|
+ user_id: form.value.associado.value,
|
|
|
+ user_dependent_id: form.value.dependent?.value ?? null,
|
|
|
+ partner_agreement_id: form.value.partner.value,
|
|
|
+ date: form.value.date || null,
|
|
|
+ time: form.value.time || null,
|
|
|
+ observations: form.value.observations || null,
|
|
|
+ };
|
|
|
+
|
|
|
submitting.value = true;
|
|
|
try {
|
|
|
- await createAppointment({
|
|
|
- user_id: form.value.associado.value,
|
|
|
- user_dependent_id: form.value.dependent?.value ?? null,
|
|
|
- partner_agreement_id: form.value.partner.value,
|
|
|
- partner_agreement_service_id: form.value.service.value,
|
|
|
- time: form.value.time,
|
|
|
- date: form.value.date,
|
|
|
- observations: form.value.observations || null,
|
|
|
- });
|
|
|
+ if (isConsulta.value) {
|
|
|
+ await createAppointment({
|
|
|
+ ...base,
|
|
|
+ partner_agreement_service_id: form.value.service.value,
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ await createAdminExam({
|
|
|
+ ...base,
|
|
|
+ service_ids: form.value.exams.map((e) => e.value),
|
|
|
+ });
|
|
|
+ }
|
|
|
await resetForm();
|
|
|
await loadCounters();
|
|
|
} catch {
|
|
|
- // silent
|
|
|
+ // erro já notificado pelo interceptor
|
|
|
} finally {
|
|
|
submitting.value = false;
|
|
|
}
|
|
|
@@ -516,6 +647,16 @@ onMounted(() => {
|
|
|
font-weight: 700;
|
|
|
}
|
|
|
|
|
|
+.field-label {
|
|
|
+ font-size: 0.9rem;
|
|
|
+}
|
|
|
+
|
|
|
+.acceptance-hint {
|
|
|
+ border: 1px solid vars.$color-border;
|
|
|
+ background: vars.$surface;
|
|
|
+ font-size: 0.85rem;
|
|
|
+}
|
|
|
+
|
|
|
.counters-row {
|
|
|
display: flex;
|
|
|
gap: 12px;
|