vue-laravel-crud 2.0.5 → 2.0.6
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/dist/vue-laravel-crud.esm.js +1359 -228
- package/dist/vue-laravel-crud.min.js +3 -3
- package/dist/vue-laravel-crud.ssr.js +1378 -241
- package/package.json +1 -1
- package/src/ItemCard.vue +45 -4
- package/src/components/CrudCards.vue +19 -3
- package/src/components/CrudCustom.vue +14 -2
- package/src/components/CrudFilters.vue +152 -0
- package/src/components/CrudHeader.vue +12 -2
- package/src/components/CrudKanban.vue +19 -3
- package/src/components/CrudModals.vue +49 -21
- package/src/components/CrudTable.vue +18 -2
- package/src/components/kanban/KanbanBoard.vue +5 -1
- package/src/components/kanban/KanbanCard.vue +45 -4
- package/src/components/kanban/KanbanColumn.vue +6 -2
- package/src/components/table/TableCell.vue +66 -20
- package/src/components/table/TableHeader.vue +21 -6
- package/src/components/table/TableRow.vue +5 -1
- package/src/vue-laravel-crud.vue +25 -4
package/package.json
CHANGED
package/src/ItemCard.vue
CHANGED
|
@@ -32,6 +32,12 @@
|
|
|
32
32
|
<span v-else-if="column.type === 'array'">
|
|
33
33
|
{{ getArrayValue(itemValue(column, item), column.displayProp, column.options) }}
|
|
34
34
|
</span>
|
|
35
|
+
<span v-else-if="column.type === 'money' || column.type === 'price'">
|
|
36
|
+
{{ formatMoney(itemValue(column, item), column) }}
|
|
37
|
+
</span>
|
|
38
|
+
<span v-else-if="column.type === 'number' && (column.thousandsSeparator || column.decimalSeparator || column.decimals !== undefined)">
|
|
39
|
+
{{ formatNumber(itemValue(column, item), column) }}
|
|
40
|
+
</span>
|
|
35
41
|
<span v-else>
|
|
36
42
|
{{ itemValue(column, item) }}
|
|
37
43
|
</span>
|
|
@@ -41,11 +47,14 @@
|
|
|
41
47
|
</slot>
|
|
42
48
|
<template v-slot:footer>
|
|
43
49
|
<b-button-group>
|
|
44
|
-
<slot name="
|
|
50
|
+
<slot name="rowActions" v-bind:item="item" v-bind:index="index" v-bind:showItem="showItem"
|
|
45
51
|
v-bind:updateItem="updateItem" v-bind:removeItem="removeItem">
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
<slot name="rowAction" v-bind:item="item" v-bind:index="index" v-bind:showItem="showItem"
|
|
53
|
+
v-bind:updateItem="updateItem" v-bind:removeItem="removeItem">
|
|
54
|
+
<b-button variant="primary" @click="showItem(item.id, index)"><b-icon-eye></b-icon-eye></b-button>
|
|
55
|
+
<b-button variant="secondary" @click="updateItem(item.id, index)"><b-icon-pencil></b-icon-pencil></b-button>
|
|
56
|
+
<b-button variant="danger" @click="removeItem(item.id, index)"><b-icon-trash></b-icon-trash></b-button>
|
|
57
|
+
</slot>
|
|
49
58
|
</slot>
|
|
50
59
|
</b-button-group>
|
|
51
60
|
</template>
|
|
@@ -80,6 +89,38 @@
|
|
|
80
89
|
return this.getStateOptions(this.itemValue(column, item), column.options);
|
|
81
90
|
}
|
|
82
91
|
return [];
|
|
92
|
+
},
|
|
93
|
+
formatNumber(value, column) {
|
|
94
|
+
if (value === null || value === undefined || value === '') {
|
|
95
|
+
return '';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const numValue = parseFloat(value);
|
|
99
|
+
if (isNaN(numValue)) {
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const thousandsSep = column.thousandsSeparator || '.';
|
|
104
|
+
const decimalSep = column.decimalSeparator || ',';
|
|
105
|
+
const decimals = column.decimals !== undefined ? column.decimals : (numValue % 1 === 0 ? 0 : 2);
|
|
106
|
+
|
|
107
|
+
// Formatear número con separadores
|
|
108
|
+
const parts = numValue.toFixed(decimals).split('.');
|
|
109
|
+
const integerPart = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, thousandsSep);
|
|
110
|
+
const decimalPart = parts[1] || '';
|
|
111
|
+
|
|
112
|
+
if (decimals > 0 && decimalPart) {
|
|
113
|
+
return `${integerPart}${decimalSep}${decimalPart}`;
|
|
114
|
+
}
|
|
115
|
+
return integerPart;
|
|
116
|
+
},
|
|
117
|
+
formatMoney(value, column) {
|
|
118
|
+
const formatted = this.formatNumber(value, column);
|
|
119
|
+
if (formatted === '') {
|
|
120
|
+
return '';
|
|
121
|
+
}
|
|
122
|
+
const symbol = column.symbol || '$';
|
|
123
|
+
return `${symbol}${formatted}`;
|
|
83
124
|
}
|
|
84
125
|
}
|
|
85
126
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div v-if="
|
|
2
|
+
<div v-if="currentDisplayMode == displayModes.MODE_CARDS">
|
|
3
3
|
<draggable
|
|
4
4
|
v-model="items"
|
|
5
5
|
:group="draggableGroup"
|
|
@@ -30,8 +30,12 @@
|
|
|
30
30
|
:getArrayValue="getArrayValue"
|
|
31
31
|
:showItem="showItem"
|
|
32
32
|
:updateItem="updateItem"
|
|
33
|
-
:removeItem="removeItem"
|
|
34
|
-
|
|
33
|
+
:removeItem="removeItem"
|
|
34
|
+
>
|
|
35
|
+
<template v-for="(slot, name) in $scopedSlots" v-slot:[name]="slotProps">
|
|
36
|
+
<slot :name="name" v-bind="slotProps" />
|
|
37
|
+
</template>
|
|
38
|
+
</ItemCard>
|
|
35
39
|
</slot>
|
|
36
40
|
</div>
|
|
37
41
|
</masonry>
|
|
@@ -93,6 +97,18 @@ export default {
|
|
|
93
97
|
return {
|
|
94
98
|
drag: false
|
|
95
99
|
};
|
|
100
|
+
},
|
|
101
|
+
computed: {
|
|
102
|
+
currentDisplayMode() {
|
|
103
|
+
if (!this.displayMode) return 1;
|
|
104
|
+
if (this.displayMode.value !== undefined) {
|
|
105
|
+
return this.displayMode.value;
|
|
106
|
+
}
|
|
107
|
+
if (typeof this.displayMode === 'function') {
|
|
108
|
+
return this.displayMode();
|
|
109
|
+
}
|
|
110
|
+
return this.displayMode;
|
|
111
|
+
}
|
|
96
112
|
}
|
|
97
113
|
};
|
|
98
114
|
</script>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div v-if="
|
|
2
|
+
<div v-if="currentDisplayMode == displayModes.MODE_CUSTOM">
|
|
3
3
|
<div :class="listContainerClass">
|
|
4
4
|
<p v-if="!loading && itemsList && itemsList.length == 0 && !infiniteScroll" class="p-3">
|
|
5
5
|
{{ messageEmptyResults }}
|
|
@@ -24,6 +24,18 @@ export default {
|
|
|
24
24
|
'infiniteScroll',
|
|
25
25
|
'messageEmptyResults',
|
|
26
26
|
'itemsList'
|
|
27
|
-
]
|
|
27
|
+
],
|
|
28
|
+
computed: {
|
|
29
|
+
currentDisplayMode() {
|
|
30
|
+
if (!this.displayMode) return 1;
|
|
31
|
+
if (this.displayMode.value !== undefined) {
|
|
32
|
+
return this.displayMode.value;
|
|
33
|
+
}
|
|
34
|
+
if (typeof this.displayMode === 'function') {
|
|
35
|
+
return this.displayMode();
|
|
36
|
+
}
|
|
37
|
+
return this.displayMode;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
28
40
|
};
|
|
29
41
|
</script>
|
|
@@ -87,6 +87,112 @@
|
|
|
87
87
|
</div>
|
|
88
88
|
</div>
|
|
89
89
|
|
|
90
|
+
<!-- Filtros custom -->
|
|
91
|
+
<div v-for="(customFilter, indexcf) in customFilters" :key="'custom-' + indexcf">
|
|
92
|
+
<div v-if="isCustomFilterEnabled(customFilter)">
|
|
93
|
+
<!-- Slot personalizado para filtro custom -->
|
|
94
|
+
<slot :name="'sidebar-filter-custom-' + customFilter.prop" v-bind:column="customFilter" v-bind:filter="filter"
|
|
95
|
+
v-bind:internalFilterByProp="internalFilterByProp" v-bind:getFilterForColumn="getFilterForColumn">
|
|
96
|
+
|
|
97
|
+
<!-- Si type es una función callback -->
|
|
98
|
+
<RenderCustomFilter
|
|
99
|
+
v-if="typeof customFilter.type === 'function'"
|
|
100
|
+
:render-function="customFilter.type"
|
|
101
|
+
:custom-filter="customFilter"
|
|
102
|
+
:filter="filter"
|
|
103
|
+
:internal-filter-by-prop="internalFilterByProp"
|
|
104
|
+
:get-filter-for-column="getFilterForColumn"
|
|
105
|
+
:on-change-filter="onChangeFilter"
|
|
106
|
+
/>
|
|
107
|
+
|
|
108
|
+
<!-- Si type es string, usar la misma lógica que las columnas -->
|
|
109
|
+
<template v-else>
|
|
110
|
+
<div class="form-group" v-if="customFilter.type == 'boolean'">
|
|
111
|
+
<label>{{ customFilter.label }}</label>
|
|
112
|
+
|
|
113
|
+
<select class="form-control" v-model="getFilterForColumn(customFilter).value"
|
|
114
|
+
@change="onChangeFilter($event)">
|
|
115
|
+
<option value=""></option>
|
|
116
|
+
<option value="1">Sí</option>
|
|
117
|
+
<option value="0">No</option>
|
|
118
|
+
</select>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<div class="form-group" v-else-if="customFilter.type == 'date'">
|
|
122
|
+
<label>{{ customFilter.label }}</label>
|
|
123
|
+
<div class="row">
|
|
124
|
+
<div class="col-6">
|
|
125
|
+
<b-form-datepicker v-model="getFilterForDateFrom(customFilter).value
|
|
126
|
+
" today-button reset-button close-button locale="es"></b-form-datepicker>
|
|
127
|
+
</div>
|
|
128
|
+
<div class="col-6">
|
|
129
|
+
<b-form-datepicker v-model="getFilterForDateTo(customFilter).value
|
|
130
|
+
" today-button reset-button close-button locale="es"></b-form-datepicker>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<div class="form-group" v-else-if="customFilter.type == 'number' || customFilter.type == 'money'">
|
|
136
|
+
<label>{{ customFilter.label }}</label>
|
|
137
|
+
<div class="row">
|
|
138
|
+
<div class="col-6">
|
|
139
|
+
<input
|
|
140
|
+
type="number"
|
|
141
|
+
class="form-control"
|
|
142
|
+
v-model.number="getFilterForDateFrom(customFilter).value"
|
|
143
|
+
:step="customFilter.type == 'money' ? '0.01' : '1'"
|
|
144
|
+
@change="onChangeFilter($event)"
|
|
145
|
+
placeholder="Desde" />
|
|
146
|
+
</div>
|
|
147
|
+
<div class="col-6">
|
|
148
|
+
<input
|
|
149
|
+
type="number"
|
|
150
|
+
class="form-control"
|
|
151
|
+
v-model.number="getFilterForDateTo(customFilter).value"
|
|
152
|
+
:step="customFilter.type == 'money' ? '0.01' : '1'"
|
|
153
|
+
@change="onChangeFilter($event)"
|
|
154
|
+
placeholder="Hasta" />
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<div class="form-group" v-else-if="customFilter.type == 'state'">
|
|
160
|
+
<label>{{ customFilter.label }}</label>
|
|
161
|
+
|
|
162
|
+
<select class="form-control" v-model="getFilterForColumn(customFilter).value"
|
|
163
|
+
@change="onChangeFilter($event)" v-if="customFilter.options && Array.isArray(customFilter.options)">
|
|
164
|
+
<option value=""></option>
|
|
165
|
+
<option :value="option.value" v-for="option in customFilter.options"
|
|
166
|
+
:key="option.value || option.id">
|
|
167
|
+
{{ option.text }}
|
|
168
|
+
</option>
|
|
169
|
+
</select>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<div class="form-group" v-else-if="customFilter.type == 'array'">
|
|
173
|
+
<label>{{ customFilter.label }}</label>
|
|
174
|
+
|
|
175
|
+
<select class="form-control" v-model="getFilterForColumn(customFilter).value"
|
|
176
|
+
@change="onChangeFilter($event)" v-if="customFilter.options && Array.isArray(customFilter.options)">
|
|
177
|
+
<option value=""></option>
|
|
178
|
+
<option :value="option.value" v-for="option in customFilter.options"
|
|
179
|
+
:key="option.value || option.id">
|
|
180
|
+
{{ option.text }}
|
|
181
|
+
</option>
|
|
182
|
+
</select>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<div class="form-group" v-else>
|
|
186
|
+
<label>{{ customFilter.label }}</label>
|
|
187
|
+
|
|
188
|
+
<input class="form-control" v-model.lazy="getFilterForColumn(customFilter).value"
|
|
189
|
+
@change="onChangeFilter($event)" />
|
|
190
|
+
</div>
|
|
191
|
+
</template>
|
|
192
|
+
</slot>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
|
|
90
196
|
<div class="mt-3 d-flex justify-content-center">
|
|
91
197
|
<button class="btn btn-light" @click="resetFilters()">
|
|
92
198
|
Reset
|
|
@@ -99,11 +205,57 @@
|
|
|
99
205
|
</template>
|
|
100
206
|
|
|
101
207
|
<script>
|
|
208
|
+
// Componente funcional para renderizar filtros custom con callback
|
|
209
|
+
const RenderCustomFilter = {
|
|
210
|
+
functional: true,
|
|
211
|
+
props: {
|
|
212
|
+
renderFunction: {
|
|
213
|
+
type: Function,
|
|
214
|
+
required: true
|
|
215
|
+
},
|
|
216
|
+
customFilter: {
|
|
217
|
+
type: Object,
|
|
218
|
+
required: true
|
|
219
|
+
},
|
|
220
|
+
filter: {
|
|
221
|
+
type: Array,
|
|
222
|
+
default: () => []
|
|
223
|
+
},
|
|
224
|
+
internalFilterByProp: {
|
|
225
|
+
type: Function,
|
|
226
|
+
required: true
|
|
227
|
+
},
|
|
228
|
+
getFilterForColumn: {
|
|
229
|
+
type: Function,
|
|
230
|
+
required: true
|
|
231
|
+
},
|
|
232
|
+
onChangeFilter: {
|
|
233
|
+
type: Function,
|
|
234
|
+
required: true
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
render(h, context) {
|
|
238
|
+
const { renderFunction, customFilter, filter, internalFilterByProp, getFilterForColumn, onChangeFilter } = context.props;
|
|
239
|
+
return renderFunction(h, {
|
|
240
|
+
column: customFilter,
|
|
241
|
+
filter: filter,
|
|
242
|
+
internalFilterByProp: internalFilterByProp,
|
|
243
|
+
getFilterForColumn: getFilterForColumn,
|
|
244
|
+
onChangeFilter: onChangeFilter
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
102
249
|
export default {
|
|
103
250
|
name: 'CrudFilters',
|
|
251
|
+
components: {
|
|
252
|
+
RenderCustomFilter
|
|
253
|
+
},
|
|
104
254
|
inject: [
|
|
105
255
|
'columns',
|
|
256
|
+
'customFilters',
|
|
106
257
|
'isColumnHasFilter',
|
|
258
|
+
'isCustomFilterEnabled',
|
|
107
259
|
'filter',
|
|
108
260
|
'internalFilterByProp',
|
|
109
261
|
'optionsLoaded',
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
<b-button variant="info" v-if="enableFilters" @click="toggleFilters()">Filtros</b-button>
|
|
31
31
|
<b-button variant="info" @click="refresh()"><b-icon-arrow-clockwise></b-icon-arrow-clockwise></b-button>
|
|
32
32
|
<b-button variant="info" @click="toggleDisplayMode()" :disabled="loading" v-if="displayModeToggler">
|
|
33
|
-
<b-icon-card-list v-if="
|
|
34
|
-
<b-icon-table v-else-if="
|
|
33
|
+
<b-icon-card-list v-if="currentDisplayMode == displayModes.MODE_TABLE"></b-icon-card-list>
|
|
34
|
+
<b-icon-table v-else-if="currentDisplayMode == displayModes.MODE_CARDS"></b-icon-table>
|
|
35
35
|
</b-button>
|
|
36
36
|
|
|
37
37
|
<div class="crud-search m-0" v-if="showSearch">
|
|
@@ -94,6 +94,16 @@ export default {
|
|
|
94
94
|
sidebarVisible() {
|
|
95
95
|
// Acceder directamente al componente padre para obtener reactividad
|
|
96
96
|
return this.$parent ? this.$parent.filterSidebarOpen : this.filterSidebarOpen;
|
|
97
|
+
},
|
|
98
|
+
currentDisplayMode() {
|
|
99
|
+
if (!this.displayMode) return 1;
|
|
100
|
+
if (this.displayMode.value !== undefined) {
|
|
101
|
+
return this.displayMode.value;
|
|
102
|
+
}
|
|
103
|
+
if (typeof this.displayMode === 'function') {
|
|
104
|
+
return this.displayMode();
|
|
105
|
+
}
|
|
106
|
+
return this.displayMode;
|
|
97
107
|
}
|
|
98
108
|
},
|
|
99
109
|
methods: {
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div v-if="
|
|
3
|
-
<KanbanBoard
|
|
2
|
+
<div v-if="currentDisplayMode == displayModes.MODE_KANBAN">
|
|
3
|
+
<KanbanBoard>
|
|
4
|
+
<template v-for="(slot, name) in $scopedSlots" v-slot:[name]="slotProps">
|
|
5
|
+
<slot :name="name" v-bind="slotProps" />
|
|
6
|
+
</template>
|
|
7
|
+
</KanbanBoard>
|
|
4
8
|
</div>
|
|
5
9
|
</template>
|
|
6
10
|
|
|
@@ -15,6 +19,18 @@ export default {
|
|
|
15
19
|
inject: [
|
|
16
20
|
'displayMode',
|
|
17
21
|
'displayModes'
|
|
18
|
-
]
|
|
22
|
+
],
|
|
23
|
+
computed: {
|
|
24
|
+
currentDisplayMode() {
|
|
25
|
+
if (!this.displayMode) return 1;
|
|
26
|
+
if (this.displayMode.value !== undefined) {
|
|
27
|
+
return this.displayMode.value;
|
|
28
|
+
}
|
|
29
|
+
if (typeof this.displayMode === 'function') {
|
|
30
|
+
return this.displayMode();
|
|
31
|
+
}
|
|
32
|
+
return this.displayMode;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
19
35
|
};
|
|
20
36
|
</script>
|
|
@@ -5,22 +5,26 @@
|
|
|
5
5
|
<b-overlay :show="loading" rounded="sm">
|
|
6
6
|
<template v-if="validate">
|
|
7
7
|
<form @submit="saveItem">
|
|
8
|
-
<
|
|
9
|
-
<
|
|
10
|
-
<b-form-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
<template v-if="reactiveItem">
|
|
9
|
+
<slot name="form" v-bind:item="reactiveItem">
|
|
10
|
+
<b-form-group label="Nombre:" description="Nombre ">
|
|
11
|
+
<b-form-input v-model="reactiveItem.title" type="text" required placeholder="Nombre"></b-form-input>
|
|
12
|
+
</b-form-group>
|
|
13
|
+
</slot>
|
|
14
|
+
</template>
|
|
13
15
|
<b-button block type="submit" variant="success" :disabled="loading">
|
|
14
16
|
<b-spinner small v-if="loading"></b-spinner>{{ messageSave }}
|
|
15
17
|
</b-button>
|
|
16
18
|
</form>
|
|
17
19
|
</template>
|
|
18
20
|
<template v-if="!validate">
|
|
19
|
-
<
|
|
20
|
-
<
|
|
21
|
-
<b-form-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
<template v-if="reactiveItem">
|
|
22
|
+
<slot name="form" v-bind:item="reactiveItem">
|
|
23
|
+
<b-form-group :label="key" v-for="(value, key) in reactiveItem" :key="key">
|
|
24
|
+
<b-form-input v-model="reactiveItem[key]" type="text" required></b-form-input>
|
|
25
|
+
</b-form-group>
|
|
26
|
+
</slot>
|
|
27
|
+
</template>
|
|
24
28
|
<b-button block type="submit" variant="success" :disabled="loading" @click="saveItem()">
|
|
25
29
|
<b-spinner small v-if="loading"></b-spinner>{{ messageSave }}
|
|
26
30
|
</b-button>
|
|
@@ -30,16 +34,18 @@
|
|
|
30
34
|
|
|
31
35
|
<!-- Modal de visualización -->
|
|
32
36
|
<b-modal :id="'modal-show-item-' + modelName" hide-footer size="xl" :title="title" no-close-on-backdrop>
|
|
33
|
-
<
|
|
34
|
-
<
|
|
35
|
-
<b-list-group
|
|
36
|
-
<b-
|
|
37
|
-
<b-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
<template v-if="reactiveItem">
|
|
38
|
+
<slot name="show" v-bind:item="reactiveItem">
|
|
39
|
+
<b-list-group>
|
|
40
|
+
<b-list-group-item v-for="(value, key) in reactiveItem" :key="key">
|
|
41
|
+
<b-row class="w-100">
|
|
42
|
+
<b-col cols="4" class="font-weight-bold">{{ key }}</b-col>
|
|
43
|
+
<b-col cols="8">{{ JSON.stringify(value) }}</b-col>
|
|
44
|
+
</b-row>
|
|
45
|
+
</b-list-group-item>
|
|
46
|
+
</b-list-group>
|
|
47
|
+
</slot>
|
|
48
|
+
</template>
|
|
43
49
|
</b-modal>
|
|
44
50
|
|
|
45
51
|
<!-- Modal de importación -->
|
|
@@ -92,6 +98,7 @@ export default {
|
|
|
92
98
|
'loading',
|
|
93
99
|
'validate',
|
|
94
100
|
'item',
|
|
101
|
+
'getItem',
|
|
95
102
|
'messageSave',
|
|
96
103
|
'showImport',
|
|
97
104
|
'showExport',
|
|
@@ -101,6 +108,27 @@ export default {
|
|
|
101
108
|
'saveItem',
|
|
102
109
|
'importItems',
|
|
103
110
|
'exportItems'
|
|
104
|
-
]
|
|
111
|
+
],
|
|
112
|
+
computed: {
|
|
113
|
+
// Computed property para asegurar reactividad del item inyectado
|
|
114
|
+
reactiveItem() {
|
|
115
|
+
// Si hay una función getItem, usarla para obtener el item actual
|
|
116
|
+
if (this.getItem && typeof this.getItem === 'function') {
|
|
117
|
+
return this.getItem();
|
|
118
|
+
}
|
|
119
|
+
// Si no, usar el item inyectado directamente
|
|
120
|
+
return this.item;
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
watch: {
|
|
124
|
+
// Watch el item inyectado para forzar actualización
|
|
125
|
+
item: {
|
|
126
|
+
handler() {
|
|
127
|
+
this.$forceUpdate();
|
|
128
|
+
},
|
|
129
|
+
deep: true,
|
|
130
|
+
immediate: true
|
|
131
|
+
}
|
|
132
|
+
}
|
|
105
133
|
};
|
|
106
134
|
</script>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div :class="['table-responsive', tableContainerClass]" v-if="
|
|
2
|
+
<div :class="['table-responsive', tableContainerClass]" v-if="currentDisplayMode == displayModes.MODE_TABLE">
|
|
3
3
|
<table :class="['table table-hover table-striped w-100', tableClass]">
|
|
4
4
|
<TableHeader />
|
|
5
5
|
|
|
@@ -21,7 +21,11 @@
|
|
|
21
21
|
:item="item"
|
|
22
22
|
:index="index"
|
|
23
23
|
:grouped="grouped"
|
|
24
|
-
|
|
24
|
+
>
|
|
25
|
+
<template v-for="(slot, name) in $scopedSlots" v-slot:[name]="slotProps">
|
|
26
|
+
<slot :name="name" v-bind="slotProps" />
|
|
27
|
+
</template>
|
|
28
|
+
</TableRow>
|
|
25
29
|
</draggable>
|
|
26
30
|
</table>
|
|
27
31
|
|
|
@@ -65,6 +69,18 @@ export default {
|
|
|
65
69
|
return {
|
|
66
70
|
drag: false
|
|
67
71
|
};
|
|
72
|
+
},
|
|
73
|
+
computed: {
|
|
74
|
+
currentDisplayMode() {
|
|
75
|
+
if (!this.displayMode) return 1;
|
|
76
|
+
if (this.displayMode.value !== undefined) {
|
|
77
|
+
return this.displayMode.value;
|
|
78
|
+
}
|
|
79
|
+
if (typeof this.displayMode === 'function') {
|
|
80
|
+
return this.displayMode();
|
|
81
|
+
}
|
|
82
|
+
return this.displayMode;
|
|
83
|
+
}
|
|
68
84
|
}
|
|
69
85
|
};
|
|
70
86
|
</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="
|
|
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
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
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
|
};
|
|
@@ -30,8 +30,12 @@
|
|
|
30
30
|
:getArrayValue="getArrayValue"
|
|
31
31
|
:showItem="showItem"
|
|
32
32
|
:updateItem="updateItem"
|
|
33
|
-
:removeItem="removeItem"
|
|
34
|
-
|
|
33
|
+
:removeItem="removeItem"
|
|
34
|
+
>
|
|
35
|
+
<template v-for="(slot, name) in $scopedSlots" v-slot:[name]="slotProps">
|
|
36
|
+
<slot :name="name" v-bind="slotProps" />
|
|
37
|
+
</template>
|
|
38
|
+
</KanbanCard>
|
|
35
39
|
</slot>
|
|
36
40
|
</div>
|
|
37
41
|
</draggable>
|