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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue-laravel-crud",
3
- "version": "2.0.5",
3
+ "version": "2.0.6",
4
4
  "type": "module",
5
5
  "description": "",
6
6
  "homepage": "https://github.com/clonixdev/vue-laravel-crud",
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="rowAction" v-bind:item="item" v-bind:index="index" v-bind:showItem="showItem"
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
- <b-button variant="primary" @click="showItem(item.id, index)"><b-icon-eye></b-icon-eye></b-button>
47
- <b-button variant="secondary" @click="updateItem(item.id, index)"><b-icon-pencil></b-icon-pencil></b-button>
48
- <b-button variant="danger" @click="removeItem(item.id, index)"><b-icon-trash></b-icon-trash></b-button>
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="displayMode == displayModes.MODE_CARDS">
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="displayMode == displayModes.MODE_CUSTOM">
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="displayMode == displayModes.MODE_TABLE"></b-icon-card-list>
34
- <b-icon-table v-else-if="displayMode == displayModes.MODE_CARDS"></b-icon-table>
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="displayMode == displayModes.MODE_KANBAN">
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
- <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>
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
- <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>
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
- <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>
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="displayMode == displayModes.MODE_TABLE">
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="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
  };
@@ -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>