| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305 |
- <template>
- <div>
- <DefaultHeaderPage />
- <div class="q-pa-md">
- <div class="row q-gutter-xs q-mb-md flex-wrap">
- <div
- v-for="tab in tabs"
- :key="tab.name"
- :class="['cat-chip', activeTab === tab.name ? 'cat-chip--selected' : 'cat-chip--default']"
- @click="activeTab = tab.name"
- >
- {{ tab.label }}
- </div>
- </div>
- <!-- Tab: Nova Solicitação -->
- <div v-if="activeTab === 'novo'" class="q-mt-md">
- <q-banner v-if="isOnLeave" rounded class="bg-orange-1 text-orange-9 q-mb-md">
- <template #avatar>
- <q-icon name="mdi-account-off-outline" color="orange" />
- </template>
- {{ $t('associado.on_leave_appointment') }}
- </q-banner>
- <q-card v-if="!isOnLeave" flat bordered>
- <q-card-section>
- <q-form ref="appointmentFormRef" @submit="submitAppointment">
- <div class="row q-col-gutter-sm">
- <PartnerAgreementSelect
- v-model="selectedPartner"
- :label="$t('ui.navigation.convenios')"
- :rules="[inputRules.required]"
- class="col-12 input-violet"
- for-associado
- />
- <PartnerAgreementServiceSelect
- v-model="selectedService"
- :partner-agreement-id="selectedPartner?.value"
- :label="$t('associado.service')"
- :rules="[inputRules.required]"
- class="col-12 input-violet"
- for-associado
- />
- <DefaultInput
- v-model="appointmentForm.observations"
- :label="$t('associado.notes')"
- type="textarea"
- autogrow
- class="col-12 input-violet"
- />
- </div>
- <div class="q-mt-md flex justify-end">
- <q-btn
- unelevated
- type="submit"
- class="btn-gradient"
- :label="editingId ? $t('common.actions.save') : $t('associado.schedule')"
- :loading="submitting"
- />
- </div>
- </q-form>
- </q-card-section>
- </q-card>
- </div>
- <!-- Tab: Meus Agendamentos -->
- <div v-else class="q-mt-md">
- <div v-if="loadingList" class="flex flex-center q-py-xl">
- <q-spinner color="violet-normal" size="48px" />
- </div>
- <div v-else-if="appointments.length === 0" class="text-center text-grey q-py-xl">
- {{ $t("http.errors.no_records_found") }}
- </div>
- <q-table
- v-else
- class="softpar-table q-pa-sm"
- :rows="pagedAppointments"
- :columns="columns"
- row-key="id"
- hide-pagination
- :rows-per-page-options="[0]"
- >
- <template #body-cell-status="props">
- <q-td :props="props">
- <q-chip
- outline
- :color="statusColor(props.row.status)"
- :label="$t(`agendamento.status.${props.row.status}`)"
- size="sm"
- />
- </q-td>
- </template>
- <template #body-cell-acoes="props">
- <q-td :props="props">
- <q-btn
- dense
- round
- flat
- icon="mdi-pencil"
- color="violet-normal"
- size="sm"
- :disable="props.row.status !== 'pendente'"
- @click="onEditAppointment(props.row)"
- />
- <q-btn
- dense
- round
- flat
- icon="mdi-calendar"
- color="violet-normal"
- size="sm"
- disable
- />
- </q-td>
- </template>
- </q-table>
- <div v-if="totalPages > 1" class="flex flex-center q-mt-lg">
- <q-pagination
- v-model="currentPage"
- :max="totalPages"
- boundary-numbers
- color="violet-normal"
- active-color="violet-normal"
- direction-links
- />
- </div>
- </div>
- </div>
- </div>
- </template>
- <script setup>
- import { ref, reactive, computed, watch, useTemplateRef } from "vue";
- import { useI18n } from "vue-i18n";
- import { createAppointment, getMyAppointments, updateAppointment } from "src/api/appointment";
- import { useInputRules } from "src/composables/useInputRules";
- import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
- import DefaultInput from "src/components/defaults/DefaultInput.vue";
- import PartnerAgreementSelect from "src/components/selects/PartnerAgreementSelect.vue";
- import PartnerAgreementServiceSelect from "src/components/selects/PartnerAgreementServiceSelect.vue";
- import { userStore } from "src/stores/user";
- const { t } = useI18n();
- const { inputRules } = useInputRules();
- const appointmentFormRef = useTemplateRef("appointmentFormRef");
- const user = userStore();
- const isOnLeave = computed(() => {
- const s = user.user?.status;
- return s === "on_leave" || s?.value === "on_leave";
- });
- const activeTab = ref("novo");
- const PER_PAGE = 12;
- const currentPage = ref(1);
- const tabs = computed(() => [
- { name: "novo", label: t("associado.new_appointment") },
- { name: "meus", label: t("associado.my_appointments") },
- ]);
- const columns = computed(() => [
- { name: "pedido", label: t("agendamento.col.pedido"), field: "order_number", align: "left" },
- { name: "parceiro", label: t("agendamento.col.parceiro"), field: (row) => row.partner_agreement?.trade_name || row.partner_agreement?.company_name || "—", align: "left" },
- { name: "servico", label: t("agendamento.col.servico"), field: (row) => row.partner_agreement_service?.name || "—", align: "left" },
- { name: "solicitacao", label: t("agendamento.col.solicitacao"), field: (row) => formatDate(row.created_at), align: "left" },
- { name: "horario", label: t("common.terms.hour2"), field: (row) => formatDateTime(row.date, row.time), align: "left" },
- { name: "acoes", label: t("common.terms.actions"), field: "id", align: "center" },
- { name: "status", label: t("common.terms.status"), field: "status", align: "center" },
- ]);
- const selectedPartner = ref(null);
- const selectedService = ref(null);
- const appointmentForm = reactive({ observations: "" });
- const submitting = ref(false);
- const editingId = ref(null);
- const appointments = ref([]);
- const loadingList = ref(false);
- const totalPages = computed(() => Math.max(1, Math.ceil(appointments.value.length / PER_PAGE)));
- const pagedAppointments = computed(() => {
- const start = (currentPage.value - 1) * PER_PAGE;
- return appointments.value.slice(start, start + PER_PAGE);
- });
- const statusColor = (status) => {
- const map = { pendente: "warning-light", confirmado: "positive", cancelado: "negative", recusado: "negative", concluido: "grey" };
- return map[status] ?? "grey";
- };
- const formatDate = (isoStr) => {
- if (!isoStr) return "—";
- const d = new Date(isoStr);
- if (isNaN(d)) return "—";
- return d.toLocaleDateString("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric" });
- };
- const formatDateTime = (date, time) => {
- if (!date) return "—";
- const d = new Date(date + "T00:00:00");
- if (isNaN(d)) return "—";
- const dateStr = d.toLocaleDateString("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric" });
- return time ? `${dateStr} ${time}` : dateStr;
- };
- const loadAppointments = async () => {
- loadingList.value = true;
- currentPage.value = 1;
- try {
- appointments.value = await getMyAppointments();
- } catch (e) {
- console.error(e);
- } finally {
- loadingList.value = false;
- }
- };
- watch(activeTab, (val) => {
- if (val === "meus") loadAppointments();
- });
- const resetForm = () => {
- selectedPartner.value = null;
- selectedService.value = null;
- appointmentForm.observations = "";
- editingId.value = null;
- };
- const submitAppointment = async () => {
- const valid = await appointmentFormRef.value?.validate();
- if (!valid) return;
- submitting.value = true;
- const payload = {
- partner_agreement_id: selectedPartner.value.value,
- partner_agreement_service_id: selectedService.value.value,
- observations: appointmentForm.observations,
- };
- try {
- if (editingId.value) {
- await updateAppointment(editingId.value, payload);
- } else {
- payload.user_id = user.user.id;
- await createAppointment(payload);
- }
- resetForm();
- activeTab.value = "meus";
- } catch {
- // silent
- } finally {
- submitting.value = false;
- }
- };
- const onEditAppointment = (apt) => {
- editingId.value = apt.id;
- selectedPartner.value = {
- value: apt.partner_agreement_id,
- label: apt.partner_agreement?.trade_name || apt.partner_agreement?.company_name || "",
- };
- selectedService.value = {
- value: apt.partner_agreement_service_id,
- label: apt.partner_agreement_service?.name || "",
- };
- appointmentForm.observations = apt.observations ?? "";
- activeTab.value = "novo";
- };
- </script>
- <style lang="scss">
- @import "src/css/table.scss";
- </style>
- <style scoped lang="scss">
- .cat-chip {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- height: 28px;
- padding: 0 12px;
- border-radius: 5px;
- font-size: 12px;
- font-weight: 500;
- cursor: pointer;
- user-select: none;
- transition: background 0.15s, color 0.15s;
- &--default { background: #c9a3dc; color: #fff; }
- &--selected { background: #4d1658; color: #fff; }
- }
- .btn-gradient {
- background: linear-gradient(90deg, #4d1658 0%, #8b30a5 100%) !important;
- color: white !important;
- border-radius: 8px !important;
- :deep(.q-icon) { color: white !important; }
- }
- </style>
|