| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230 |
- <template>
- <div class="parceiros-page">
- <input
- ref="importParceiroInput"
- type="file"
- accept=".xlsx"
- class="hidden"
- @change="onParceiroFileSelected"
- />
- <DefaultHeaderPage>
- <template #after>
- <div v-if="isAdministrador" class="flex gap-sm q-mt-md">
- <q-btn
- unelevated
- class="btn-gradient"
- :label="$t('parceiro.import_parceiros')"
- icon="mdi-upload"
- padding="8px 12px"
- :loading="importingParceiros"
- @click="onImportParceirosClick"
- />
- <q-btn
- unelevated
- class="btn-gradient"
- icon="mdi-clock-outline"
- padding="8px 12px"
- :title="$t('import_log.history_title')"
- @click="onOpenImportHistory"
- />
- <q-btn
- unelevated
- class="btn-gradient"
- :label="$t('common.actions.add')"
- icon="mdi-plus"
- padding="8px 12px"
- @click="onAddItem"
- />
- </div>
- </template>
- </DefaultHeaderPage>
- <div class="q-pb-sm">
- <q-input
- v-model="searchQuery"
- outlined
- dense
- bg-color="white"
- :placeholder="$t('parceiro.search_placeholder')"
- clearable
- class="parceiros-page__search-input"
- >
- <template #prepend>
- <q-icon name="mdi-magnify" />
- </template>
- </q-input>
- </div>
- <div class="row q-gutter-xs q-mb-md flex-wrap">
- <div
- :class="['cat-chip', activeCategory === 'all' ? 'cat-chip--selected' : 'cat-chip--default']"
- @click="activeCategory = 'all'"
- >
- {{ $t('common.terms.all') }}
- </div>
- <div
- v-for="cat in categories"
- :key="cat.id"
- :class="['cat-chip', activeCategory === String(cat.id) ? 'cat-chip--selected' : 'cat-chip--default']"
- @click="activeCategory = String(cat.id)"
- >
- {{ cat.name }}
- </div>
- </div>
- <div v-if="loading" class="flex flex-center q-pa-xl">
- <q-spinner color="violet-normal" size="50px" />
- </div>
- <div v-else-if="filteredPartners.length === 0" class="flex flex-center q-pa-xl text-grey-6">
- {{ $t("http.errors.no_records_found") }}
- </div>
- <div v-else class="row q-col-gutter-md">
- <div
- v-for="partner in filteredPartners"
- :key="partner.id"
- class="col-xl-2 col-lg-3 col-md-4 col-sm-6 col-12"
- >
- <PartnerAgreementCard
- :partner="partner"
- :editable="isAdministrador"
- @edit="onEditItem"
- />
- </div>
- </div>
- </div>
- </template>
- <script setup>
- import { ref, computed, onMounted, useTemplateRef, defineAsyncComponent } from "vue";
- import { useRouter } from "vue-router";
- import { useQuasar } from "quasar";
- import { useI18n } from "vue-i18n";
- import { permissionStore } from "src/stores/permission";
- import { userStore } from "src/stores/user";
- import { getPartnerAgreements, importParceiros } from "src/api/partnerAgreement";
- import { getCategories } from "src/api/category";
- import { normalizeString } from "src/helpers/utils";
- import { useImportPoller } from "src/composables/useImportPoller";
- const ImportHistoryDialog = defineAsyncComponent(
- () => import("src/components/ImportHistoryDialog.vue"),
- );
- import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
- import PartnerAgreementCard from "./components/PartnerAgreementCard.vue";
- const router = useRouter();
- const $q = useQuasar();
- const { t } = useI18n();
- const permission_store = permissionStore();
- const { isAdministrador } = userStore();
- const loading = ref(true);
- const allPartners = ref([]);
- const categories = ref([]);
- const activeCategory = ref("all");
- const searchQuery = ref("");
- const importParceiroInput = useTemplateRef("importParceiroInput");
- const { polling: importingParceiros, start: startParceiroPolling } = useImportPoller();
- const filteredPartners = computed(() => {
- let list = allPartners.value;
- if (activeCategory.value !== "all") {
- list = list.filter((p) => String(p.category_id) === activeCategory.value);
- }
- if (searchQuery.value) {
- const needle = normalizeString(searchQuery.value);
- list = list.filter((p) => {
- const fields = [p.company_name, p.cnpj, p.responsible, p.email, p.phone, p.category?.name, p.address, p.city?.name];
- return fields.some((f) => f && normalizeString(String(f)).includes(needle));
- });
- }
- return list;
- });
- const onAddItem = () => {
- if (!permission_store.getAccess("parceiro.convenio", "add")) {
- $q.notify({ type: "negative", message: t("validation.permissions.add") });
- return;
- }
- router.push({ name: "ParceiroCadastroPage" });
- };
- const onEditItem = (partner) => {
- if (!permission_store.getAccess("parceiro.convenio", "edit")) {
- $q.notify({ type: "negative", message: t("validation.permissions.edit") });
- return;
- }
- router.push({ name: "ParceiroCadastroPage", params: { id: partner.id } });
- };
- const loadPartners = async () => {
- const [partners, cats] = await Promise.all([
- getPartnerAgreements({ type: "partner" }),
- getCategories("partner"),
- ]);
- allPartners.value = partners;
- categories.value = cats;
- };
- const onOpenImportHistory = () => {
- $q.dialog({ component: ImportHistoryDialog, componentProps: { type: 'parceiro' } });
- };
- const onImportParceirosClick = () => {
- if (!permission_store.getAccess("parceiro.convenio", "add")) {
- $q.notify({ type: "negative", message: t("validation.permissions.add") });
- return;
- }
- importParceiroInput.value.value = null;
- importParceiroInput.value.click();
- };
- const onParceiroFileSelected = async (event) => {
- const file = event.target.files?.[0];
- if (!file) return;
- try {
- const { import_id } = await importParceiros(file);
- startParceiroPolling(import_id, {
- onComplete: async (stats) => {
- $q.notify({
- type: "positive",
- message: t("parceiro.import_parceiros_result", { created: stats.created, updated: stats.updated, inactivated: stats.inactivated }),
- timeout: 7000,
- });
- await loadPartners();
- },
- onError: () => $q.notify({ type: "negative", message: t("http.errors.failed") }),
- onTimeout: () => $q.notify({ type: "warning", message: t("parceiro.import_processing") }),
- });
- } catch {
- $q.notify({ type: "negative", message: t("http.errors.failed") });
- }
- };
- onMounted(async () => {
- try { await loadPartners(); }
- finally { loading.value = false; }
- });
- </script>
- <style scoped>
- .gap-sm { gap: 8px; }
- .parceiros-page { min-height: 100vh; }
- .parceiros-page__search-input { max-width: 700px; width: 100%; }
- .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;
- }
- .cat-chip--default { background: #c9a3dc; color: #fff; }
- .cat-chip--selected { background: #4d1658; color: #fff; }
- .btn-gradient {
- background: linear-gradient(90deg, #4d1658 0%, #8b30a5 100%) !important;
- color: white !important; border-radius: 8px !important;
- }
- .btn-gradient :deep(.q-icon) { color: white !important; }
- </style>
|