v-sistec-features 1.17.7 → 1.18.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,142 @@
1
+ <template>
2
+ <div class="loading-container">
3
+ <template v-if="props.custom_loading">
4
+ <component :is="props.custom_loading" />
5
+ </template>
6
+ <template v-else>
7
+ <table class="table table-vcenter table-selectable" :class="props.class_table">
8
+ <thead>
9
+ <tr>
10
+ <th v-for="col in columns" :key="col.field || col.header" :class="col.class_column">
11
+ {{ col.header }}
12
+ </th>
13
+ </tr>
14
+ </thead>
15
+ <tbody>
16
+ <template v-if="props.type_loading === 'placeholder'">
17
+ <tr v-for="n in pagination.limit_per_page" :key="'placeholder-' + n" class="placeholder-glow">
18
+ <td v-for="col in columns" :key="col.field || col.header" :class="col.class_row">
19
+ <span v-if="col.bodySlot">
20
+ <span class="placeholder col-8"></span>
21
+ </span>
22
+ <span :class="col.class_item" v-else-if="col.type === 'text'">
23
+ <span class="placeholder col-8"></span>
24
+ </span>
25
+ <span v-else-if="col.type === 'date'">
26
+ <span class="placeholder col-9"></span>
27
+ </span>
28
+ <div :class="col.class_item" v-else-if="col.type === 'html'">
29
+ <div class="placeholder col-12"></div>
30
+ </div>
31
+
32
+ <div :class="col.class_item" v-else-if="col.type === 'img'">
33
+ <div class="placeholder placeholder-img"></div>
34
+ </div>
35
+
36
+ <span class="text-danger erro-custom-container" v-else>tipo <span
37
+ class="badge bg-orange text-white erro-custom-text">{{ col.type }}</span> não
38
+ suportado
39
+ </span>
40
+ </td>
41
+ </tr>
42
+ </template>
43
+ <template v-else-if="props.type_loading === 'spiner-table'">
44
+ <tr v-for="n in pagination.limit_per_page" :key="'placeholder-' + n">
45
+ <td v-for="col in columns" :key="col.field || col.header" :class="col.class_row">
46
+ <span v-if="col.bodySlot">
47
+ <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
48
+ </span>
49
+ <span :class="col.class_item" v-else-if="col.type === 'text'">
50
+ <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
51
+ </span>
52
+ <span v-else-if="col.type === 'date'">
53
+ <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
54
+ </span>
55
+ <div :class="col.class_item" v-else-if="col.type === 'html'">
56
+ <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
57
+ </div>
58
+
59
+ <div class="" :class="col.class_item" v-else-if="col.type === 'img'">
60
+ <span class="placeholder-img d-flex justify-content-center align-items-center">
61
+ <span class="spinner-border spinner-border-sm" role="status"
62
+ aria-hidden="true"></span>
63
+ </span>
64
+ </div>
65
+
66
+ <span class="text-danger erro-custom-container" v-else>tipo <span
67
+ class="badge bg-orange text-white erro-custom-text">{{ col.type }}</span> não
68
+ suportado
69
+ </span>
70
+ </td>
71
+ </tr>
72
+
73
+ </template>
74
+ <template v-else-if="props.type_loading === 'spiner'">
75
+ <tr v-for="n in pagination.limit_per_page" :key="n">
76
+ <td :colspan="columns.length" class="text-center p-0" style="border-bottom: none;">
77
+ <div v-if="n === Math.floor(pagination.limit_per_page / 2) + 1"
78
+ class="d-flex flex-column justify-content-center align-items-center"
79
+ style="height: 6rem;">
80
+ <div class="spinner-border" style="width: 3rem; height: 3rem;" role="status">
81
+ </div>
82
+ <span class="mt-2">Carregando...</span>
83
+ </div>
84
+ <div v-else style="height: 3rem;"></div>
85
+ </td>
86
+ </tr>
87
+ </template>
88
+
89
+ </tbody>
90
+ </table>
91
+ </template>
92
+
93
+ <div v-if="attempt && attempt.current > 1" class="p-3 text-center text-secondary">
94
+ A conexão falhou. Tentando novamente... (Tentativa {{ attempt.current }} de {{ attempt.total }})
95
+ </div>
96
+ </div>
97
+ </template>
98
+
99
+ <script setup lang="ts">
100
+ import type { ColumnConfiguration } from '../keys';
101
+ import type { PaginationObject } from '@/DataPageVue/types/v-data-page.ts';
102
+ interface AttemptObject {
103
+ current: number;
104
+ total: number;
105
+ }
106
+
107
+ const props = defineProps<{
108
+ columns: ColumnConfiguration[];
109
+ limit: number;
110
+ type_loading: string;
111
+ custom_loading?: any;
112
+ class_table?: string;
113
+ attempt?: AttemptObject | null;
114
+ pagination: PaginationObject;
115
+ }>();
116
+ </script>
117
+
118
+ <style lang="scss" scoped>
119
+
120
+ $max-width-img: 40px;
121
+
122
+ .placeholder-img {
123
+ width: $max-width-img;
124
+ height: $max-width-img;
125
+ border-radius: 4px;
126
+ }
127
+
128
+ .erro-custom-container {
129
+ display: inline-block;
130
+ padding: 0.2em 0.6em;
131
+ border-radius: 4px;
132
+ background-color: #ffffff;
133
+ font-weight: bold;
134
+ text-transform: uppercase;
135
+ }
136
+
137
+ .erro-custom-text {
138
+ font-size: 0.8em;
139
+ text-transform: uppercase;
140
+ font-weight: bold;
141
+ }
142
+ </style>
@@ -0,0 +1,89 @@
1
+ import { ref, watch, computed, type Ref, } from 'vue';
2
+ import type { DataTablePropsWithDefaults } from '../types/v-data-table'; // Ajuste o caminho conforme necessário
3
+
4
+ export function useCheckBox<T extends Record<string, any>>(
5
+ props: DataTablePropsWithDefaults,
6
+ items: Ref<T[]>,
7
+ ) {
8
+ const selectAllCheckbox = ref<HTMLInputElement | null>(null);
9
+ const selected_items = ref<T[]>([]) as Ref<T[]>;
10
+ // Helper para verificar se um item está selecionado, comparando pela chave única
11
+ const isSelected = (item: T): boolean => {
12
+ const key = props.item_key;
13
+ return selected_items.value.some(selectedItem => selectedItem[key] === item[key]);
14
+ };
15
+
16
+ // Controla o estado do checkbox "selecionar todos"
17
+ const selectAllState = computed<boolean | 'indeterminate'>(() => {
18
+ if (!items.value.length) return false;
19
+ const selectedOnPageCount = items.value.filter(item => isSelected(item)).length;
20
+ if (selectedOnPageCount === 0) return false;
21
+ if (selectedOnPageCount === items.value.length) return true;
22
+ return 'indeterminate';
23
+ });
24
+
25
+ // computed que mostra se pelo menos um item está selecionado
26
+ const atLeastOneSelected = computed<boolean>(() => selected_items.value.length > 0);
27
+
28
+
29
+ // observa o estado e atualiza a propriedade 'indeterminate'
30
+ watch([selectAllState, selectAllCheckbox], ([newState]) => {
31
+ if (selectAllCheckbox.value) {
32
+ if (newState === 'indeterminate') {
33
+ // Se o estado for indeterminado:
34
+ selectAllCheckbox.value.checked = false; // Ele não está "marcado"
35
+ selectAllCheckbox.value.indeterminate = true; // Ele está com o "traço"
36
+ } else {
37
+ selectAllCheckbox.value.checked = newState; // Define o estado marcado/desmarcado
38
+ selectAllCheckbox.value.indeterminate = false; // Remove o "traço"
39
+ }
40
+ }
41
+ }, {
42
+ immediate: true,
43
+ flush: 'post'
44
+ });
45
+ // Função para marcar ou desmarcar todos os itens da página atual
46
+ function toggleSelectAll(): void {
47
+ const pageItems = items.value;
48
+ if (!pageItems.length) return;
49
+
50
+ // Usa a propriedade computada para saber o estado atual
51
+ const currentState = selectAllState.value;
52
+
53
+ // Se TODOS ou ALGUNS estiverem selecionados, o clique irá LIMPAR a seleção da página.
54
+ if (currentState === true || currentState === 'indeterminate') {
55
+ const pageItemKeys = pageItems.map(item => item[props.item_key]);
56
+ selected_items.value = selected_items.value.filter(
57
+ selectedItem => !pageItemKeys.includes(selectedItem[props.item_key])
58
+ );
59
+ }
60
+ // Se NENHUM estiver selecionado, o clique irá SELECIONAR TODOS da página.
61
+ else { // currentState é false
62
+ pageItems.forEach(item => {
63
+ if (!isSelected(item)) {
64
+ selected_items.value.push(item);
65
+ }
66
+ });
67
+ }
68
+ }
69
+
70
+ // Função para marcar ou desmarcar um item individual
71
+ function toggleItemSelection(item: T): void {
72
+ const key = props.item_key;
73
+ const index = selected_items.value.findIndex(selectedItem => selectedItem[key] === item[key]);
74
+
75
+ if (index > -1) {
76
+ selected_items.value.splice(index, 1); // Remove se já existe
77
+ } else {
78
+ selected_items.value.push(item); // Adiciona se não existe
79
+ }
80
+ }
81
+
82
+ return {
83
+ selectAllCheckbox,
84
+ selected_items,
85
+ isSelected, selectAllState,
86
+ atLeastOneSelected, toggleSelectAll, toggleItemSelection
87
+
88
+ };
89
+ }
@@ -0,0 +1,116 @@
1
+ import { ref, computed, type Ref } from 'vue';
2
+ import type { PaginationObject, DataTablePropsWithDefaults } from '../types/v-data-table'; // Ajuste o caminho conforme necessário
3
+ import { type ColumnConfiguration } from '../keys';
4
+
5
+
6
+ export function useDataTableFetch<T>(
7
+ props: DataTablePropsWithDefaults,
8
+ pagination: Ref<PaginationObject>,
9
+ columns: Ref<ColumnConfiguration[]>,
10
+ orderings_state: Ref<Record<string, 'none' | 'increasing' | 'decreasing'>>,
11
+ emit: (event: 'beforeFetch' | 'afterFetch') => void,
12
+ close_all_expanded_items: () => void
13
+
14
+ ) {
15
+ const items = ref<T[]>([]) as Ref<T[]>;
16
+
17
+ const urlReativa = computed(() => {
18
+ pagination.value.current_page = props.page_starts_at;
19
+ return props.endpoint;
20
+ });
21
+
22
+ const default_params = computed<Record<string, any>>(() => ({
23
+ [props.page_param_name]: pagination.value.current_page + 1,
24
+ [props.page_size_param_name]: pagination.value.limit_per_page,
25
+ [props.search_param_name]: pagination.value.search || "",
26
+ [props.filter_param_name]: pagination.value.filter || "",
27
+ }));
28
+
29
+ const params_ordering = computed(() => {
30
+ const objectOrdering: Record<string, any> = {};
31
+ for (const col of columns.value) {
32
+ if (col.use_ordering) {
33
+ if (orderings_state.value[col.header] === 'increasing') {
34
+ objectOrdering[col.param_ordering] = col.increasing_value || 'increasing';
35
+ } else if (orderings_state.value[col.header] === 'decreasing') {
36
+ objectOrdering[col.param_ordering] = col.decreasing_value || 'decreasing';
37
+ }
38
+ }
39
+ }
40
+
41
+ return objectOrdering;
42
+ });
43
+ const { data: response, pending, error, execute, attempt } = props.fetch(urlReativa, {
44
+ disable_request: () => props.disable_request,
45
+ params: () => {
46
+ const isFunction = typeof props.add_params === 'function';
47
+ let extraParams = {};
48
+
49
+ if (isFunction) {
50
+ const getParams = props.add_params as () => Record<string, any>;
51
+ extraParams = getParams();
52
+ } else {
53
+ extraParams = props.add_params || {};
54
+ }
55
+ if (props.deactivate_default_params) {
56
+ return {
57
+ ...extraParams,
58
+ ...params_ordering.value
59
+ };
60
+ }
61
+ return {
62
+ ...default_params.value,
63
+ ...props.add_params,
64
+ ...params_ordering.value
65
+ };
66
+ },
67
+ retry: props.retry_attempts,
68
+ retryDelay: props.retry_delay,
69
+ paramsReactives: false,
70
+ immediate: false,
71
+ }, props.fetch_name);
72
+
73
+ const isDelaying = ref<boolean>(false);
74
+ const delayTimer = ref<ReturnType<typeof setTimeout> | null>(null);
75
+ const first_fetch = ref<boolean>(false);
76
+ // para controlar a exibição do loading
77
+ const showLoadingState = computed<boolean>(() => {
78
+ return (pending.value || isDelaying.value)
79
+ });
80
+ // Função que gerencia o delay e a chamada da API
81
+ async function fetchDataWithDelay(): Promise<void> {
82
+ // agora já fez pelo menos a primeira busca então marca como true
83
+ if (!first_fetch.value) first_fetch.value = true;
84
+ // Limpa timer anterior, se houver
85
+ if (delayTimer.value) clearTimeout(delayTimer.value);
86
+
87
+ isDelaying.value = true;
88
+
89
+ delayTimer.value = setTimeout(() => {
90
+ isDelaying.value = false;
91
+ }, props.min_loading_delay);
92
+ close_all_expanded_items();
93
+ emit('beforeFetch');
94
+ await execute(); // Executa a busca de dados original do useApiFetch
95
+ emit('afterFetch');
96
+ }
97
+
98
+ function reSearch(): void {
99
+ pagination.value.current_page = props.page_starts_at;
100
+ fetchDataWithDelay();
101
+ }
102
+ return {
103
+ items,
104
+ pending,
105
+ error,
106
+ execute,
107
+ response,
108
+ attempt,
109
+ default_params,
110
+ fetchDataWithDelay,
111
+ reSearch,
112
+ showLoadingState,
113
+ first_fetch
114
+
115
+ };
116
+ }
@@ -8,7 +8,8 @@ export function useExpandedItem(
8
8
  ) {
9
9
  const expanded_items = ref<any[]>([]);
10
10
 
11
- function close_all_expanded_items() {
11
+
12
+ function close_all_expanded_items(): void {
12
13
  expanded_items.value = [];
13
14
  }
14
15
 
@@ -75,7 +75,56 @@ export interface VDataTableProps {
75
75
 
76
76
  disable_request?: MaybeRefOrGetter<boolean>;
77
77
  }
78
+ export type DataTablePropsWithDefaults = VDataTableProps & {
79
+ // Strings
80
+ fetch_name: string;
81
+ type_loading: string;
82
+ filter_param_name: string;
83
+ search_param_name: string;
84
+ page_param_name: string;
85
+ page_size_param_name: string;
86
+ data_key: string;
87
+ total_key: string;
88
+ item_key: string;
89
+ first_text_page_size: string;
90
+ second_text_page_size: string;
91
+ placeholder_search: string;
92
+ type_animation_expand: string;
93
+ type_button_expand: string;
78
94
 
95
+ // Classes CSS (Strings)
96
+ class_table: string;
97
+ class_content: string;
98
+ class_container: string;
99
+ class_pagination: string;
100
+ class_filters: string;
101
+ class_page_size: string;
102
+
103
+ // Numbers
104
+ min_loading_delay: number;
105
+ retry_attempts: number;
106
+ retry_delay: number;
107
+ limit_per_page: number;
108
+ page_starts_at: number;
109
+
110
+ // Booleans
111
+ deactivate_default_params: boolean;
112
+ use_checkbox: boolean;
113
+ deactivate_selected_info: boolean;
114
+ immediate: boolean;
115
+ deactivate_search_on_clear: boolean;
116
+ use_expandable_items: boolean;
117
+ close_expanded_item_on_expand_new: boolean;
118
+ scroll_to_expanded_item: boolean;
119
+ deactivate_animation_expand: boolean;
120
+ deactivate_search_empty: boolean;
121
+ disable_request: MaybeRefOrGetter<boolean>;
122
+
123
+ // Complex Types (Arrays/Objects/Functions)
124
+ add_params: Record<string, any> | (() => Record<string, any>);
125
+ list_filter: any[];
126
+ custom_loading: any | null;
127
+ };
79
128
  export interface ExposedFunctions<T extends Record<string, any>> {
80
129
  execute: () => void;
81
130
  reSearch: () => void;
@@ -89,4 +138,5 @@ export interface ExposedFunctions<T extends Record<string, any>> {
89
138
  set_page: (newPage: number) => void;
90
139
  expand_item_toggle: (item: any) => void;
91
140
  close_all_expanded_items: () => void;
141
+ selectAllCheckbox: Ref<HTMLInputElement | null>;
92
142
  }
@@ -0,0 +1,8 @@
1
+ import type { InjectionKey } from 'vue';
2
+ import type { VDataTableProps } from '@/DatatableVue/types/v-data-table';
3
+
4
+ //o Partial para dizer que no global não precisamos passar TUDO
5
+ export type DataTableGlobalConfig = Partial<VDataTableProps>;
6
+
7
+ // A chave para o provide/inject
8
+ export const DATA_TABLE_CONFIG: InjectionKey<DataTableGlobalConfig> = Symbol('DataTableConfig');
@@ -26,10 +26,16 @@ import '@tabler/core/dist/css/tabler-themes.min.css'
26
26
  import * as Tabler from '@tabler/core/dist/js/tabler.min.js';
27
27
  import vRequired from "v-required"
28
28
  import '../assets/v-required-style.css'
29
- import Toast from "vue-toastification";
29
+ import Toast, { type PluginOptions as ToastPluginOptions } from "vue-toastification";
30
30
  import "vue-toastification/dist/index.css";
31
31
 
32
- const options_toast = {
32
+ import { DATA_TABLE_CONFIG, type DataTableGlobalConfig } from '@/config/datatableConfig';
33
+ export interface SistecOptions {
34
+ dataTable?: DataTableGlobalConfig; // Configurações opcionais da tabela
35
+ toast?: ToastPluginOptions; // Configurações opcionais do Toast
36
+ }
37
+
38
+ const defaultToastOptions: ToastPluginOptions = {
33
39
  transition: "Vue-Toastification__bounce",
34
40
  maxToasts: 20,
35
41
  newestOnTop: true,
@@ -38,14 +44,22 @@ const options_toast = {
38
44
 
39
45
  const SistecPlugin = {
40
46
  // até o momento não usamos opções nem o app, mas deixei aqui caso precise no futuro
41
- install: (app: App, _options?: any) => {
47
+ install: (app: App, options: SistecOptions = {}) => {
42
48
  // Disponibiliza o Tabler globalmente
43
49
  (window as any).bootstrap = Tabler;
44
50
 
51
+
52
+
45
53
  // futuros upgrades podem ser feitos aqui
46
54
  // Ex: app.component('MeuComponente', MeuComponente);
47
55
  app.directive('required', vRequired);
48
- app.use(Toast, options_toast);
56
+ // Aqui um merge: Opções do usuário > Padrão do Sistec
57
+ const finalToastOptions = { ...defaultToastOptions, ...options.toast };
58
+ app.use(Toast, finalToastOptions);
59
+ // CONFIGURAÇÃO DO DATATABLE
60
+ // Se o usuário passou configurações goblais de tabela, fazemos o provide AQUI.
61
+ const tableConfig = options.dataTable || {};
62
+ app.provide(DATA_TABLE_CONFIG, tableConfig);
49
63
  }
50
64
  };
51
65
  export { SistecPlugin };