vue-laravel-crud 2.0.1 → 2.0.5

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,6 @@
1
1
  {
2
2
  "name": "vue-laravel-crud",
3
- "version": "2.0.1",
3
+ "version": "2.0.5",
4
4
  "type": "module",
5
5
  "description": "",
6
6
  "homepage": "https://github.com/clonixdev/vue-laravel-crud",
@@ -21,7 +21,9 @@
21
21
  "build": "cross-env NODE_ENV=production rollup --config build/rollup.config.js",
22
22
  "build:ssr": "cross-env NODE_ENV=production rollup --config build/rollup.config.js --format cjs",
23
23
  "build:es": "cross-env NODE_ENV=production rollup --config build/rollup.config.js --format es",
24
- "build:unpkg": "cross-env NODE_ENV=production rollup --config build/rollup.config.js --format iife"
24
+ "build:unpkg": "cross-env NODE_ENV=production rollup --config build/rollup.config.js --format iife",
25
+ "build:demo": "cross-env NODE_ENV=production vue-cli-service build --mode production dev/demo/main.js",
26
+ "build:gh-pages": "npm run build:demo"
25
27
  },
26
28
  "dependencies": {
27
29
  "axios": "^1.3.5",
@@ -51,6 +53,7 @@
51
53
  "cors": "^2.8.5",
52
54
  "cross-env": "^7.0.3",
53
55
  "express": "^4.18.2",
56
+ "marked": "^4.3.0",
54
57
  "minimist": "^1.2.5",
55
58
  "node-sass": "^8.0.0",
56
59
  "rollup": "^2.68.0",
package/src/ItemCard.vue CHANGED
@@ -15,7 +15,19 @@
15
15
  {{ itemValue(column, item) }}
16
16
  </span>
17
17
  <span v-else-if="column.type === 'state'">
18
- {{ getStateValue(itemValue(column, item), column.options) }}
18
+ <template v-if="getStateOptionsForColumn(column, item).length > 0">
19
+ <b-badge
20
+ v-for="(option, optIndex) in getStateOptionsForColumn(column, item)"
21
+ :key="optIndex"
22
+ :variant="getStateBadgeVariant(option)"
23
+ class="mr-1"
24
+ >
25
+ {{ option.text }}
26
+ </b-badge>
27
+ </template>
28
+ <span v-else>
29
+ {{ itemValue(column, item) }}
30
+ </span>
19
31
  </span>
20
32
  <span v-else-if="column.type === 'array'">
21
33
  {{ getArrayValue(itemValue(column, item), column.displayProp, column.options) }}
@@ -55,11 +67,21 @@
55
67
  cardHideFooter: Boolean,
56
68
  itemValue: Function,
57
69
  getStateValue: Function,
70
+ getStateOptions: Function,
71
+ getStateBadgeVariant: Function,
58
72
  getArrayValue: Function,
59
73
  showItem: Function,
60
74
  updateItem: Function,
61
75
  removeItem: Function,
62
76
  },
77
+ methods: {
78
+ getStateOptionsForColumn(column, item) {
79
+ if (column.type === 'state' && column.options) {
80
+ return this.getStateOptions(this.itemValue(column, item), column.options);
81
+ }
82
+ return [];
83
+ }
84
+ }
63
85
  };
64
86
  </script>
65
87
 
@@ -24,7 +24,9 @@
24
24
  :cardClass="cardClass"
25
25
  :cardHideFooter="cardHideFooter"
26
26
  :itemValue="itemValue"
27
- :getStateValue="getStateValue"
27
+ :getStateValue="getStateValue"
28
+ :getStateOptions="getStateOptions"
29
+ :getStateBadgeVariant="getStateBadgeVariant"
28
30
  :getArrayValue="getArrayValue"
29
31
  :showItem="showItem"
30
32
  :updateItem="updateItem"
@@ -74,6 +76,8 @@ export default {
74
76
  'cardHideFooter',
75
77
  'itemValue',
76
78
  'getStateValue',
79
+ 'getStateOptions',
80
+ 'getStateBadgeVariant',
77
81
  'getArrayValue',
78
82
  'showItem',
79
83
  'updateItem',
@@ -3,11 +3,11 @@
3
3
  <div v-for="(column, indexc) in columns" :key="indexc">
4
4
  <div v-if="isColumnHasFilter(column)">
5
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)">
6
+ v-bind:internalFilterByProp="internalFilterByProp" v-bind:getFilterForColumn="getFilterForColumn">
7
7
  <div class="form-group" v-if="column.type == 'boolean'">
8
8
  <label>{{ column.label }}</label>
9
9
 
10
- <select class="form-control" v-model="internalFilterByProp(column.prop).value"
10
+ <select class="form-control" v-model="getFilterForColumn(column).value"
11
11
  @change="onChangeFilter($event)">
12
12
  <option value=""></option>
13
13
  <option value="1">Sí</option>
@@ -17,31 +17,49 @@
17
17
  <div class="form-group" v-else-if="column.type == 'date'">
18
18
  <div class="row">
19
19
  <div class="col-6">
20
- <b-form-datepicker v-model="internalFilterByProp(column.prop + '_from').value
20
+ <b-form-datepicker v-model="getFilterForDateFrom(column).value
21
21
  " today-button reset-button close-button locale="es"></b-form-datepicker>
22
22
  </div>
23
23
  <div class="col-6">
24
- <b-form-datepicker v-model="internalFilterByProp(column.prop + '_to').value
24
+ <b-form-datepicker v-model="getFilterForDateTo(column).value
25
25
  " today-button reset-button close-button locale="es"></b-form-datepicker>
26
26
  </div>
27
27
  </div>
28
28
  </div>
29
29
 
30
+ <div class="form-group" v-else-if="column.type == 'number' || column.type == 'money'">
31
+ <label>{{ column.label }}</label>
32
+ <div class="row">
33
+ <div class="col-6">
34
+ <input
35
+ type="number"
36
+ class="form-control"
37
+ v-model.number="getFilterForDateFrom(column).value"
38
+ :step="column.type == 'money' ? '0.01' : '1'"
39
+ @change="onChangeFilter($event)"
40
+ placeholder="Desde" />
41
+ </div>
42
+ <div class="col-6">
43
+ <input
44
+ type="number"
45
+ class="form-control"
46
+ v-model.number="getFilterForDateTo(column).value"
47
+ :step="column.type == 'money' ? '0.01' : '1'"
48
+ @change="onChangeFilter($event)"
49
+ placeholder="Hasta" />
50
+ </div>
51
+ </div>
52
+ </div>
53
+
30
54
  <div class="form-group" v-else-if="column.type == 'state'">
31
55
  <label>{{ column.label }}</label>
32
56
 
33
- <select class="form-control" v-model="internalFilterByProp(column.prop).value"
34
- @change="onChangeFilter($event)" v-if="optionsLoaded">
57
+ <select class="form-control" v-model="getFilterForColumn(column).value"
58
+ @change="onChangeFilter($event)" v-if="column.options && Array.isArray(column.options)">
35
59
  <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
- }}
60
+ <option :value="option.value" v-for="option in column.options"
61
+ :key="option.value || option.id">
62
+ {{ option.text }}
45
63
  </option>
46
64
  </select>
47
65
  </div>
@@ -49,28 +67,20 @@
49
67
  <div class="form-group" v-else-if="column.type == 'array'">
50
68
  <label>{{ column.label }}</label>
51
69
 
52
- <select class="form-control" v-model="internalFilterByProp(column.prop).value"
53
- @change="onChangeFilter($event)" v-if="optionsLoaded">
70
+ <select class="form-control" v-model="getFilterForColumn(column).value"
71
+ @change="onChangeFilter($event)" v-if="column.options && Array.isArray(column.options)">
54
72
  <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>
73
+ <option :value="option.value" v-for="option in column.options"
74
+ :key="option.value || option.id">
75
+ {{ option.text }}
76
+ </option>
67
77
  </select>
68
78
  </div>
69
79
 
70
80
  <div class="form-group" v-else>
71
81
  <label>{{ column.label }}</label>
72
82
 
73
- <input class="form-control" v-model.lazy="internalFilterByProp(column.prop).value"
83
+ <input class="form-control" v-model.lazy="getFilterForColumn(column).value"
74
84
  @change="onChangeFilter($event)" />
75
85
  </div>
76
86
  </slot>
@@ -98,7 +108,60 @@ export default {
98
108
  'internalFilterByProp',
99
109
  'optionsLoaded',
100
110
  'onChangeFilter',
101
- 'resetFilters'
102
- ]
111
+ 'resetFilters',
112
+ 'setupFilters',
113
+ 'internalFilters'
114
+ ],
115
+ methods: {
116
+ // Método helper para obtener el filtro de forma segura, creándolo si no existe
117
+ getFilterForColumn(column) {
118
+ let filter = this.internalFilterByProp(column.prop);
119
+
120
+ // Si el filtro no existe, intentar inicializar los filtros
121
+ if (!filter) {
122
+ // Verificar si hay filtros inicializados
123
+ if (this.internalFilters && this.internalFilters.length === 0) {
124
+ this.setupFilters();
125
+ // Intentar obtener el filtro nuevamente después de inicializar
126
+ filter = this.internalFilterByProp(column.prop);
127
+ }
128
+ }
129
+
130
+ // Si aún no existe, crear un objeto temporal para evitar errores
131
+ if (!filter) {
132
+ return {
133
+ value: null
134
+ };
135
+ }
136
+
137
+ return filter;
138
+ },
139
+
140
+ // Método helper específico para campos de fecha (from)
141
+ getFilterForDateFrom(column) {
142
+ const filter = this.internalFilterByProp(column.prop + '_from');
143
+ if (!filter) {
144
+ if (this.internalFilters && this.internalFilters.length === 0) {
145
+ this.setupFilters();
146
+ return this.internalFilterByProp(column.prop + '_from') || { value: null };
147
+ }
148
+ return { value: null };
149
+ }
150
+ return filter;
151
+ },
152
+
153
+ // Método helper específico para campos de fecha (to)
154
+ getFilterForDateTo(column) {
155
+ const filter = this.internalFilterByProp(column.prop + '_to');
156
+ if (!filter) {
157
+ if (this.internalFilters && this.internalFilters.length === 0) {
158
+ this.setupFilters();
159
+ return this.internalFilterByProp(column.prop + '_to') || { value: null };
160
+ }
161
+ return { value: null };
162
+ }
163
+ return filter;
164
+ }
165
+ }
103
166
  };
104
167
  </script>
@@ -26,18 +26,33 @@
26
26
  <span class="paginator-label">Filas:</span>
27
27
  <span class="paginator-value">{{ pagination.total }}</span>
28
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>
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>
41
56
  </div>
42
57
 
43
58
  <div class="crud-paginator">
@@ -73,28 +88,42 @@ export default {
73
88
  'selectedItems',
74
89
  'showPaginator',
75
90
  'infiniteHandler',
76
- 'onPaginationChange'
77
- ]
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
+ }
78
106
  };
79
107
  </script>
80
108
 
81
109
  <style scoped>
82
110
  .paginator-container {
83
- display: flex;
84
- flex-direction: column;
111
+ display: grid;
112
+ grid-template-columns: 1fr auto 1fr;
85
113
  align-items: center;
86
114
  width: 100%;
87
115
  margin-top: 1rem;
88
- gap: 0.75rem;
116
+ gap: 1rem;
89
117
  }
90
118
 
91
119
  .paginator-data {
92
120
  display: flex;
93
- flex-wrap: wrap;
94
- justify-content: center;
121
+ flex-wrap: nowrap;
122
+ justify-content: flex-start;
95
123
  align-items: center;
96
124
  gap: 0.5rem;
97
125
  font-size: 0.875rem;
126
+ grid-column: 1;
98
127
  }
99
128
 
100
129
  .paginator-badge {
@@ -124,10 +153,48 @@ export default {
124
153
  color: #212529;
125
154
  }
126
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
+
127
173
  .crud-paginator {
128
174
  display: flex;
129
175
  justify-content: center;
130
176
  align-items: center;
131
- width: 100%;
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;
132
199
  }
133
200
  </style>
@@ -8,6 +8,8 @@
8
8
  :columns="columns"
9
9
  :itemValue="itemValue"
10
10
  :getStateValue="getStateValue"
11
+ :getStateOptions="getStateOptions"
12
+ :getStateBadgeVariant="getStateBadgeVariant"
11
13
  :getArrayValue="getArrayValue"
12
14
  :showItem="showItem"
13
15
  :updateItem="updateItem"
@@ -34,6 +36,8 @@ export default {
34
36
  'columns',
35
37
  'itemValue',
36
38
  'getStateValue',
39
+ 'getStateOptions',
40
+ 'getStateBadgeVariant',
37
41
  'getArrayValue',
38
42
  'showItem',
39
43
  'updateItem',
@@ -22,7 +22,19 @@
22
22
  {{ itemValue(column, item) }}
23
23
  </span>
24
24
  <span v-else-if="column.type === 'state'">
25
- {{ getStateValue(itemValue(column, item), column.options) }}
25
+ <template v-if="getStateOptionsForColumn(column, item).length > 0">
26
+ <b-badge
27
+ v-for="(option, optIndex) in getStateOptionsForColumn(column, item)"
28
+ :key="optIndex"
29
+ :variant="getStateBadgeVariant(option)"
30
+ class="mr-1"
31
+ >
32
+ {{ option.text }}
33
+ </b-badge>
34
+ </template>
35
+ <span v-else>
36
+ {{ itemValue(column, item) }}
37
+ </span>
26
38
  </span>
27
39
  <span v-else-if="column.type === 'array'">
28
40
  {{ getArrayValue(itemValue(column, item), column.displayProp, column.options) }}
@@ -61,10 +73,20 @@ export default {
61
73
  cardHideFooter: Boolean,
62
74
  itemValue: Function,
63
75
  getStateValue: Function,
76
+ getStateOptions: Function,
77
+ getStateBadgeVariant: Function,
64
78
  getArrayValue: Function,
65
79
  showItem: Function,
66
80
  updateItem: Function,
67
81
  removeItem: Function,
82
+ },
83
+ methods: {
84
+ getStateOptionsForColumn(column, item) {
85
+ if (column.type === 'state' && column.options) {
86
+ return this.getStateOptions(this.itemValue(column, item), column.options);
87
+ }
88
+ return [];
89
+ }
68
90
  }
69
91
  };
70
92
  </script>
@@ -24,7 +24,9 @@
24
24
  :cardClass="cardClass"
25
25
  :cardHideFooter="cardHideFooter"
26
26
  :itemValue="itemValue"
27
- :getStateValue="getStateValue"
27
+ :getStateValue="getStateValue"
28
+ :getStateOptions="getStateOptions"
29
+ :getStateBadgeVariant="getStateBadgeVariant"
28
30
  :getArrayValue="getArrayValue"
29
31
  :showItem="showItem"
30
32
  :updateItem="updateItem"
@@ -53,6 +55,8 @@ export default {
53
55
  columns: Array,
54
56
  itemValue: Function,
55
57
  getStateValue: Function,
58
+ getStateOptions: Function,
59
+ getStateBadgeVariant: Function,
56
60
  getArrayValue: Function,
57
61
  showItem: Function,
58
62
  updateItem: Function,
@@ -1,5 +1,6 @@
1
1
  <template>
2
- <td :scope="column.prop == 'id' ? 'row' : ''">
2
+ <td :scope="column.prop == 'id' ? 'row' : ''"
3
+ :class="{ 'actions-cell': column.type == 'actions' }">
3
4
  <slot :name="'cell-' + column.prop" v-bind:item="item" v-bind:index="index" v-bind:itemindex="index"
4
5
  v-bind:columnindex="columnIndex">
5
6
  <span v-if="column.type == 'boolean'">
@@ -25,10 +26,24 @@
25
26
  <b-form-checkbox v-model="item.selected" @change="onCheckSelect($event, item)">
26
27
  </b-form-checkbox>
27
28
  </span>
28
- <span v-else-if="column.type == 'state' && optionsLoaded">
29
- {{
30
- getStateValue(itemValue(column, item), column.options)
31
- }}
29
+ <span v-else-if="column.type == 'checkbox'">
30
+ <b-form-checkbox v-model="item.selected" @change="onCheckSelect($event, item)">
31
+ </b-form-checkbox>
32
+ </span>
33
+ <span v-else-if="column.type == 'state'">
34
+ <template v-if="stateOptions.length > 0">
35
+ <b-badge
36
+ v-for="(option, optIndex) in stateOptions"
37
+ :key="optIndex"
38
+ :variant="getStateBadgeVariant(option)"
39
+ class="mr-1"
40
+ >
41
+ {{ option.text }}
42
+ </b-badge>
43
+ </template>
44
+ <span v-else>
45
+ {{ itemValue(column, item) }}
46
+ </span>
32
47
  </span>
33
48
  <span v-else-if="column.type == 'array' && optionsLoaded">
34
49
  {{
@@ -40,11 +55,35 @@
40
55
  }}
41
56
  </span>
42
57
  <span v-else>
58
+
43
59
  {{ itemValue(column, item) }}
44
60
  </span>
45
61
  </slot>
46
62
 
47
- <b-button-group v-if="column.type == 'actions'">
63
+ <!-- Modo dropdown cuando useDropdown está activo -->
64
+ <b-dropdown v-if="column.type == 'actions' && column.useDropdown"
65
+ variant="secondary"
66
+ size="sm"
67
+ class="actions-dropdown">
68
+ <template #button-content>
69
+ <b-icon-list></b-icon-list>
70
+ </template>
71
+ <slot name="rowAction" v-bind:item="item" v-bind:index="index" v-bind:showItem="showItem"
72
+ v-bind:updateItem="updateItem" v-bind:removeItem="removeItem">
73
+ <b-dropdown-item @click="showItem(item.id, index)">
74
+ <b-icon-eye></b-icon-eye> Ver
75
+ </b-dropdown-item>
76
+ <b-dropdown-item @click="updateItem(item.id, index)">
77
+ <b-icon-pencil></b-icon-pencil> Editar
78
+ </b-dropdown-item>
79
+ <b-dropdown-item @click="removeItem(item.id, index)" class="text-danger">
80
+ <b-icon-trash></b-icon-trash> Eliminar
81
+ </b-dropdown-item>
82
+ </slot>
83
+ </b-dropdown>
84
+
85
+ <!-- Modo botones normal (comportamiento original) -->
86
+ <b-button-group v-else-if="column.type == 'actions'" class="actions-button-group">
48
87
  <slot name="rowAction" v-bind:item="item" v-bind:index="index" v-bind:showItem="showItem"
49
88
  v-bind:updateItem="updateItem" v-bind:removeItem="removeItem">
50
89
  <b-button variant="primary" @click="showItem(item.id, index)">
@@ -75,6 +114,8 @@ export default {
75
114
  inject: [
76
115
  'itemValue',
77
116
  'getStateValue',
117
+ 'getStateOptions',
118
+ 'getStateBadgeVariant',
78
119
  'getArrayValue',
79
120
  'onCheckSelect',
80
121
  'showItem',
@@ -86,6 +127,40 @@ export default {
86
127
  return {
87
128
  moment: moment
88
129
  };
130
+ },
131
+ computed: {
132
+ stateOptions() {
133
+ // Permitir usar opciones incluso si optionsLoaded es false, ya que getStateOptions normaliza internamente
134
+ if (this.column.type === 'state' && this.column.options && Array.isArray(this.column.options)) {
135
+ const itemVal = this.itemValue(this.column, this.item);
136
+ const options = this.column.options;
137
+ const result = this.getStateOptions(itemVal, options);
138
+ return result;
139
+ }
140
+ return [];
141
+ }
89
142
  }
90
143
  };
91
144
  </script>
145
+
146
+ <style scoped>
147
+ /* Fijar ancho de la columna de acciones */
148
+ .actions-cell {
149
+ width: 1%;
150
+ white-space: nowrap;
151
+ }
152
+
153
+ .actions-button-group {
154
+ display: inline-flex;
155
+ flex-wrap: nowrap;
156
+ }
157
+
158
+ .actions-dropdown {
159
+ display: inline-block;
160
+ }
161
+
162
+ /* Asegurar que los botones no se expandan */
163
+ .actions-button-group .btn {
164
+ flex-shrink: 0;
165
+ }
166
+ </style>