vue-laravel-crud 1.8.5 → 2.0.1

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.
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "vue-laravel-crud",
3
- "version": "1.8.5",
3
+ "version": "2.0.1",
4
+ "type": "module",
4
5
  "description": "",
5
6
  "homepage": "https://github.com/clonixdev/vue-laravel-crud",
6
7
  "main": "dist/vue-laravel-crud.ssr.js",
@@ -14,6 +15,9 @@
14
15
  "sideEffects": false,
15
16
  "scripts": {
16
17
  "serve": "vue-cli-service serve dev/serve.js",
18
+ "demo": "concurrently \"npm run demo:api\" \"npm run demo:app\"",
19
+ "demo:api": "node dev/api/mockServer.js",
20
+ "demo:app": "vue-cli-service serve dev/demo/main.js",
17
21
  "build": "cross-env NODE_ENV=production rollup --config build/rollup.config.js",
18
22
  "build:ssr": "cross-env NODE_ENV=production rollup --config build/rollup.config.js --format cjs",
19
23
  "build:es": "cross-env NODE_ENV=production rollup --config build/rollup.config.js --format es",
@@ -21,6 +25,7 @@
21
25
  },
22
26
  "dependencies": {
23
27
  "axios": "^1.3.5",
28
+ "bootstrap": "^4.6.2",
24
29
  "bootstrap-vue": "^2.21.2",
25
30
  "moment": "^2.29.1",
26
31
  "sass-loader": "^14.1.1",
@@ -42,7 +47,10 @@
42
47
  "@vue/cli-service": "^5.0.8",
43
48
  "@vuex-orm/core": "^0.36.4",
44
49
  "@vuex-orm/plugin-axios": "^0.9.4",
50
+ "concurrently": "^7.6.0",
51
+ "cors": "^2.8.5",
45
52
  "cross-env": "^7.0.3",
53
+ "express": "^4.18.2",
46
54
  "minimist": "^1.2.5",
47
55
  "node-sass": "^8.0.0",
48
56
  "rollup": "^2.68.0",
@@ -0,0 +1,94 @@
1
+ <template>
2
+ <div v-if="displayMode == displayModes.MODE_CARDS">
3
+ <draggable
4
+ v-model="items"
5
+ :group="draggableGroup"
6
+ :draggable="orderable ? '.item' : '.none'"
7
+ @start="drag = true"
8
+ @end="drag = false"
9
+ @sort="onSort()"
10
+ @add="onDraggableAdded($event)"
11
+ @change="onDraggableChange($event)"
12
+ :options="draggableOptions"
13
+ >
14
+ <masonry
15
+ :cols="{ default: 12 / colLg, 1400: 12 / colXl, 1200: 12 / colLg, 1000: 12 / colMd, 700: 12 / colSm, 400: 12 / colXs }"
16
+ :gutter="{ default: '15px', 700: '15px' }"
17
+ >
18
+ <div v-for="(item, itemIndex) in itemsList" v-bind:key="itemIndex" class="item">
19
+ <slot name="card" v-bind:item="item">
20
+ <ItemCard
21
+ :item="item"
22
+ :columns="columns"
23
+ :index="itemIndex"
24
+ :cardClass="cardClass"
25
+ :cardHideFooter="cardHideFooter"
26
+ :itemValue="itemValue"
27
+ :getStateValue="getStateValue"
28
+ :getArrayValue="getArrayValue"
29
+ :showItem="showItem"
30
+ :updateItem="updateItem"
31
+ :removeItem="removeItem"
32
+ />
33
+ </slot>
34
+ </div>
35
+ </masonry>
36
+ </draggable>
37
+
38
+ <p v-if="!loading && itemsList && itemsList.length == 0 && !infiniteScroll" class="p-3">
39
+ {{ messageEmptyResults }}
40
+ </p>
41
+ </div>
42
+ </template>
43
+
44
+ <script>
45
+ import Vue from 'vue';
46
+ import draggable from "vuedraggable";
47
+ import VueMasonry from 'vue-masonry-css';
48
+ import ItemCard from '../ItemCard.vue';
49
+
50
+ // Registrar el componente masonry usando el Plugin
51
+ Vue.use(VueMasonry);
52
+
53
+ export default {
54
+ name: 'CrudCards',
55
+ components: {
56
+ draggable,
57
+ ItemCard
58
+ },
59
+ inject: [
60
+ 'displayMode',
61
+ 'displayModes',
62
+ 'items',
63
+ 'draggableGroup',
64
+ 'orderable',
65
+ 'draggableOptions',
66
+ 'itemsList',
67
+ 'colLg',
68
+ 'colXl',
69
+ 'colMd',
70
+ 'colSm',
71
+ 'colXs',
72
+ 'columns',
73
+ 'cardClass',
74
+ 'cardHideFooter',
75
+ 'itemValue',
76
+ 'getStateValue',
77
+ 'getArrayValue',
78
+ 'showItem',
79
+ 'updateItem',
80
+ 'removeItem',
81
+ 'loading',
82
+ 'infiniteScroll',
83
+ 'messageEmptyResults',
84
+ 'onSort',
85
+ 'onDraggableAdded',
86
+ 'onDraggableChange'
87
+ ],
88
+ data() {
89
+ return {
90
+ drag: false
91
+ };
92
+ }
93
+ };
94
+ </script>
@@ -0,0 +1,29 @@
1
+ <template>
2
+ <div v-if="displayMode == displayModes.MODE_CUSTOM">
3
+ <div :class="listContainerClass">
4
+ <p v-if="!loading && itemsList && itemsList.length == 0 && !infiniteScroll" class="p-3">
5
+ {{ messageEmptyResults }}
6
+ </p>
7
+ <div :class="listItemClass" v-for="(item, index) in itemsList" v-bind:key="index">
8
+ <slot name="card" v-bind:item="item"> </slot>
9
+ </div>
10
+ </div>
11
+ </div>
12
+ </template>
13
+
14
+ <script>
15
+ export default {
16
+ name: 'CrudCustom',
17
+ inject: [
18
+ 'displayMode',
19
+ 'displayModes',
20
+ 'listContainerClass',
21
+ 'listItemClass',
22
+ 'loading',
23
+ 'items',
24
+ 'infiniteScroll',
25
+ 'messageEmptyResults',
26
+ 'itemsList'
27
+ ]
28
+ };
29
+ </script>
@@ -0,0 +1,104 @@
1
+ <template>
2
+ <div class="px-3 py-2">
3
+ <div v-for="(column, indexc) in columns" :key="indexc">
4
+ <div v-if="isColumnHasFilter(column)">
5
+ <slot :name="'sidebar-filter-' + column.prop" v-bind:column="column" v-bind:filter="filter"
6
+ v-bind:internalFilterByProp="internalFilterByProp" v-if="internalFilterByProp(column.prop)">
7
+ <div class="form-group" v-if="column.type == 'boolean'">
8
+ <label>{{ column.label }}</label>
9
+
10
+ <select class="form-control" v-model="internalFilterByProp(column.prop).value"
11
+ @change="onChangeFilter($event)">
12
+ <option value=""></option>
13
+ <option value="1">Sí</option>
14
+ <option value="0">No</option>
15
+ </select>
16
+ </div>
17
+ <div class="form-group" v-else-if="column.type == 'date'">
18
+ <div class="row">
19
+ <div class="col-6">
20
+ <b-form-datepicker v-model="internalFilterByProp(column.prop + '_from').value
21
+ " today-button reset-button close-button locale="es"></b-form-datepicker>
22
+ </div>
23
+ <div class="col-6">
24
+ <b-form-datepicker v-model="internalFilterByProp(column.prop + '_to').value
25
+ " today-button reset-button close-button locale="es"></b-form-datepicker>
26
+ </div>
27
+ </div>
28
+ </div>
29
+
30
+ <div class="form-group" v-else-if="column.type == 'state'">
31
+ <label>{{ column.label }}</label>
32
+
33
+ <select class="form-control" v-model="internalFilterByProp(column.prop).value"
34
+ @change="onChangeFilter($event)" v-if="optionsLoaded">
35
+ <option value=""></option>
36
+ <option :value="option.id ? option.id : option.value" v-for="option in column.options"
37
+ :key="option.id ? option.id : option.value">
38
+ {{
39
+ option.text
40
+ ? option.text
41
+ : option.label
42
+ ? option.label
43
+ : ""
44
+ }}
45
+ </option>
46
+ </select>
47
+ </div>
48
+
49
+ <div class="form-group" v-else-if="column.type == 'array'">
50
+ <label>{{ column.label }}</label>
51
+
52
+ <select class="form-control" v-model="internalFilterByProp(column.prop).value"
53
+ @change="onChangeFilter($event)" v-if="optionsLoaded">
54
+ <option value=""></option>
55
+ <template v-if="column.options">
56
+ <option :value="option.id ? option.id : option.value" v-for="option in column.options"
57
+ :key="option.id ? option.id : option.value">
58
+ {{
59
+ option.text
60
+ ? option.text
61
+ : option.label
62
+ ? option.label
63
+ : ""
64
+ }}
65
+ </option>
66
+ </template>
67
+ </select>
68
+ </div>
69
+
70
+ <div class="form-group" v-else>
71
+ <label>{{ column.label }}</label>
72
+
73
+ <input class="form-control" v-model.lazy="internalFilterByProp(column.prop).value"
74
+ @change="onChangeFilter($event)" />
75
+ </div>
76
+ </slot>
77
+ </div>
78
+ </div>
79
+
80
+ <div class="mt-3 d-flex justify-content-center">
81
+ <button class="btn btn-light" @click="resetFilters()">
82
+ Reset
83
+ </button>
84
+ <button class="btn btn-info" @click="onChangeFilter($event)">
85
+ Filtrar
86
+ </button>
87
+ </div>
88
+ </div>
89
+ </template>
90
+
91
+ <script>
92
+ export default {
93
+ name: 'CrudFilters',
94
+ inject: [
95
+ 'columns',
96
+ 'isColumnHasFilter',
97
+ 'filter',
98
+ 'internalFilterByProp',
99
+ 'optionsLoaded',
100
+ 'onChangeFilter',
101
+ 'resetFilters'
102
+ ]
103
+ };
104
+ </script>
@@ -0,0 +1,142 @@
1
+ <template>
2
+ <div class="crud-header" v-if="showHeader">
3
+ <h4 class="crud-title" v-if="showTitle">{{ title }}</h4>
4
+
5
+ <b-sidebar :visible="sidebarVisible" @hidden="closeSidebar" title="Filtrar" right shadow>
6
+ <CrudFilters />
7
+ </b-sidebar>
8
+
9
+ <div class="table-options">
10
+ <b-button-group class="mr-1">
11
+ <slot name="tableActions" v-bind:createItem="createItem" v-bind:toggleDisplayMode="toggleDisplayMode"
12
+ v-bind:loading="loading">
13
+ <slot name="tableActionsPrepend" v-bind:loading="loading"> </slot>
14
+
15
+ <b-button variant="info" @click="showImportModal()" v-if="showImport">
16
+ <b-icon-cloud-upload></b-icon-cloud-upload>{{ messageImport }}
17
+ </b-button>
18
+ <b-button variant="info" @click="showExportModal()" v-if="showExport">
19
+ <b-icon-cloud-download></b-icon-cloud-download>{{ messageExport }}
20
+ </b-button>
21
+ <b-button variant="info" v-if="showPrincipalSortBtn" @click="togglePrincipalSort()" :disabled="loading">
22
+ <b-icon-sort-numeric-down v-if="principalSort"></b-icon-sort-numeric-down>
23
+ <b-icon-sort-numeric-up v-else></b-icon-sort-numeric-up>
24
+ </b-button>
25
+ <b-button variant="danger" @click="confirmBulkDelete()"
26
+ v-if="bulkDelete"><b-icon-trash></b-icon-trash></b-button>
27
+ <b-button variant="success" v-if="showCreateBtn" @click="createItem()" :disabled="loading">
28
+ <b-icon-plus></b-icon-plus>{{ messageNew }}
29
+ </b-button>
30
+ <b-button variant="info" v-if="enableFilters" @click="toggleFilters()">Filtros</b-button>
31
+ <b-button variant="info" @click="refresh()"><b-icon-arrow-clockwise></b-icon-arrow-clockwise></b-button>
32
+ <b-button variant="info" @click="toggleDisplayMode()" :disabled="loading" v-if="displayModeToggler">
33
+ <b-icon-card-list v-if="displayMode == displayModes.MODE_TABLE"></b-icon-card-list>
34
+ <b-icon-table v-else-if="displayMode == displayModes.MODE_CARDS"></b-icon-table>
35
+ </b-button>
36
+
37
+ <div class="crud-search m-0" v-if="showSearch">
38
+ <b-input-group>
39
+ <b-input-group-prepend>
40
+ <b-button variant="info" @click="displaySearch = !displaySearch"
41
+ :class="{ open: displaySearch }"><b-icon-search></b-icon-search></b-button>
42
+ </b-input-group-prepend>
43
+ <b-form-input v-if="displaySearch" v-model="search" class="pl-2" type="search" required
44
+ :placeholder="searchPlaceholder" debounce="500"></b-form-input>
45
+ </b-input-group>
46
+
47
+ <slot name="tableActionsAppend" v-bind:loading="loading"> </slot>
48
+ </div>
49
+ </slot>
50
+ </b-button-group>
51
+ </div>
52
+ </div>
53
+ </template>
54
+
55
+ <script>
56
+ import CrudFilters from './CrudFilters.vue';
57
+
58
+ export default {
59
+ name: 'CrudHeader',
60
+ components: {
61
+ CrudFilters
62
+ },
63
+ inject: [
64
+ 'showHeader',
65
+ 'showTitle',
66
+ 'title',
67
+ 'filterSidebarOpen',
68
+ 'showImport',
69
+ 'showExport',
70
+ 'showPrincipalSortBtn',
71
+ 'principalSort',
72
+ 'bulkDelete',
73
+ 'showCreateBtn',
74
+ 'enableFilters',
75
+ 'displayModeToggler',
76
+ 'displayMode',
77
+ 'displayModes',
78
+ 'showSearch',
79
+ 'displaySearch',
80
+ 'search',
81
+ 'searchPlaceholder',
82
+ 'loading',
83
+ 'messageImport',
84
+ 'messageExport',
85
+ 'messageNew',
86
+ 'createItem',
87
+ 'toggleDisplayMode',
88
+ 'togglePrincipalSort',
89
+ 'confirmBulkDelete',
90
+ 'toggleFilters',
91
+ 'refresh'
92
+ ],
93
+ computed: {
94
+ sidebarVisible() {
95
+ // Acceder directamente al componente padre para obtener reactividad
96
+ return this.$parent ? this.$parent.filterSidebarOpen : this.filterSidebarOpen;
97
+ }
98
+ },
99
+ methods: {
100
+ closeSidebar() {
101
+ if (this.filterSidebarOpen) {
102
+ this.toggleFilters();
103
+ }
104
+ }
105
+ }
106
+ };
107
+ </script>
108
+
109
+ <style scoped>
110
+ .crud-header {
111
+ display: flex;
112
+ justify-content: space-between;
113
+ max-height: 3rem;
114
+ }
115
+
116
+ .crud-title {
117
+ margin: 0;
118
+ }
119
+
120
+ .crud-search {
121
+ max-width: 15rem;
122
+ }
123
+
124
+ .crud-search .btn {
125
+ border-top-left-radius: 0;
126
+ border-bottom-left-radius: 0;
127
+ border-top-right-radius: 0.375rem;
128
+ border-bottom-right-radius: 0.375rem;
129
+ }
130
+
131
+ .crud-search .btn.open {
132
+ border-top-right-radius: 0;
133
+ border-bottom-right-radius: 0;
134
+ }
135
+
136
+ .table-options {
137
+ margin-bottom: 1rem;
138
+ display: flex;
139
+ align-items: center;
140
+ justify-content: flex-end;
141
+ }
142
+ </style>
@@ -0,0 +1,20 @@
1
+ <template>
2
+ <div v-if="displayMode == displayModes.MODE_KANBAN">
3
+ <KanbanBoard />
4
+ </div>
5
+ </template>
6
+
7
+ <script>
8
+ import KanbanBoard from './kanban/KanbanBoard.vue';
9
+
10
+ export default {
11
+ name: 'CrudKanban',
12
+ components: {
13
+ KanbanBoard
14
+ },
15
+ inject: [
16
+ 'displayMode',
17
+ 'displayModes'
18
+ ]
19
+ };
20
+ </script>
@@ -0,0 +1,106 @@
1
+ <template>
2
+ <div>
3
+ <!-- Modal de formulario -->
4
+ <b-modal :id="'modal-form-item-' + modelName" hide-footer size="xl" :title="title" no-close-on-backdrop>
5
+ <b-overlay :show="loading" rounded="sm">
6
+ <template v-if="validate">
7
+ <form @submit="saveItem">
8
+ <slot name="form" v-bind:item="item" v-if="item">
9
+ <b-form-group label="Nombre:" description="Nombre ">
10
+ <b-form-input v-model="item.title" type="text" required placeholder="Nombre"></b-form-input>
11
+ </b-form-group>
12
+ </slot>
13
+ <b-button block type="submit" variant="success" :disabled="loading">
14
+ <b-spinner small v-if="loading"></b-spinner>{{ messageSave }}
15
+ </b-button>
16
+ </form>
17
+ </template>
18
+ <template v-if="!validate">
19
+ <slot name="form" v-bind:item="item" v-if="item">
20
+ <b-form-group :label="key" v-for="(value, key) in item" :key="key">
21
+ <b-form-input v-model="item[key]" type="text" required></b-form-input>
22
+ </b-form-group>
23
+ </slot>
24
+ <b-button block type="submit" variant="success" :disabled="loading" @click="saveItem()">
25
+ <b-spinner small v-if="loading"></b-spinner>{{ messageSave }}
26
+ </b-button>
27
+ </template>
28
+ </b-overlay>
29
+ </b-modal>
30
+
31
+ <!-- Modal de visualización -->
32
+ <b-modal :id="'modal-show-item-' + modelName" hide-footer size="xl" :title="title" no-close-on-backdrop>
33
+ <slot name="show" v-bind:item="item" v-if="item">
34
+ <b-list-group>
35
+ <b-list-group-item v-for="(value, key) in item" :key="key">
36
+ <b-row class="w-100">
37
+ <b-col cols="4" class="font-weight-bold">{{ key }}</b-col>
38
+ <b-col cols="8">{{ JSON.stringify(value) }}</b-col>
39
+ </b-row>
40
+ </b-list-group-item>
41
+ </b-list-group>
42
+ </slot>
43
+ </b-modal>
44
+
45
+ <!-- Modal de importación -->
46
+ <b-modal ref="modal-import" title="Importar" hide-footer v-if="showImport">
47
+ <slot name="import" v-bind:item="item" v-if="item">
48
+ <b-overlay :show="loading" rounded="sm">
49
+ <b-form-file v-model="fileImport" :state="Boolean(fileImport)" browse-text="Explorar"
50
+ placeholder="Importar..." drop-placeholder="Arrastrar Archivo aquí..."></b-form-file>
51
+ <div class="text-center mt-3">
52
+ <b-button variant="info" v-on:click="importItems()" :disabled="loading">
53
+ <b-icon-cloud-upload></b-icon-cloud-upload>
54
+ {{ loading ? "Cargando..." : "Importar" }}
55
+ </b-button>
56
+ </div>
57
+ </b-overlay>
58
+ </slot>
59
+ </b-modal>
60
+
61
+ <!-- Modal de exportación -->
62
+ <b-modal ref="modal-export" title="Exportar" hide-footer v-if="showExport">
63
+ <slot name="export" v-bind:item="item" v-if="item">
64
+ <b-overlay :show="loading" rounded="sm">
65
+
66
+ <p v-if="selectedItems.length">Se exportará {{ selectedItems.length }} elementos.</p>
67
+ <p v-else>Se exportará la consulta actual.</p>
68
+
69
+ <select class="form-control" v-model="exportFormat">
70
+ <option value="JSON">JSON</option>
71
+ <option value="XLSX">XLSX</option>
72
+ </select>
73
+
74
+ <div class="text-center mt-3">
75
+ <b-button variant="info" v-on:click="exportItems()" :disabled="loading">
76
+ <b-icon-cloud-upload></b-icon-cloud-upload>
77
+ {{ loading ? "Cargando..." : "Exportar" }}
78
+ </b-button>
79
+ </div>
80
+ </b-overlay>
81
+ </slot>
82
+ </b-modal>
83
+ </div>
84
+ </template>
85
+
86
+ <script>
87
+ export default {
88
+ name: 'CrudModals',
89
+ inject: [
90
+ 'modelName',
91
+ 'title',
92
+ 'loading',
93
+ 'validate',
94
+ 'item',
95
+ 'messageSave',
96
+ 'showImport',
97
+ 'showExport',
98
+ 'fileImport',
99
+ 'selectedItems',
100
+ 'exportFormat',
101
+ 'saveItem',
102
+ 'importItems',
103
+ 'exportItems'
104
+ ]
105
+ };
106
+ </script>
@@ -0,0 +1,133 @@
1
+ <template>
2
+ <div>
3
+ <!-- Infinite Loading -->
4
+ <infinite-loading
5
+ ref="infiniteLoading"
6
+ @infinite="infiniteHandler"
7
+ v-if="infiniteScroll"
8
+ :forceUseInfiniteWrapper="true"
9
+ :key="infiniteScrollKey"
10
+ >
11
+ <div slot="spinner">
12
+ <div class="text-center">{{ messageLoading }}</div>
13
+ </div>
14
+ <div slot="no-more">
15
+ <div class="text-center" v-if="!loading">{{ messageNoMore }}</div>
16
+ </div>
17
+ <div slot="no-results">
18
+ <div class="text-center" v-if="!loading">{{ items.length == 0 ? messageEmptyResults : messageNoMore }}</div>
19
+ </div>
20
+ </infinite-loading>
21
+
22
+ <!-- Paginador -->
23
+ <div class="paginator-container" v-if="!infiniteScroll">
24
+ <div class="paginator-data">
25
+ <span class="paginator-badge">
26
+ <span class="paginator-label">Filas:</span>
27
+ <span class="paginator-value">{{ pagination.total }}</span>
28
+ </span>
29
+ <span class="paginator-badge">
30
+ <span class="paginator-label">xPág:</span>
31
+ <span class="paginator-value">{{ pagination.per_page }}</span>
32
+ </span>
33
+ <span class="paginator-badge">
34
+ <span class="paginator-label">Pág:</span>
35
+ <span class="paginator-value">{{ pagination.current_page }}</span>
36
+ </span>
37
+ <span class="paginator-badge" v-if="selectedItems.length > 0">
38
+ <span class="paginator-label">Seleccionados:</span>
39
+ <span class="paginator-value">{{ selectedItems.length }}</span>
40
+ </span>
41
+ </div>
42
+
43
+ <div class="crud-paginator">
44
+ <b-pagination
45
+ v-if="showPaginator"
46
+ v-model="pagination.current_page"
47
+ :total-rows="pagination.total"
48
+ :per-page="pagination.per_page"
49
+ @change="onPaginationChange($event)"
50
+ ></b-pagination>
51
+ </div>
52
+ </div>
53
+ </div>
54
+ </template>
55
+
56
+ <script>
57
+ import InfiniteLoading from 'vue-infinite-loading';
58
+
59
+ export default {
60
+ name: 'CrudPagination',
61
+ components: {
62
+ InfiniteLoading
63
+ },
64
+ inject: [
65
+ 'infiniteScroll',
66
+ 'infiniteScrollKey',
67
+ 'messageLoading',
68
+ 'messageNoMore',
69
+ 'messageEmptyResults',
70
+ 'loading',
71
+ 'items',
72
+ 'pagination',
73
+ 'selectedItems',
74
+ 'showPaginator',
75
+ 'infiniteHandler',
76
+ 'onPaginationChange'
77
+ ]
78
+ };
79
+ </script>
80
+
81
+ <style scoped>
82
+ .paginator-container {
83
+ display: flex;
84
+ flex-direction: column;
85
+ align-items: center;
86
+ width: 100%;
87
+ margin-top: 1rem;
88
+ gap: 0.75rem;
89
+ }
90
+
91
+ .paginator-data {
92
+ display: flex;
93
+ flex-wrap: wrap;
94
+ justify-content: center;
95
+ align-items: center;
96
+ gap: 0.5rem;
97
+ font-size: 0.875rem;
98
+ }
99
+
100
+ .paginator-badge {
101
+ display: inline-flex;
102
+ align-items: center;
103
+ gap: 0.25rem;
104
+ padding: 0.375rem 0.625rem;
105
+ background-color: #f8f9fa;
106
+ border: 1px solid #dee2e6;
107
+ border-radius: 0.375rem;
108
+ color: #495057;
109
+ transition: all 0.2s ease;
110
+ }
111
+
112
+ .paginator-badge:hover {
113
+ background-color: #e9ecef;
114
+ border-color: #ced4da;
115
+ }
116
+
117
+ .paginator-label {
118
+ font-weight: 500;
119
+ color: #6c757d;
120
+ }
121
+
122
+ .paginator-value {
123
+ font-weight: 600;
124
+ color: #212529;
125
+ }
126
+
127
+ .crud-paginator {
128
+ display: flex;
129
+ justify-content: center;
130
+ align-items: center;
131
+ width: 100%;
132
+ }
133
+ </style>