| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- <template>
- <div>
- <input
- ref="importFileInput"
- type="file"
- accept=".xlsx"
- class="hidden"
- @change="onFileSelected"
- />
- <DefaultHeaderPage>
- <template #after>
- <div class="flex gap-sm q-mt-md">
- <q-btn
- unelevated
- class="btn-gradient"
- :label="$t('associado.import_afastamentos')"
- icon="mdi-upload"
- padding="8px 12px"
- />
- <q-btn
- unelevated
- class="btn-gradient"
- :label="$t('associado.import_associados')"
- icon="mdi-upload"
- padding="8px 12px"
- :loading="importing"
- @click="onImportClick"
- />
- <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.new')"
- icon="mdi-plus"
- padding="8px 12px"
- @click="onAddItem"
- />
- </div>
- </template>
- </DefaultHeaderPage>
- <DefaultTableServerSide
- ref="tableRef"
- :columns="columns"
- :api-call="getAssociadosPaginated"
- :add-item="false"
- open-item
- @on-row-click="onRowClick"
- >
- <template #body-cell-actions="{ row }">
- <div class="row no-wrap items-center" style="gap: 4px">
- <q-btn flat round dense color="violet-normal" icon="mdi-pencil" @click.stop="onRowClick({ row })" />
- <q-btn
- flat round dense
- :color="row.status === 'on_leave' || row.status?.value === 'on_leave' ? 'grey-4' : 'violet-normal'"
- icon="mdi-account-minus"
- :disable="row.status === 'on_leave' || row.status?.value === 'on_leave'"
- @click.stop="onSetOnLeave(row)"
- />
- <q-btn flat round dense color="violet-normal" icon="mdi-calendar" @click.stop />
- <q-btn flat round dense color="violet-normal" icon="mdi-sofa" @click.stop />
- </div>
- </template>
- <template #body-cell-status="{ row }">
- <q-td class="text-center">
- <q-badge
- outline
- :color="getStatusColor(row.status)"
- :label="$t(getStatusI18nKey(row.status))"
- />
- </q-td>
- </template>
- </DefaultTableServerSide>
- <q-separator class="q-my-xl" />
- <AccessLogsTable />
- </div>
- </template>
- <script setup>
- import { ref, computed, defineAsyncComponent, useTemplateRef } from "vue";
- import { useQuasar } from "quasar";
- import { useI18n } from "vue-i18n";
- import { permissionStore } from "src/stores/permission";
- import { getAssociadosPaginated, importAssociados, setUserOnLeave } from "src/api/user";
- import { getStatusColor, getStatusI18nKey } from "src/helpers/utils";
- import { useImportPoller } from "src/composables/useImportPoller";
- import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
- import DefaultTableServerSide from "src/components/defaults/DefaultTableServerSide.vue";
- import AccessLogsTable from "./components/AccessLogsTable.vue";
- const AddEditAssociadoDialog = defineAsyncComponent(
- () => import("./components/AddEditAssociadoDialog.vue"),
- );
- const ImportHistoryDialog = defineAsyncComponent(
- () => import("src/components/ImportHistoryDialog.vue"),
- );
- const permission_store = permissionStore();
- const $q = useQuasar();
- const { t } = useI18n();
- const tableRef = ref(null);
- const importFileInput = useTemplateRef("importFileInput");
- const { polling: importing, start: startPolling } = useImportPoller();
- const columns = computed(() => [
- {
- name: "registration",
- label: t("associado.cracha"),
- field: "registration",
- align: "left",
- sortable: true,
- width: "10%",
- },
- {
- name: "name",
- label: t("common.terms.name"),
- field: "name",
- align: "left",
- sortable: true,
- width: "25%",
- },
- {
- name: "position",
- label: t("associado.position"),
- field: (row) => row.position?.name ?? "—",
- align: "left",
- sortable: true,
- width: "25%",
- },
- {
- name: "admission_date",
- label: t("associado.admission"),
- field: (row) => (row.admission_date ? row.admission_date : "—"),
- align: "left",
- sortable: true,
- width: "10%",
- },
- {
- name: "expiry_date",
- label: t("associado.validity"),
- field: (row) => (row.expiry_date ? row.expiry_date : "—"),
- align: "left",
- sortable: true,
- width: "10%",
- },
- {
- name: "actions",
- label: t("common.terms.actions"),
- align: "right",
- required: true,
- width: "15%",
- },
- {
- name: "status",
- label: t("common.terms.status"),
- field: "status",
- align: "center",
- sortable: true,
- width: "5%",
- },
- ]);
- const onAddItem = async () => {
- if (!permission_store.getAccess("associado", "add")) {
- $q.notify({ type: "negative", message: t("validation.permissions.add") });
- return;
- }
- $q.dialog({ component: AddEditAssociadoDialog }).onOk(() => {
- tableRef.value?.refresh();
- });
- };
- const onRowClick = async ({ row }) => {
- if (!permission_store.getAccess("associado", "edit")) {
- $q.notify({ type: "negative", message: t("validation.permissions.edit") });
- return;
- }
- $q.dialog({
- component: AddEditAssociadoDialog,
- componentProps: {
- associado: row,
- title: () => t("common.actions.edit") + " " + t("ui.navigation.associados"),
- },
- }).onOk(() => {
- tableRef.value?.refresh();
- });
- };
- const onSetOnLeave = (row) => {
- if (!permission_store.getAccess("associado", "edit")) {
- $q.notify({ type: "negative", message: t("validation.permissions.edit") });
- return;
- }
- $q.dialog({
- title: t("associado.set_on_leave_title"),
- message: t("associado.set_on_leave_message", { name: row.name }),
- cancel: { flat: true, label: t("common.actions.cancel"), color: "grey-7" },
- ok: { unelevated: true, label: t("associado.set_on_leave_confirm"), color: "violet-normal" },
- persistent: true,
- }).onOk(async () => {
- try {
- await setUserOnLeave(row.id);
- tableRef.value?.refresh();
- } catch {
- // silent
- }
- });
- };
- const onOpenImportHistory = () => {
- $q.dialog({ component: ImportHistoryDialog, componentProps: { type: 'associado' } });
- };
- const onImportClick = () => {
- if (!permission_store.getAccess("associado", "add")) {
- $q.notify({ type: "negative", message: t("validation.permissions.add") });
- return;
- }
- importFileInput.value.value = null;
- importFileInput.value.click();
- };
- const onFileSelected = async (event) => {
- const file = event.target.files?.[0];
- if (!file) return;
- try {
- const { import_id } = await importAssociados(file);
- startPolling(import_id, {
- onComplete: (stats) => {
- $q.notify({
- type: "positive",
- message: t("associado.import_sync_result", {
- created: stats.created,
- updated: stats.updated,
- inactivated: stats.inactivated,
- }),
- timeout: 6000,
- });
- tableRef.value?.refresh();
- },
- onError: () => $q.notify({ type: "negative", message: t("http.errors.failed") }),
- onTimeout: () => $q.notify({ type: "warning", message: t("associado.import_processing") }),
- });
- } catch {
- $q.notify({ type: "negative", message: t("http.errors.failed") });
- }
- };
- </script>
- <style scoped>
- .gap-sm {
- gap: 8px;
- }
- .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>
|