vue-laravel-crud 2.0.5 → 2.0.7

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.
@@ -1,200 +1,207 @@
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
- <b-dropdown
30
- variant="outline-secondary"
31
- size="sm"
32
- class="paginator-dropdown"
33
- :text="`xPág: ${pagination.per_page}`"
34
- >
35
- <b-dropdown-item
36
- v-for="option in perPageOptions"
37
- :key="option"
38
- @click="onPerPageChange(option)"
39
- :active="pagination.per_page === option"
40
- >
41
- {{ option }}
42
- </b-dropdown-item>
43
- </b-dropdown>
44
- <b-dropdown
45
- v-if="selectedItemsCount > 0"
46
- variant="outline-secondary"
47
- size="sm"
48
- class="paginator-dropdown paginator-badge-dropdown"
49
- :text="`Seleccionados: ${selectedItemsCount}`"
50
- >
51
- <b-dropdown-item @click="clearSelection">
52
- <b-icon-x-circle class="mr-1"></b-icon-x-circle>
53
- Limpiar selección
54
- </b-dropdown-item>
55
- </b-dropdown>
56
- </div>
57
-
58
- <div class="crud-paginator">
59
- <b-pagination
60
- v-if="showPaginator"
61
- v-model="pagination.current_page"
62
- :total-rows="pagination.total"
63
- :per-page="pagination.per_page"
64
- @change="onPaginationChange($event)"
65
- ></b-pagination>
66
- </div>
67
- </div>
68
- </div>
69
- </template>
70
-
71
- <script>
72
- import InfiniteLoading from 'vue-infinite-loading';
73
-
74
- export default {
75
- name: 'CrudPagination',
76
- components: {
77
- InfiniteLoading
78
- },
79
- inject: [
80
- 'infiniteScroll',
81
- 'infiniteScrollKey',
82
- 'messageLoading',
83
- 'messageNoMore',
84
- 'messageEmptyResults',
85
- 'loading',
86
- 'items',
87
- 'pagination',
88
- 'selectedItems',
89
- 'showPaginator',
90
- 'infiniteHandler',
91
- 'onPaginationChange',
92
- 'onPerPageChange',
93
- 'clearSelection'
94
- ],
95
- data() {
96
- return {
97
- perPageOptions: [10, 20, 50, 100]
98
- };
99
- },
100
- computed: {
101
- selectedItemsCount() {
102
- // Computed para forzar reactividad del contador
103
- return this.selectedItems ? this.selectedItems.length : 0;
104
- }
105
- }
106
- };
107
- </script>
108
-
109
- <style scoped>
110
- .paginator-container {
111
- display: grid;
112
- grid-template-columns: 1fr auto 1fr;
113
- align-items: center;
114
- width: 100%;
115
- margin-top: 1rem;
116
- gap: 1rem;
117
- }
118
-
119
- .paginator-data {
120
- display: flex;
121
- flex-wrap: nowrap;
122
- justify-content: flex-start;
123
- align-items: center;
124
- gap: 0.5rem;
125
- font-size: 0.875rem;
126
- grid-column: 1;
127
- }
128
-
129
- .paginator-badge {
130
- display: inline-flex;
131
- align-items: center;
132
- gap: 0.25rem;
133
- padding: 0.375rem 0.625rem;
134
- background-color: #f8f9fa;
135
- border: 1px solid #dee2e6;
136
- border-radius: 0.375rem;
137
- color: #495057;
138
- transition: all 0.2s ease;
139
- }
140
-
141
- .paginator-badge:hover {
142
- background-color: #e9ecef;
143
- border-color: #ced4da;
144
- }
145
-
146
- .paginator-label {
147
- font-weight: 500;
148
- color: #6c757d;
149
- }
150
-
151
- .paginator-value {
152
- font-weight: 600;
153
- color: #212529;
154
- }
155
-
156
- .paginator-dropdown {
157
- font-size: 0.875rem;
158
- }
159
-
160
- .paginator-dropdown >>> .btn {
161
- padding: 0.375rem 0.625rem;
162
- font-size: 0.875rem;
163
- background-color: #f8f9fa;
164
- border: 1px solid #dee2e6;
165
- color: #495057;
166
- }
167
-
168
- .paginator-dropdown >>> .btn:hover {
169
- background-color: #e9ecef;
170
- border-color: #ced4da;
171
- }
172
-
173
- .crud-paginator {
174
- display: flex;
175
- justify-content: center;
176
- align-items: center;
177
- grid-column: 2;
178
- }
179
-
180
- .paginator-badge-dropdown {
181
- z-index: 1;
182
- position: relative;
183
- }
184
-
185
- .paginator-badge-dropdown >>> .btn {
186
- padding: 0.375rem 0.625rem;
187
- font-size: 0.875rem;
188
- background-color: #f8f9fa;
189
- border: 1px solid #dee2e6;
190
- color: #495057;
191
- display: inline-flex;
192
- align-items: center;
193
- gap: 0.25rem;
194
- }
195
-
196
- .paginator-badge-dropdown >>> .btn:hover {
197
- background-color: #e9ecef;
198
- border-color: #ced4da;
199
- }
200
- </style>
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="!loadingValue">{{ messageNoMore }}</div>
16
+ </div>
17
+ <div slot="no-results">
18
+ <div class="text-center" v-if="!loadingValue && firstLoadValue">{{ 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
+ <b-dropdown
30
+ variant="outline-secondary"
31
+ size="sm"
32
+ class="paginator-dropdown"
33
+ :text="`xPág: ${pagination.per_page}`"
34
+ >
35
+ <b-dropdown-item
36
+ v-for="option in perPageOptions"
37
+ :key="option"
38
+ @click="onPerPageChange(option)"
39
+ :active="pagination.per_page === option"
40
+ >
41
+ {{ option }}
42
+ </b-dropdown-item>
43
+ </b-dropdown>
44
+ <b-dropdown
45
+ v-if="selectedItemsCount > 0"
46
+ variant="outline-secondary"
47
+ size="sm"
48
+ class="paginator-dropdown paginator-badge-dropdown"
49
+ :text="`Seleccionados: ${selectedItemsCount}`"
50
+ >
51
+ <b-dropdown-item @click="clearSelection">
52
+ <b-icon-x-circle class="mr-1"></b-icon-x-circle>
53
+ Limpiar selección
54
+ </b-dropdown-item>
55
+ </b-dropdown>
56
+ </div>
57
+
58
+ <div class="crud-paginator">
59
+ <b-pagination
60
+ v-if="showPaginator"
61
+ v-model="pagination.current_page"
62
+ :total-rows="pagination.total"
63
+ :per-page="pagination.per_page"
64
+ @change="onPaginationChange($event)"
65
+ ></b-pagination>
66
+ </div>
67
+ </div>
68
+ </div>
69
+ </template>
70
+
71
+ <script>
72
+ import InfiniteLoading from 'vue-infinite-loading';
73
+
74
+ export default {
75
+ name: 'CrudPagination',
76
+ components: {
77
+ InfiniteLoading
78
+ },
79
+ inject: [
80
+ 'infiniteScroll',
81
+ 'infiniteScrollKey',
82
+ 'messageLoading',
83
+ 'messageNoMore',
84
+ 'messageEmptyResults',
85
+ 'loading',
86
+ 'firstLoad',
87
+ 'items',
88
+ 'pagination',
89
+ 'selectedItems',
90
+ 'showPaginator',
91
+ 'infiniteHandler',
92
+ 'onPaginationChange',
93
+ 'onPerPageChange',
94
+ 'clearSelection'
95
+ ],
96
+ data() {
97
+ return {
98
+ perPageOptions: [10, 20, 50, 100]
99
+ };
100
+ },
101
+ computed: {
102
+ selectedItemsCount() {
103
+ // Computed para forzar reactividad del contador
104
+ return this.selectedItems ? this.selectedItems.length : 0;
105
+ },
106
+ loadingValue() {
107
+ return this.loading && this.loading.value !== undefined ? this.loading.value : this.loading;
108
+ },
109
+ firstLoadValue() {
110
+ return this.firstLoad && this.firstLoad.value !== undefined ? this.firstLoad.value : this.firstLoad;
111
+ }
112
+ }
113
+ };
114
+ </script>
115
+
116
+ <style scoped>
117
+ .paginator-container {
118
+ display: grid;
119
+ grid-template-columns: 1fr auto 1fr;
120
+ align-items: center;
121
+ width: 100%;
122
+ margin-top: 1rem;
123
+ gap: 1rem;
124
+ }
125
+
126
+ .paginator-data {
127
+ display: flex;
128
+ flex-wrap: nowrap;
129
+ justify-content: flex-start;
130
+ align-items: center;
131
+ gap: 0.5rem;
132
+ font-size: 0.875rem;
133
+ grid-column: 1;
134
+ }
135
+
136
+ .paginator-badge {
137
+ display: inline-flex;
138
+ align-items: center;
139
+ gap: 0.25rem;
140
+ padding: 0.375rem 0.625rem;
141
+ background-color: #f8f9fa;
142
+ border: 1px solid #dee2e6;
143
+ border-radius: 0.375rem;
144
+ color: #495057;
145
+ transition: all 0.2s ease;
146
+ }
147
+
148
+ .paginator-badge:hover {
149
+ background-color: #e9ecef;
150
+ border-color: #ced4da;
151
+ }
152
+
153
+ .paginator-label {
154
+ font-weight: 500;
155
+ color: #6c757d;
156
+ }
157
+
158
+ .paginator-value {
159
+ font-weight: 600;
160
+ color: #212529;
161
+ }
162
+
163
+ .paginator-dropdown {
164
+ font-size: 0.875rem;
165
+ }
166
+
167
+ .paginator-dropdown >>> .btn {
168
+ padding: 0.375rem 0.625rem;
169
+ font-size: 0.875rem;
170
+ background-color: #f8f9fa;
171
+ border: 1px solid #dee2e6;
172
+ color: #495057;
173
+ }
174
+
175
+ .paginator-dropdown >>> .btn:hover {
176
+ background-color: #e9ecef;
177
+ border-color: #ced4da;
178
+ }
179
+
180
+ .crud-paginator {
181
+ display: flex;
182
+ justify-content: center;
183
+ align-items: center;
184
+ grid-column: 2;
185
+ }
186
+
187
+ .paginator-badge-dropdown {
188
+ z-index: 1;
189
+ position: relative;
190
+ }
191
+
192
+ .paginator-badge-dropdown >>> .btn {
193
+ padding: 0.375rem 0.625rem;
194
+ font-size: 0.875rem;
195
+ background-color: #f8f9fa;
196
+ border: 1px solid #dee2e6;
197
+ color: #495057;
198
+ display: inline-flex;
199
+ align-items: center;
200
+ gap: 0.25rem;
201
+ }
202
+
203
+ .paginator-badge-dropdown >>> .btn:hover {
204
+ background-color: #e9ecef;
205
+ border-color: #ced4da;
206
+ }
207
+ </style>
@@ -1,70 +1,103 @@
1
- <template>
2
- <div :class="['table-responsive', tableContainerClass]" v-if="displayMode == displayModes.MODE_TABLE">
3
- <table :class="['table table-hover table-striped w-100', tableClass]">
4
- <TableHeader />
5
-
6
- <draggable
7
- :list="items"
8
- :group="draggableGroup"
9
- tag="tbody"
10
- :draggable="orderable ? '.item' : '.none'"
11
- @start="drag = true"
12
- @end="drag = false"
13
- @sort="onSort()"
14
- @add="onDraggableAdded($event)"
15
- @change="onDraggableChange($event)"
16
- :options="draggableOptions"
17
- >
18
- <TableRow
19
- v-for="(item, index) in itemsList"
20
- v-bind:key="index"
21
- :item="item"
22
- :index="index"
23
- :grouped="grouped"
24
- />
25
- </draggable>
26
- </table>
27
-
28
- <p v-if="!loading && itemsList && itemsList.length == 0 && !infiniteScroll" class="p-3">
29
- {{ messageEmptyResults }}
30
- </p>
31
- </div>
32
- </template>
33
-
34
- <script>
35
- import draggable from "vuedraggable";
36
- import TableHeader from './table/TableHeader.vue';
37
- import TableRow from './table/TableRow.vue';
38
-
39
- export default {
40
- name: 'CrudTable',
41
- components: {
42
- draggable,
43
- TableHeader,
44
- TableRow
45
- },
46
- inject: [
47
- 'displayMode',
48
- 'displayModes',
49
- 'tableContainerClass',
50
- 'tableClass',
51
- 'items',
52
- 'draggableGroup',
53
- 'orderable',
54
- 'draggableOptions',
55
- 'itemsList',
56
- 'grouped',
57
- 'loading',
58
- 'infiniteScroll',
59
- 'messageEmptyResults',
60
- 'onSort',
61
- 'onDraggableAdded',
62
- 'onDraggableChange'
63
- ],
64
- data() {
65
- return {
66
- drag: false
67
- };
68
- }
69
- };
70
- </script>
1
+ <template>
2
+ <div :class="['table-responsive', tableContainerClass]" v-if="currentDisplayMode == displayModes.MODE_TABLE">
3
+ <!-- Spinner durante la carga inicial -->
4
+ <div v-if="loadingValue || !firstLoadValue" class="text-center p-5">
5
+ <b-spinner variant="primary" label="Cargando..."></b-spinner>
6
+ <p class="mt-2">{{ messageLoading }}</p>
7
+ </div>
8
+
9
+ <!-- Tabla con datos -->
10
+ <template v-else>
11
+ <table :class="['table table-hover table-striped w-100', tableClass]">
12
+ <TableHeader />
13
+
14
+ <draggable
15
+ :list="items"
16
+ :group="draggableGroup"
17
+ tag="tbody"
18
+ :draggable="orderable ? '.item' : '.none'"
19
+ @start="drag = true"
20
+ @end="drag = false"
21
+ @sort="onSort()"
22
+ @add="onDraggableAdded($event)"
23
+ @change="onDraggableChange($event)"
24
+ :options="draggableOptions"
25
+ >
26
+ <TableRow
27
+ v-for="(item, index) in itemsList"
28
+ v-bind:key="index"
29
+ :item="item"
30
+ :index="index"
31
+ :grouped="grouped"
32
+ >
33
+ <template v-for="(slot, name) in $scopedSlots" v-slot:[name]="slotProps">
34
+ <slot :name="name" v-bind="slotProps" />
35
+ </template>
36
+ </TableRow>
37
+ </draggable>
38
+ </table>
39
+
40
+ <p v-if="firstLoadValue && itemsList && itemsList.length == 0 && !infiniteScroll" class="p-3">
41
+ {{ messageEmptyResults }}
42
+ </p>
43
+ </template>
44
+ </div>
45
+ </template>
46
+
47
+ <script>
48
+ import draggable from "vuedraggable";
49
+ import TableHeader from './table/TableHeader.vue';
50
+ import TableRow from './table/TableRow.vue';
51
+
52
+ export default {
53
+ name: 'CrudTable',
54
+ components: {
55
+ draggable,
56
+ TableHeader,
57
+ TableRow
58
+ },
59
+ inject: [
60
+ 'displayMode',
61
+ 'displayModes',
62
+ 'tableContainerClass',
63
+ 'tableClass',
64
+ 'items',
65
+ 'draggableGroup',
66
+ 'orderable',
67
+ 'draggableOptions',
68
+ 'itemsList',
69
+ 'grouped',
70
+ 'loading',
71
+ 'firstLoad',
72
+ 'infiniteScroll',
73
+ 'messageEmptyResults',
74
+ 'messageLoading',
75
+ 'onSort',
76
+ 'onDraggableAdded',
77
+ 'onDraggableChange'
78
+ ],
79
+ data() {
80
+ return {
81
+ drag: false
82
+ };
83
+ },
84
+ computed: {
85
+ currentDisplayMode() {
86
+ if (!this.displayMode) return 1;
87
+ if (this.displayMode.value !== undefined) {
88
+ return this.displayMode.value;
89
+ }
90
+ if (typeof this.displayMode === 'function') {
91
+ return this.displayMode();
92
+ }
93
+ return this.displayMode;
94
+ },
95
+ loadingValue() {
96
+ return this.loading && this.loading.value !== undefined ? this.loading.value : this.loading;
97
+ },
98
+ firstLoadValue() {
99
+ return this.firstLoad && this.firstLoad.value !== undefined ? this.firstLoad.value : this.firstLoad;
100
+ }
101
+ }
102
+ };
103
+ </script>
@@ -17,7 +17,11 @@
17
17
  :cardClass="cardClass"
18
18
  :cardHideFooter="cardHideFooter"
19
19
  @draggableChange="onDraggableChange"
20
- />
20
+ >
21
+ <template v-for="(slot, name) in $scopedSlots" v-slot:[name]="slotProps">
22
+ <slot :name="name" v-bind="slotProps" />
23
+ </template>
24
+ </KanbanColumn>
21
25
  </div>
22
26
  </div>
23
27
  </template>
@@ -39,6 +39,12 @@
39
39
  <span v-else-if="column.type === 'array'">
40
40
  {{ getArrayValue(itemValue(column, item), column.displayProp, column.options) }}
41
41
  </span>
42
+ <span v-else-if="column.type === 'money' || column.type === 'price'">
43
+ {{ formatMoney(itemValue(column, item), column) }}
44
+ </span>
45
+ <span v-else-if="column.type === 'number' && (column.thousandsSeparator || column.decimalSeparator || column.decimals !== undefined)">
46
+ {{ formatNumber(itemValue(column, item), column) }}
47
+ </span>
42
48
  <span v-else>
43
49
  {{ itemValue(column, item) }}
44
50
  </span>
@@ -50,11 +56,14 @@
50
56
 
51
57
  <template v-slot:footer>
52
58
  <b-button-group size="sm">
53
- <slot name="rowAction" v-bind:item="item" v-bind:index="index" v-bind:showItem="showItem"
59
+ <slot name="rowActions" v-bind:item="item" v-bind:index="index" v-bind:showItem="showItem"
54
60
  v-bind:updateItem="updateItem" v-bind:removeItem="removeItem">
55
- <b-button variant="primary" @click="showItem(item.id, index)"><b-icon-eye></b-icon-eye></b-button>
56
- <b-button variant="secondary" @click="updateItem(item.id, index)"><b-icon-pencil></b-icon-pencil></b-button>
57
- <b-button variant="danger" @click="removeItem(item.id, index)"><b-icon-trash></b-icon-trash></b-button>
61
+ <slot name="rowAction" v-bind:item="item" v-bind:index="index" v-bind:showItem="showItem"
62
+ v-bind:updateItem="updateItem" v-bind:removeItem="removeItem">
63
+ <b-button variant="primary" @click="showItem(item.id, index)"><b-icon-eye></b-icon-eye></b-button>
64
+ <b-button variant="secondary" @click="updateItem(item.id, index)"><b-icon-pencil></b-icon-pencil></b-button>
65
+ <b-button variant="danger" @click="removeItem(item.id, index)"><b-icon-trash></b-icon-trash></b-button>
66
+ </slot>
58
67
  </slot>
59
68
  </b-button-group>
60
69
  </template>
@@ -86,6 +95,38 @@ export default {
86
95
  return this.getStateOptions(this.itemValue(column, item), column.options);
87
96
  }
88
97
  return [];
98
+ },
99
+ formatNumber(value, column) {
100
+ if (value === null || value === undefined || value === '') {
101
+ return '';
102
+ }
103
+
104
+ const numValue = parseFloat(value);
105
+ if (isNaN(numValue)) {
106
+ return value;
107
+ }
108
+
109
+ const thousandsSep = column.thousandsSeparator || '.';
110
+ const decimalSep = column.decimalSeparator || ',';
111
+ const decimals = column.decimals !== undefined ? column.decimals : (numValue % 1 === 0 ? 0 : 2);
112
+
113
+ // Formatear número con separadores
114
+ const parts = numValue.toFixed(decimals).split('.');
115
+ const integerPart = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, thousandsSep);
116
+ const decimalPart = parts[1] || '';
117
+
118
+ if (decimals > 0 && decimalPart) {
119
+ return `${integerPart}${decimalSep}${decimalPart}`;
120
+ }
121
+ return integerPart;
122
+ },
123
+ formatMoney(value, column) {
124
+ const formatted = this.formatNumber(value, column);
125
+ if (formatted === '') {
126
+ return '';
127
+ }
128
+ const symbol = column.symbol || '$';
129
+ return `${symbol}${formatted}`;
89
130
  }
90
131
  }
91
132
  };