|
|
@@ -0,0 +1,778 @@
|
|
|
+<template>
|
|
|
+ <div class="cadastro-page q-mr-md">
|
|
|
+ <DefaultHeaderPage :title="{ value: pageTitle, translate: false }" />
|
|
|
+
|
|
|
+ <div class="cadastro-page__tabs q-mb-md">
|
|
|
+ <div
|
|
|
+ v-for="tab in tabsItems"
|
|
|
+ :key="tab.name"
|
|
|
+ class="cadastro-page__tab"
|
|
|
+ :class="{
|
|
|
+ 'cadastro-page__tab--active': activeTab === tab.name,
|
|
|
+ 'cadastro-page__tab--disabled': tab.disable,
|
|
|
+ }"
|
|
|
+ @click="!tab.disable && (activeTab = tab.name)"
|
|
|
+ >
|
|
|
+ {{ tab.label }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <q-tab-panels v-model="activeTab" animated keep-alive>
|
|
|
+
|
|
|
+ <q-tab-panel name="dados" class="q-pa-none">
|
|
|
+ <q-form ref="formDadosRef" @submit="saveDados">
|
|
|
+ <div class="q-pr-md q-pb-md bg-violet-light">
|
|
|
+ <DefaultFilePicker
|
|
|
+ v-model="logoFile"
|
|
|
+ type="image"
|
|
|
+ :label="$t('parceiro.logo')"
|
|
|
+ :initial-image="partner?.logo?.url"
|
|
|
+ style="height: 220px; max-width: 280px"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <q-card flat class="cadastro-card q-pa-md">
|
|
|
+ <div class="row q-col-gutter-sm">
|
|
|
+ <DefaultInput
|
|
|
+ v-model="formDados.company_name"
|
|
|
+ v-model:error="errorsDados.company_name"
|
|
|
+ :rules="[inputRules.required]"
|
|
|
+ :label="$t('parceiro.company_name')"
|
|
|
+ class="col-12 input-violet"
|
|
|
+ />
|
|
|
+ <DefaultInput
|
|
|
+ v-model="formDados.cnpj"
|
|
|
+ v-model:error="errorsDados.cnpj"
|
|
|
+ :label="$t('common.terms.cnpj')"
|
|
|
+ :placeholder="'00.000.000/0000-00'"
|
|
|
+ :mask="'##.###.###/####-##'"
|
|
|
+ class="col-12 input-violet"
|
|
|
+ />
|
|
|
+ <PartnerAgreementCategorySelect
|
|
|
+ v-model="selectedCategory"
|
|
|
+ v-model:error="errorsDados.category_id"
|
|
|
+ class="col-12 input-violet"
|
|
|
+ />
|
|
|
+ <DefaultInput
|
|
|
+ v-model="formDados.responsible"
|
|
|
+ v-model:error="errorsDados.responsible"
|
|
|
+ :label="$t('parceiro.responsible')"
|
|
|
+ class="col-12 input-violet"
|
|
|
+ />
|
|
|
+ <DefaultInput
|
|
|
+ v-model="formDados.discount_percentage"
|
|
|
+ v-model:error="errorsDados.discount_percentage"
|
|
|
+ :label="$t('parceiro.discount_percentage')"
|
|
|
+ type="number"
|
|
|
+ suffix="%"
|
|
|
+ class="col-12 input-violet"
|
|
|
+ />
|
|
|
+ <DefaultInput
|
|
|
+ v-model="formDados.description"
|
|
|
+ v-model:error="errorsDados.description"
|
|
|
+ :label="$t('common.terms.description')"
|
|
|
+ type="textarea"
|
|
|
+ autogrow
|
|
|
+ class="col-12 input-violet"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </q-card>
|
|
|
+
|
|
|
+ <div class="row justify-end q-py-md bg-violet-light">
|
|
|
+ <q-btn
|
|
|
+ unelevated
|
|
|
+ class="btn-gradient"
|
|
|
+ :label="$t('common.actions.save')"
|
|
|
+ icon="mdi-check"
|
|
|
+ type="submit"
|
|
|
+ :loading="loadingDados"
|
|
|
+ :disable="!hasUpdatedDados"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </q-form>
|
|
|
+ </q-tab-panel>
|
|
|
+
|
|
|
+ <q-tab-panel name="contato" class="q-pa-none">
|
|
|
+ <q-form ref="formContatoRef" @submit="saveContato">
|
|
|
+ <q-card flat class="cadastro-card q-pa-md">
|
|
|
+ <div class="row q-col-gutter-sm">
|
|
|
+ <DefaultInput
|
|
|
+ v-model="formContato.email"
|
|
|
+ v-model:error="errorsContato.email"
|
|
|
+ :rules="[inputRules.email]"
|
|
|
+ :label="$t('common.terms.email')"
|
|
|
+ class="col-12 input-violet"
|
|
|
+ />
|
|
|
+ <DefaultInput
|
|
|
+ v-model="formContato.website"
|
|
|
+ v-model:error="errorsContato.website"
|
|
|
+ :label="$t('parceiro.website')"
|
|
|
+ class="col-12 input-violet"
|
|
|
+ />
|
|
|
+ <DefaultInput
|
|
|
+ v-model="formContato.phone"
|
|
|
+ v-model:error="errorsContato.phone"
|
|
|
+ :label="$t('common.terms.phone')"
|
|
|
+ :mask="'(##) #####-####'"
|
|
|
+ class="col-12 input-violet"
|
|
|
+ />
|
|
|
+ <DefaultInput
|
|
|
+ v-model="formContato.whatsapp"
|
|
|
+ v-model:error="errorsContato.whatsapp"
|
|
|
+ :label="$t('common.terms.whatsapp')"
|
|
|
+ :mask="'(##) #####-####'"
|
|
|
+ class="col-12 input-violet"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </q-card>
|
|
|
+
|
|
|
+ <div class="row justify-end bg-violet-light q-pt-md">
|
|
|
+ <q-btn
|
|
|
+ unelevated
|
|
|
+ class="btn-gradient"
|
|
|
+ :label="$t('common.actions.save')"
|
|
|
+ icon="mdi-check"
|
|
|
+ type="submit"
|
|
|
+ :loading="loadingContato"
|
|
|
+ :disable="!hasUpdatedContato"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </q-form>
|
|
|
+ </q-tab-panel>
|
|
|
+
|
|
|
+ <q-tab-panel name="endereco" class="q-pa-none">
|
|
|
+ <q-form ref="formEnderecoRef" @submit="saveEndereco">
|
|
|
+ <q-card flat class="cadastro-card q-pa-md">
|
|
|
+ <div class="row q-col-gutter-sm">
|
|
|
+ <DefaultInput
|
|
|
+ v-model="formEndereco.zip_code"
|
|
|
+ v-model:error="errorsEndereco.zip_code"
|
|
|
+ :label="$t('parceiro.zip_code')"
|
|
|
+ :placeholder="'00000-000'"
|
|
|
+ :mask="'#####-###'"
|
|
|
+ class="col-md-4 col-12 input-violet"
|
|
|
+ @update:model-value="onCepChange"
|
|
|
+ />
|
|
|
+ <DefaultInput
|
|
|
+ v-model="formEndereco.address"
|
|
|
+ v-model:error="errorsEndereco.address"
|
|
|
+ :label="$t('parceiro.address')"
|
|
|
+ :loading="loadingCep"
|
|
|
+ class="col-md-8 col-12 input-violet"
|
|
|
+ />
|
|
|
+ <DefaultInput
|
|
|
+ v-model="formEndereco.neighborhood"
|
|
|
+ v-model:error="errorsEndereco.neighborhood"
|
|
|
+ :label="$t('parceiro.neighborhood')"
|
|
|
+ :loading="loadingCep"
|
|
|
+ class="col-12 input-violet"
|
|
|
+ />
|
|
|
+ <StateSelect
|
|
|
+ v-model="selectedState"
|
|
|
+ v-model:error="errorsEndereco.state_id"
|
|
|
+ class="col-md-4 col-12 input-violet"
|
|
|
+ />
|
|
|
+ <CitySelect
|
|
|
+ v-model="selectedCity"
|
|
|
+ v-model:error="errorsEndereco.city_id"
|
|
|
+ :state="selectedState"
|
|
|
+ class="col-md-8 col-12 input-violet"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </q-card>
|
|
|
+
|
|
|
+ <div class="row justify-end q-pt-md bg-violet-light">
|
|
|
+ <q-btn
|
|
|
+ unelevated
|
|
|
+ class="btn-gradient"
|
|
|
+ :label="$t('common.actions.save')"
|
|
|
+ icon="mdi-check"
|
|
|
+ type="submit"
|
|
|
+ :loading="loadingEndereco"
|
|
|
+ :disable="!hasUpdatedEndereco"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </q-form>
|
|
|
+ </q-tab-panel>
|
|
|
+
|
|
|
+ <q-tab-panel name="horario" class="q-pa-none">
|
|
|
+ <q-form ref="formHorarioRef" @submit="saveHorario">
|
|
|
+ <q-card flat class="cadastro-card q-pa-md">
|
|
|
+ <div class="row q-col-gutter-sm">
|
|
|
+ <DefaultInput
|
|
|
+ v-model="formHorario.working_hours"
|
|
|
+ v-model:error="errorsHorario.working_hours"
|
|
|
+ :label="$t('parceiro.working_hours')"
|
|
|
+ :placeholder="$t('parceiro.working_hours_placeholder')"
|
|
|
+ class="col-12 input-violet"
|
|
|
+ />
|
|
|
+ <DefaultInputDatePicker
|
|
|
+ v-model:untreated-date="formHorario.contract_start"
|
|
|
+ v-model:error="errorsHorario.contract_start"
|
|
|
+ :label="$t('parceiro.contract_start')"
|
|
|
+ class="col-md-6 col-12 input-violet"
|
|
|
+ />
|
|
|
+ <DefaultInputDatePicker
|
|
|
+ v-model:untreated-date="formHorario.contract_end"
|
|
|
+ v-model:error="errorsHorario.contract_end"
|
|
|
+ :label="$t('parceiro.contract_end')"
|
|
|
+ class="col-md-6 col-12 input-violet"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div v-if="contractMedia.length > 0" class="q-mt-md">
|
|
|
+ <div class="text-subtitle2 text-violet-normal q-mb-xs">{{ $t("parceiro.contract_files") }}</div>
|
|
|
+ <div class="row q-col-gutter-xs">
|
|
|
+ <div v-for="media in contractMedia" :key="media.id" class="col-auto">
|
|
|
+ <q-chip
|
|
|
+ removable
|
|
|
+ clickable
|
|
|
+ color="violet-light"
|
|
|
+ text-color="violet-normal"
|
|
|
+ icon="mdi-file-outline"
|
|
|
+ :label="media.name"
|
|
|
+ @click="openMedia(media)"
|
|
|
+ @remove="removeMedia(media)"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </q-card>
|
|
|
+
|
|
|
+ <div class="row justify-end items-center q-pt-md bg-violet-light" style="gap: 8px">
|
|
|
+ <q-file
|
|
|
+ ref="mediaFileInputRef"
|
|
|
+ v-model="newMediaFile"
|
|
|
+ accept=".pdf,.doc,.docx,.png,.jpg,.jpeg"
|
|
|
+ style="display: none"
|
|
|
+ @update:model-value="onMediaFileSelected"
|
|
|
+ />
|
|
|
+ <q-btn
|
|
|
+ unelevated
|
|
|
+ class="btn-gradient"
|
|
|
+ :label="$t('parceiro.add_file')"
|
|
|
+ icon="mdi-paperclip"
|
|
|
+ :disable="!partnerId"
|
|
|
+ @click="triggerMediaUpload"
|
|
|
+ />
|
|
|
+ <q-btn
|
|
|
+ unelevated
|
|
|
+ class="btn-gradient"
|
|
|
+ :label="$t('common.actions.save')"
|
|
|
+ icon="mdi-check"
|
|
|
+ type="submit"
|
|
|
+ :loading="loadingHorario"
|
|
|
+ :disable="!hasUpdatedHorario"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </q-form>
|
|
|
+ </q-tab-panel>
|
|
|
+
|
|
|
+ <q-tab-panel name="servicos" class="q-pa-none">
|
|
|
+ <div class="bg-violet-light q-pb-md">
|
|
|
+ <div class="row justify-end q-mb-sm q-gutter-sm">
|
|
|
+ <q-btn
|
|
|
+ unelevated
|
|
|
+ icon="mdi-upload"
|
|
|
+ :label="$t('common.actions.import')"
|
|
|
+ padding="6px 12px"
|
|
|
+ :disable="!partnerId"
|
|
|
+ class="btn-gradient"
|
|
|
+ />
|
|
|
+ <q-btn
|
|
|
+ unelevated
|
|
|
+ icon="mdi-plus"
|
|
|
+ :label="$t('common.actions.new')"
|
|
|
+ padding="6px 12px"
|
|
|
+ :disable="!partnerId"
|
|
|
+ class="btn-gradient"
|
|
|
+ @click="onAddService"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <q-input
|
|
|
+ v-model="serviceSearch"
|
|
|
+ :placeholder="$t('parceiro.search_placeholder')"
|
|
|
+ outlined
|
|
|
+ dense
|
|
|
+ clearable
|
|
|
+ color="violet-normal"
|
|
|
+ class="q-mb-sm service-search"
|
|
|
+ >
|
|
|
+ <template #prepend>
|
|
|
+ <q-icon name="mdi-magnify" color="violet-normal" />
|
|
|
+ </template>
|
|
|
+ </q-input>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <q-card class="">
|
|
|
+ <div v-if="loadingServices" class="flex flex-center q-pa-xl">
|
|
|
+ <q-spinner color="violet-normal" size="40px" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <DefaultTable
|
|
|
+ v-else
|
|
|
+ v-model:rows="filteredServices"
|
|
|
+ :columns="serviceColumns"
|
|
|
+ no-api-call
|
|
|
+ :show-search-field="false"
|
|
|
+ open-item
|
|
|
+ @on-row-click="({ row }) => onEditService(row)"
|
|
|
+ />
|
|
|
+ </q-card>
|
|
|
+ </q-tab-panel>
|
|
|
+
|
|
|
+ </q-tab-panels>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, computed, watch, onMounted, nextTick, useTemplateRef } from "vue";
|
|
|
+import { useRoute, useRouter } from "vue-router";
|
|
|
+import { useQuasar } from "quasar";
|
|
|
+import { useI18n } from "vue-i18n";
|
|
|
+import { useInputRules } from "src/composables/useInputRules";
|
|
|
+import { useFormUpdateTracker } from "src/composables/useFormUpdateTracker";
|
|
|
+import { useSubmitHandler } from "src/composables/useSubmitHandler";
|
|
|
+import {
|
|
|
+ getPartnerAgreement,
|
|
|
+ createPartnerAgreement,
|
|
|
+ updatePartnerAgreement,
|
|
|
+ uploadPartnerLogo,
|
|
|
+ uploadPartnerMedia,
|
|
|
+ deletePartnerMedia,
|
|
|
+} from "src/api/partnerAgreement";
|
|
|
+import { getServicesByPartner } from "src/api/partnerAgreementService";
|
|
|
+import axios from "axios";
|
|
|
+
|
|
|
+import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
|
|
|
+import DefaultInput from "src/components/defaults/DefaultInput.vue";
|
|
|
+import DefaultInputDatePicker from "src/components/defaults/DefaultInputDatePicker.vue";
|
|
|
+import DefaultFilePicker from "src/components/defaults/DefaultFilePicker.vue";
|
|
|
+import DefaultTable from "src/components/defaults/DefaultTable.vue";
|
|
|
+import PartnerAgreementCategorySelect from "src/components/selects/PartnerAgreementCategorySelect.vue";
|
|
|
+import CitySelect from "src/components/selects/CitySelect.vue";
|
|
|
+import StateSelect from "src/components/selects/StateSelect.vue";
|
|
|
+
|
|
|
+const route = useRoute();
|
|
|
+const router = useRouter();
|
|
|
+const $q = useQuasar();
|
|
|
+const { t } = useI18n();
|
|
|
+const { inputRules } = useInputRules();
|
|
|
+
|
|
|
+const partnerId = computed(() => (route.params.id ? Number(route.params.id) : null));
|
|
|
+const partner = ref(null);
|
|
|
+const activeTab = ref(route.query.tab ?? "dados");
|
|
|
+const panelContainerRef = ref(null);
|
|
|
+
|
|
|
+watch(activeTab, async () => {
|
|
|
+ const container = panelContainerRef.value;
|
|
|
+ if (!container) return;
|
|
|
+
|
|
|
+ const fromHeight = container.offsetHeight;
|
|
|
+
|
|
|
+ container.style.transition = "none";
|
|
|
+ container.style.overflow = "hidden";
|
|
|
+ container.style.height = fromHeight + "px";
|
|
|
+
|
|
|
+ await nextTick();
|
|
|
+
|
|
|
+ void container.offsetHeight;
|
|
|
+
|
|
|
+ requestAnimationFrame(() => {
|
|
|
+ const activePanel = container.querySelector(".panel-item:not(.panel-item--hidden)");
|
|
|
+ const toHeight = activePanel ? activePanel.offsetHeight : fromHeight;
|
|
|
+
|
|
|
+ container.style.transition = "height 0.5s ease";
|
|
|
+ container.style.height = toHeight + "px";
|
|
|
+
|
|
|
+ setTimeout(() => {
|
|
|
+ container.style.height = "auto";
|
|
|
+ container.style.overflow = "";
|
|
|
+ container.style.transition = "";
|
|
|
+ }, 520);
|
|
|
+ });
|
|
|
+}, { flush: "pre" });
|
|
|
+
|
|
|
+const pageTitle = computed(() =>
|
|
|
+ partnerId.value ? t("parceiro.dados_parceiro") : t("parceiro.cadastro_parceiro"),
|
|
|
+);
|
|
|
+
|
|
|
+const tabsItems = computed(() => [
|
|
|
+ { name: "dados", label: t("parceiro.tab_dados") },
|
|
|
+ { name: "contato", label: t("parceiro.tab_contato"), disable: !partnerId.value },
|
|
|
+ { name: "endereco", label: t("parceiro.tab_endereco"), disable: !partnerId.value },
|
|
|
+ { name: "horario", label: t("parceiro.tab_horario"), disable: !partnerId.value },
|
|
|
+ { name: "servicos", label: t("parceiro.tab_servicos"), disable: !partnerId.value },
|
|
|
+]);
|
|
|
+
|
|
|
+const formDadosRef = useTemplateRef("formDadosRef");
|
|
|
+const logoFile = ref(null);
|
|
|
+const selectedCategory = ref(null);
|
|
|
+
|
|
|
+const {
|
|
|
+ form: formDados,
|
|
|
+ getUpdatedFields: updatedDados,
|
|
|
+ hasUpdatedFields: hasUpdatedDados,
|
|
|
+} = useFormUpdateTracker({
|
|
|
+ company_name: "",
|
|
|
+ cnpj: "",
|
|
|
+ category_id: null,
|
|
|
+ responsible: "",
|
|
|
+ discount_percentage: null,
|
|
|
+ description: "",
|
|
|
+});
|
|
|
+
|
|
|
+const {
|
|
|
+ loading: loadingDados,
|
|
|
+ validationErrors: errorsDados,
|
|
|
+ execute: execDados,
|
|
|
+} = useSubmitHandler({ formRef: formDadosRef });
|
|
|
+
|
|
|
+watch(selectedCategory, (val) => {
|
|
|
+ formDados.category_id = val?.value ?? null;
|
|
|
+});
|
|
|
+
|
|
|
+const saveDados = async () => {
|
|
|
+ await execDados(async () => {
|
|
|
+ let savedPartner;
|
|
|
+ if (partnerId.value) {
|
|
|
+ savedPartner = await updatePartnerAgreement(partnerId.value, updatedDados.value);
|
|
|
+ } else {
|
|
|
+ savedPartner = await createPartnerAgreement({ ...formDados });
|
|
|
+ await router.replace({ name: "ParceiroCadastroPage", params: { id: savedPartner.id } });
|
|
|
+ }
|
|
|
+ if (logoFile.value instanceof File) {
|
|
|
+ const uploadedMedia = await uploadPartnerLogo(savedPartner.id ?? partnerId.value, logoFile.value);
|
|
|
+ savedPartner = { ...savedPartner, logo: uploadedMedia };
|
|
|
+ }
|
|
|
+ partner.value = savedPartner;
|
|
|
+ populateForms(savedPartner);
|
|
|
+ logoFile.value = null;
|
|
|
+ $q.notify({ type: "positive", message: t("http.success") });
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const formContatoRef = useTemplateRef("formContatoRef");
|
|
|
+const {
|
|
|
+ form: formContato,
|
|
|
+ getUpdatedFields: updatedContato,
|
|
|
+ hasUpdatedFields: hasUpdatedContato,
|
|
|
+} = useFormUpdateTracker({ email: "", phone: "", whatsapp: "", website: "" });
|
|
|
+
|
|
|
+const {
|
|
|
+ loading: loadingContato,
|
|
|
+ validationErrors: errorsContato,
|
|
|
+ execute: execContato,
|
|
|
+} = useSubmitHandler({ formRef: formContatoRef });
|
|
|
+
|
|
|
+const saveContato = async () => {
|
|
|
+ await execContato(async () => {
|
|
|
+ const saved = await updatePartnerAgreement(partnerId.value, updatedContato.value);
|
|
|
+ partner.value = saved;
|
|
|
+ $q.notify({ type: "positive", message: t("http.success") });
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const formEnderecoRef = useTemplateRef("formEnderecoRef");
|
|
|
+const selectedState = ref(null);
|
|
|
+const selectedCity = ref(null);
|
|
|
+const loadingCep = ref(false);
|
|
|
+
|
|
|
+const {
|
|
|
+ form: formEndereco,
|
|
|
+ getUpdatedFields: updatedEndereco,
|
|
|
+ hasUpdatedFields: hasUpdatedEndereco,
|
|
|
+} = useFormUpdateTracker({
|
|
|
+ zip_code: "",
|
|
|
+ address: "",
|
|
|
+ neighborhood: "",
|
|
|
+ city_id: null,
|
|
|
+ state_id: null,
|
|
|
+});
|
|
|
+
|
|
|
+const {
|
|
|
+ loading: loadingEndereco,
|
|
|
+ validationErrors: errorsEndereco,
|
|
|
+ execute: execEndereco,
|
|
|
+} = useSubmitHandler({ formRef: formEnderecoRef });
|
|
|
+
|
|
|
+watch(selectedState, (val) => { formEndereco.state_id = val?.value ?? null; });
|
|
|
+watch(selectedCity, (val) => { formEndereco.city_id = val?.value ?? null; });
|
|
|
+
|
|
|
+const onCepChange = async (val) => {
|
|
|
+ const digits = (val ?? "").replace(/\D/g, "");
|
|
|
+ if (digits.length !== 8) return;
|
|
|
+ loadingCep.value = true;
|
|
|
+ try {
|
|
|
+ const { data } = await axios.get(`https://viacep.com.br/ws/${digits}/json/`);
|
|
|
+ if (!data.erro) {
|
|
|
+ formEndereco.address = data.logradouro ?? formEndereco.address;
|
|
|
+ formEndereco.neighborhood = data.bairro ?? formEndereco.neighborhood;
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ // silent — user fills manually
|
|
|
+ } finally {
|
|
|
+ loadingCep.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const saveEndereco = async () => {
|
|
|
+ await execEndereco(async () => {
|
|
|
+ const saved = await updatePartnerAgreement(partnerId.value, updatedEndereco.value);
|
|
|
+ partner.value = saved;
|
|
|
+ $q.notify({ type: "positive", message: t("http.success") });
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const formHorarioRef = useTemplateRef("formHorarioRef");
|
|
|
+const mediaFileInputRef = useTemplateRef("mediaFileInputRef");
|
|
|
+const newMediaFile = ref(null);
|
|
|
+const contractMedia = ref([]);
|
|
|
+
|
|
|
+const {
|
|
|
+ form: formHorario,
|
|
|
+ getUpdatedFields: updatedHorario,
|
|
|
+ hasUpdatedFields: hasUpdatedHorario,
|
|
|
+} = useFormUpdateTracker({ working_hours: "", contract_start: null, contract_end: null });
|
|
|
+
|
|
|
+const {
|
|
|
+ loading: loadingHorario,
|
|
|
+ validationErrors: errorsHorario,
|
|
|
+ execute: execHorario,
|
|
|
+} = useSubmitHandler({ formRef: formHorarioRef });
|
|
|
+
|
|
|
+const onMediaFileSelected = async (file) => {
|
|
|
+ if (!file || !partnerId.value) return;
|
|
|
+ try {
|
|
|
+ const media = await uploadPartnerMedia(partnerId.value, file);
|
|
|
+ contractMedia.value.push(media);
|
|
|
+ } catch {
|
|
|
+ $q.notify({ type: "negative", message: t("http.errors.failed") });
|
|
|
+ } finally {
|
|
|
+ newMediaFile.value = null;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const triggerMediaUpload = () => {
|
|
|
+ mediaFileInputRef.value?.pickFiles();
|
|
|
+};
|
|
|
+
|
|
|
+const removeMedia = async (media) => {
|
|
|
+ try {
|
|
|
+ await deletePartnerMedia(partnerId.value, media.id);
|
|
|
+ contractMedia.value = contractMedia.value.filter((m) => m.id !== media.id);
|
|
|
+ } catch {
|
|
|
+ $q.notify({ type: "negative", message: t("http.errors.failed") });
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const openMedia = (media) => {
|
|
|
+ window.open(media.url, "_blank");
|
|
|
+};
|
|
|
+
|
|
|
+const saveHorario = async () => {
|
|
|
+ await execHorario(async () => {
|
|
|
+ const saved = await updatePartnerAgreement(partnerId.value, updatedHorario.value);
|
|
|
+ partner.value = saved;
|
|
|
+ $q.notify({ type: "positive", message: t("http.success") });
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const services = ref([]);
|
|
|
+const loadingServices = ref(false);
|
|
|
+const serviceSearch = ref("");
|
|
|
+
|
|
|
+const filteredServices = computed(() => {
|
|
|
+ if (!serviceSearch.value) return services.value;
|
|
|
+ const needle = serviceSearch.value.toLowerCase();
|
|
|
+ return services.value.filter((s) =>
|
|
|
+ [s.name, s.category?.name, s.price, s.associate_price]
|
|
|
+ .some((f) => f != null && String(f).toLowerCase().includes(needle))
|
|
|
+ );
|
|
|
+});
|
|
|
+
|
|
|
+const serviceColumns = computed(() => [
|
|
|
+ { name: "name", label: t("common.terms.name"), field: "name", align: "left", sortable: true },
|
|
|
+ { name: "category", label: t("parceiro.service_category"), field: (r) => r.category?.name ?? "—", align: "left" },
|
|
|
+ { name: "price", label: t("parceiro.price"), field: "price", align: "left" },
|
|
|
+ { name: "associate_price", label: t("parceiro.associate_price"), field: "associate_price", align: "left" },
|
|
|
+ { name: "actions", label: t("common.terms.actions"), align: "right", required: true },
|
|
|
+]);
|
|
|
+
|
|
|
+const loadServices = async () => {
|
|
|
+ if (!partnerId.value) return;
|
|
|
+ loadingServices.value = true;
|
|
|
+ try {
|
|
|
+ services.value = await getServicesByPartner(partnerId.value);
|
|
|
+ } finally {
|
|
|
+ loadingServices.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const onAddService = () => {
|
|
|
+ router.push({
|
|
|
+ name: "ParceiroServicoCadastroPage",
|
|
|
+ params: { id: partnerId.value },
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const onEditService = (svc) => {
|
|
|
+ router.push({
|
|
|
+ name: "ParceiroServicoCadastroPage",
|
|
|
+ params: { id: partnerId.value, serviceId: svc.id },
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const populateForms = (p) => {
|
|
|
+ if (!p) return;
|
|
|
+
|
|
|
+ formDados.company_name = p.company_name ?? "";
|
|
|
+ formDados.cnpj = p.cnpj ?? "";
|
|
|
+ formDados.category_id = p.category_id ?? null;
|
|
|
+ formDados.responsible = p.responsible ?? "";
|
|
|
+ formDados.discount_percentage = p.discount_percentage != null ? String(p.discount_percentage) : null;
|
|
|
+ formDados.description = p.description ?? "";
|
|
|
+
|
|
|
+ selectedCategory.value = p.category
|
|
|
+ ? { label: p.category.name, value: p.category.id }
|
|
|
+ : null;
|
|
|
+
|
|
|
+ formContato.email = p.email ?? "";
|
|
|
+ formContato.phone = p.phone ?? "";
|
|
|
+ formContato.whatsapp = p.whatsapp ?? "";
|
|
|
+ formContato.website = p.website ?? "";
|
|
|
+
|
|
|
+ formEndereco.zip_code = p.zip_code ?? "";
|
|
|
+ formEndereco.address = p.address ?? "";
|
|
|
+ formEndereco.neighborhood = p.neighborhood ?? "";
|
|
|
+ formEndereco.city_id = p.city_id ?? null;
|
|
|
+ formEndereco.state_id = p.state_id ?? null;
|
|
|
+
|
|
|
+ selectedState.value = p.state ? { label: p.state.name, value: p.state.id } : null;
|
|
|
+ selectedCity.value = p.city ? { label: p.city.name, value: p.city.id } : null;
|
|
|
+
|
|
|
+ formHorario.working_hours = p.working_hours ?? "";
|
|
|
+ formHorario.contract_start = p.contract_start ?? null;
|
|
|
+ formHorario.contract_end = p.contract_end ?? null;
|
|
|
+
|
|
|
+ contractMedia.value = p.media ?? [];
|
|
|
+};
|
|
|
+
|
|
|
+onMounted(async () => {
|
|
|
+ if (partnerId.value) {
|
|
|
+ try {
|
|
|
+ const p = await getPartnerAgreement(partnerId.value);
|
|
|
+ partner.value = p;
|
|
|
+ populateForms(p);
|
|
|
+ } catch {
|
|
|
+ $q.notify({ type: "negative", message: t("http.errors.failed") });
|
|
|
+ }
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+watch(activeTab, (tab) => {
|
|
|
+ if (tab === "servicos") loadServices();
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style lang="scss" scoped>
|
|
|
+@use "src/css/quasar.variables.scss" as vars;
|
|
|
+
|
|
|
+.cadastro-page {
|
|
|
+ &__tabs {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__tab {
|
|
|
+ padding: 4px 16px;
|
|
|
+ border-radius: 6px;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 600;
|
|
|
+ cursor: pointer;
|
|
|
+ background: white;
|
|
|
+ color: vars.$violet-normal;
|
|
|
+ border: 1.5px solid rgba(vars.$violet-normal, 0.3);
|
|
|
+ transition: background 0.15s, color 0.15s;
|
|
|
+ user-select: none;
|
|
|
+
|
|
|
+ &--active {
|
|
|
+ background: vars.$violet-normal;
|
|
|
+ color: white;
|
|
|
+ border-color: vars.$violet-normal;
|
|
|
+ }
|
|
|
+
|
|
|
+ &--disabled {
|
|
|
+ opacity: 0.4;
|
|
|
+ cursor: not-allowed;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.tab-panels-wrapper {
|
|
|
+ :deep(.q-panel-parent) {
|
|
|
+ overflow: hidden !important;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.panels-container {
|
|
|
+ position: relative;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.panel-item--hidden {
|
|
|
+ position: absolute !important;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ visibility: hidden !important;
|
|
|
+ pointer-events: none !important;
|
|
|
+ z-index: -1;
|
|
|
+}
|
|
|
+
|
|
|
+.cadastro-card {
|
|
|
+ border-radius: 12px;
|
|
|
+ background: white;
|
|
|
+}
|
|
|
+
|
|
|
+.input-violet {
|
|
|
+ :deep(.q-field__control) {
|
|
|
+ background: vars.$violet-light !important;
|
|
|
+ border-radius: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.q-field__native),
|
|
|
+ :deep(.q-field__input) {
|
|
|
+ color: vars.$violet-dark !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.q-field__label) {
|
|
|
+ color: vars.$violet-normal !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.q-field--outlined .q-field__control:before) {
|
|
|
+ border-color: transparent !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.q-field--outlined .q-field__control:after) {
|
|
|
+ border-color: vars.$violet-normal !important;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.btn-gradient {
|
|
|
+ background: linear-gradient(90deg, #4d1658 0%, #8b30a5 100%) !important;
|
|
|
+ color: white !important;
|
|
|
+ border-radius: 8px !important;
|
|
|
+ padding: 8px 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.btn-gradient :deep(.q-icon) {
|
|
|
+ color: white !important;
|
|
|
+}
|
|
|
+
|
|
|
+.service-search :deep(.q-field__control) {
|
|
|
+ border-radius: 24px !important;
|
|
|
+}
|
|
|
+</style>
|