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.
@@ -0,0 +1,70 @@
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>
@@ -0,0 +1,65 @@
1
+ <template>
2
+ <div class="kanban-board">
3
+ <div v-for="(column, colIndex) in items" :key="colIndex" class="kanban-column">
4
+ <KanbanColumn
5
+ :column="column"
6
+ :colIndex="colIndex"
7
+ :groupedAttribute="groupedAttribute"
8
+ :columns="columns"
9
+ :itemValue="itemValue"
10
+ :getStateValue="getStateValue"
11
+ :getArrayValue="getArrayValue"
12
+ :showItem="showItem"
13
+ :updateItem="updateItem"
14
+ :removeItem="removeItem"
15
+ :cardClass="cardClass"
16
+ :cardHideFooter="cardHideFooter"
17
+ @draggableChange="onDraggableChange"
18
+ />
19
+ </div>
20
+ </div>
21
+ </template>
22
+
23
+ <script>
24
+ import KanbanColumn from './KanbanColumn.vue';
25
+
26
+ export default {
27
+ name: 'KanbanBoard',
28
+ components: {
29
+ KanbanColumn
30
+ },
31
+ inject: [
32
+ 'items',
33
+ 'groupedAttribute',
34
+ 'columns',
35
+ 'itemValue',
36
+ 'getStateValue',
37
+ 'getArrayValue',
38
+ 'showItem',
39
+ 'updateItem',
40
+ 'removeItem',
41
+ 'cardClass',
42
+ 'cardHideFooter',
43
+ 'onDraggableChange'
44
+ ]
45
+ };
46
+ </script>
47
+
48
+ <style scoped>
49
+ .kanban-board {
50
+ display: flex;
51
+ gap: 1rem;
52
+ overflow-x: auto;
53
+ padding: 1rem;
54
+ }
55
+
56
+ .kanban-column {
57
+ background: #f4f5f7;
58
+ border-radius: 8px;
59
+ width: 300px;
60
+ display: flex;
61
+ flex-direction: column;
62
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
63
+ min-width: 300px;
64
+ }
65
+ </style>
@@ -0,0 +1,103 @@
1
+ <template>
2
+ <div class="kanban-card">
3
+ <b-card
4
+ :title="item.title || item.name || `Item ${item.id}`"
5
+ tag="article"
6
+ class="mb-2 card-crud"
7
+ :class="cardClass"
8
+ :hide-footer="cardHideFooter"
9
+ >
10
+ <slot name="card" v-bind:item="item">
11
+ <div v-for="(column, indexc) in columns" :key="indexc">
12
+ <b-card-text v-if="column.type !== 'actions' && column.prop !== 'id'">
13
+ <small class="text-muted">{{ column.label }}:</small>
14
+ <div class="mb-1">
15
+ <slot :name="'cell-' + column.prop" v-bind:item="item" v-bind:index="index" v-bind:itemindex="index"
16
+ v-bind:columnindex="indexc">
17
+ <span v-if="column.type === 'boolean'">
18
+ <b-badge variant="success" v-if="itemValue(column, item)"><b-icon-check-circle></b-icon-check-circle></b-badge>
19
+ <b-badge variant="danger" v-else><b-icon-x-circle></b-icon-x-circle></b-badge>
20
+ </span>
21
+ <span v-else-if="column.type === 'date'">
22
+ {{ itemValue(column, item) }}
23
+ </span>
24
+ <span v-else-if="column.type === 'state'">
25
+ {{ getStateValue(itemValue(column, item), column.options) }}
26
+ </span>
27
+ <span v-else-if="column.type === 'array'">
28
+ {{ getArrayValue(itemValue(column, item), column.displayProp, column.options) }}
29
+ </span>
30
+ <span v-else>
31
+ {{ itemValue(column, item) }}
32
+ </span>
33
+ </slot>
34
+ </div>
35
+ </b-card-text>
36
+ </div>
37
+ </slot>
38
+
39
+ <template v-slot:footer>
40
+ <b-button-group size="sm">
41
+ <slot name="rowAction" v-bind:item="item" v-bind:index="index" v-bind:showItem="showItem"
42
+ v-bind:updateItem="updateItem" v-bind:removeItem="removeItem">
43
+ <b-button variant="primary" @click="showItem(item.id, index)"><b-icon-eye></b-icon-eye></b-button>
44
+ <b-button variant="secondary" @click="updateItem(item.id, index)"><b-icon-pencil></b-icon-pencil></b-button>
45
+ <b-button variant="danger" @click="removeItem(item.id, index)"><b-icon-trash></b-icon-trash></b-button>
46
+ </slot>
47
+ </b-button-group>
48
+ </template>
49
+ </b-card>
50
+ </div>
51
+ </template>
52
+
53
+ <script>
54
+ export default {
55
+ name: 'KanbanCard',
56
+ props: {
57
+ item: Object,
58
+ columns: Array,
59
+ index: Number,
60
+ cardClass: String,
61
+ cardHideFooter: Boolean,
62
+ itemValue: Function,
63
+ getStateValue: Function,
64
+ getArrayValue: Function,
65
+ showItem: Function,
66
+ updateItem: Function,
67
+ removeItem: Function,
68
+ }
69
+ };
70
+ </script>
71
+
72
+ <style scoped>
73
+ .kanban-card {
74
+ background: #ffffff;
75
+ border-radius: 4px;
76
+ padding: 0.5rem;
77
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
78
+ cursor: grab;
79
+ transition: box-shadow 0.2s ease;
80
+ }
81
+
82
+ .kanban-card:hover {
83
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
84
+ }
85
+
86
+ .kanban-card:active {
87
+ cursor: grabbing;
88
+ }
89
+
90
+ .card-crud {
91
+ border: 1px solid #e1e5e9;
92
+ }
93
+
94
+ .card-crud .card-title {
95
+ font-size: 0.9rem;
96
+ margin-bottom: 0.5rem;
97
+ }
98
+
99
+ .card-crud .card-text {
100
+ font-size: 0.8rem;
101
+ margin-bottom: 0.25rem;
102
+ }
103
+ </style>
@@ -0,0 +1,110 @@
1
+ <template>
2
+ <div class="kanban-column">
3
+ <div class="kanban-column-header">
4
+ <div class="d-flex justify-content-between align-items-center">
5
+ <span>{{ column.groupLabel }}</span>
6
+ <b-badge variant="secondary">{{ column.items.length }}</b-badge>
7
+ </div>
8
+ </div>
9
+
10
+ <draggable
11
+ v-model="column.items"
12
+ group="kanban"
13
+ class="kanban-column-body"
14
+ @end="onDragEnd"
15
+ :data-column="column.groupKey"
16
+ >
17
+ <div v-for="(item, itemIndex) in column.items" v-bind:key="itemIndex" class="item">
18
+ <slot name="card" v-bind:item="item">
19
+ <KanbanCard
20
+ :key="itemIndex"
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
+ </draggable>
36
+ </div>
37
+ </template>
38
+
39
+ <script>
40
+ import draggable from "vuedraggable";
41
+ import KanbanCard from './KanbanCard.vue';
42
+
43
+ export default {
44
+ name: 'KanbanColumn',
45
+ components: {
46
+ draggable,
47
+ KanbanCard
48
+ },
49
+ props: {
50
+ column: Object,
51
+ colIndex: Number,
52
+ groupedAttribute: String,
53
+ columns: Array,
54
+ itemValue: Function,
55
+ getStateValue: Function,
56
+ getArrayValue: Function,
57
+ showItem: Function,
58
+ updateItem: Function,
59
+ removeItem: Function,
60
+ cardClass: String,
61
+ cardHideFooter: Boolean
62
+ },
63
+ methods: {
64
+ onDragEnd(event) {
65
+ // Handle drag end for Kanban
66
+ if (event.added || event.moved) {
67
+ const item = event.item.__vue__.$parent.item || event.item.__vue__.item;
68
+ const newColumn = event.to.parentElement.getAttribute('data-column');
69
+
70
+ if (item && newColumn) {
71
+ // Update the item's grouped attribute
72
+ item[this.groupedAttribute] = newColumn;
73
+
74
+ // Emit the change event
75
+ this.$emit('draggableChange', event);
76
+ }
77
+ }
78
+ }
79
+ }
80
+ };
81
+ </script>
82
+
83
+ <style scoped>
84
+ .kanban-column {
85
+ background: #f4f5f7;
86
+ border-radius: 8px;
87
+ width: 300px;
88
+ display: flex;
89
+ flex-direction: column;
90
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
91
+ }
92
+
93
+ .kanban-column-header {
94
+ font-weight: bold;
95
+ padding: 0.5rem;
96
+ background: #dfe1e6;
97
+ border-radius: 8px 8px 0 0;
98
+ text-align: center;
99
+ }
100
+
101
+ .kanban-column-body {
102
+ padding: 0.5rem;
103
+ min-height: 100px;
104
+ background: #ffffff;
105
+ border-radius: 0 0 8px 8px;
106
+ display: flex;
107
+ flex-direction: column;
108
+ gap: 0.5rem;
109
+ }
110
+ </style>
@@ -0,0 +1,91 @@
1
+ <template>
2
+ <td :scope="column.prop == 'id' ? 'row' : ''">
3
+ <slot :name="'cell-' + column.prop" v-bind:item="item" v-bind:index="index" v-bind:itemindex="index"
4
+ v-bind:columnindex="columnIndex">
5
+ <span v-if="column.type == 'boolean'">
6
+ <b-badge variant="success" v-if="itemValue(column, item) == 'true' ||
7
+ itemValue(column, item) == 1 ||
8
+ itemValue(column, item) == '1'
9
+ "><b-icon-check-circle></b-icon-check-circle></b-badge>
10
+ <b-badge variant="danger" v-if="!itemValue(column, item) ||
11
+ itemValue(column, item) == '0' ||
12
+ itemValue(column, item) == 'false'
13
+ "><b-icon-x-circle></b-icon-x-circle></b-badge>
14
+ </span>
15
+ <span v-else-if="column.type == 'date'">
16
+ {{
17
+ itemValue(column, item)
18
+ ? moment(itemValue(column, item)).format(
19
+ column.format ? column.format : 'L LT'
20
+ )
21
+ : itemValue(column, item)
22
+ }}
23
+ </span>
24
+ <span v-else-if="column.type == 'select'">
25
+ <b-form-checkbox v-model="item.selected" @change="onCheckSelect($event, item)">
26
+ </b-form-checkbox>
27
+ </span>
28
+ <span v-else-if="column.type == 'state' && optionsLoaded">
29
+ {{
30
+ getStateValue(itemValue(column, item), column.options)
31
+ }}
32
+ </span>
33
+ <span v-else-if="column.type == 'array' && optionsLoaded">
34
+ {{
35
+ getArrayValue(
36
+ itemValue(column, item),
37
+ column.displayProp,
38
+ column.options
39
+ )
40
+ }}
41
+ </span>
42
+ <span v-else>
43
+ {{ itemValue(column, item) }}
44
+ </span>
45
+ </slot>
46
+
47
+ <b-button-group v-if="column.type == 'actions'">
48
+ <slot name="rowAction" v-bind:item="item" v-bind:index="index" v-bind:showItem="showItem"
49
+ v-bind:updateItem="updateItem" v-bind:removeItem="removeItem">
50
+ <b-button variant="primary" @click="showItem(item.id, index)">
51
+ <b-icon-eye></b-icon-eye>
52
+ </b-button>
53
+ <b-button variant="secondary" @click="updateItem(item.id, index)">
54
+ <b-icon-pencil></b-icon-pencil>
55
+ </b-button>
56
+ <b-button variant="danger" @click="removeItem(item.id, index)">
57
+ <b-icon-trash></b-icon-trash>
58
+ </b-button>
59
+ </slot>
60
+ </b-button-group>
61
+ </td>
62
+ </template>
63
+
64
+ <script>
65
+ import moment from "moment";
66
+
67
+ export default {
68
+ name: 'TableCell',
69
+ props: {
70
+ column: Object,
71
+ item: Object,
72
+ index: Number,
73
+ columnIndex: Number
74
+ },
75
+ inject: [
76
+ 'itemValue',
77
+ 'getStateValue',
78
+ 'getArrayValue',
79
+ 'onCheckSelect',
80
+ 'showItem',
81
+ 'updateItem',
82
+ 'removeItem',
83
+ 'optionsLoaded'
84
+ ],
85
+ data() {
86
+ return {
87
+ moment: moment
88
+ };
89
+ }
90
+ };
91
+ </script>
@@ -0,0 +1,145 @@
1
+ <template>
2
+ <thead class="thead-light">
3
+ <tr>
4
+ <slot name="rowHead">
5
+ <th v-for="(column, indexc) in columns" :key="indexc"
6
+ :style="{ width: column.width ? column.width : 'inherit' }" scope="col"
7
+ @mouseenter="hoveredColumn = column.prop"
8
+ @mouseleave="hoveredColumn = null">
9
+ <slot :name="'filter-' + column.prop" v-bind:column="column" v-bind:filter="filter"
10
+ v-bind:internalFilterByProp="internalFilterByProp" v-if="enableFilters &&
11
+ filtersVisible &&
12
+ isColumnHasFilter(column) &&
13
+ internalFilterByProp(column.prop)
14
+ ">
15
+
16
+ <div class="form-group">
17
+ <select v-if="column.type == 'boolean'" class="form-control form-control-md p-2"
18
+ v-model="internalFilterByProp(column.prop).value" @change="onChangeFilter($event)">
19
+ <option value="">{{ column.label }}</option>
20
+ <option value="1">Sí</option>
21
+ <option value="0">No</option>
22
+ </select>
23
+
24
+ <div class="row" v-else-if="column.type == 'date'">
25
+ <div class="col-6">
26
+ <b-form-datepicker v-model="internalFilterByProp(column.prop + '_from').value
27
+ " today-button reset-button close-button locale="es"
28
+ class="form-control-md p-2"></b-form-datepicker>
29
+ </div>
30
+ <div class="col-6">
31
+ <b-form-datepicker v-model="internalFilterByProp(column.prop + '_to').value
32
+ " today-button reset-button close-button locale="es"
33
+ class="form-control-md p-2"></b-form-datepicker>
34
+ </div>
35
+ </div>
36
+
37
+ <select v-else-if="column.type == 'state' && optionsLoaded" class="form-control form-control-md p-2"
38
+ v-model="internalFilterByProp(column.prop).value" @change="onChangeFilter($event)"
39
+ :placeholder="column.label">
40
+ <option value="">{{ column.label }}</option>
41
+ <option :value="option.id" v-for="(option, indexo) in column.options" :key="indexo">
42
+ {{
43
+ option.text
44
+ ? option.text
45
+ : option.label
46
+ ? option.label
47
+ : ""
48
+ }}
49
+ </option>
50
+ </select>
51
+
52
+ <select v-else-if="column.type == 'array' && optionsLoaded" class="form-control form-control-md p-2"
53
+ v-model="internalFilterByProp(column.prop).value" @change="onChangeFilter($event)"
54
+ :placeholder="column.label">
55
+ <option value="">{{ column.label }}</option>
56
+ <option :value="option.id" v-for="(option, indexo) in column.options" :key="indexo">
57
+ {{
58
+ option.text
59
+ ? option.text
60
+ : option.label
61
+ ? option.label
62
+ : ""
63
+ }}
64
+ </option>
65
+ </select>
66
+
67
+ <b-form-checkbox v-else-if="column.type == 'checkbox'" name="select-all"
68
+ @change="toggleAll($event)">
69
+ </b-form-checkbox>
70
+
71
+ <b-form-checkbox v-else-if="column.type == 'select'" name="select-all" @change="toggleAll($event)">
72
+ </b-form-checkbox>
73
+
74
+ <input v-else class="form-control form-control-md p-2"
75
+ v-model="internalFilterByProp(column.prop).value" :placeholder="column.label"
76
+ @change="onChangeFilter($event)" />
77
+
78
+ </div>
79
+ </slot>
80
+ <span v-else-if="column.type == 'select'">
81
+ <b-form-checkbox name="select-all" @change="toggleAll($event)"></b-form-checkbox>
82
+ </span>
83
+ <span v-else>{{ column.label }}</span>
84
+
85
+ <span
86
+ v-if="isSortableColumn(column) && shouldShowSortIcon(column)"
87
+ class="sort-filter" @click="toggleSortFilter(column)">
88
+ <b-icon-sort-up
89
+ v-if="getSortIconDirection(column) === 'up'"></b-icon-sort-up>
90
+ <b-icon-sort-down
91
+ v-if="getSortIconDirection(column) === 'down'"></b-icon-sort-down>
92
+ </span>
93
+ </th>
94
+ </slot>
95
+ </tr>
96
+ </thead>
97
+ </template>
98
+
99
+ <script>
100
+ export default {
101
+ name: 'TableHeader',
102
+ inject: [
103
+ 'columns',
104
+ 'enableFilters',
105
+ 'filtersVisible',
106
+ 'isColumnHasFilter',
107
+ 'internalFilterByProp',
108
+ 'onChangeFilter',
109
+ 'toggleAll',
110
+ 'toggleSortFilter',
111
+ 'sortable',
112
+ 'optionsLoaded'
113
+ ],
114
+ data() {
115
+ return {
116
+ hoveredColumn: null
117
+ };
118
+ },
119
+ methods: {
120
+ isSortableColumn(column) {
121
+ return this.sortable &&
122
+ column.type != 'select' &&
123
+ column.type != 'checkbox' &&
124
+ this.internalFilterByProp(column.prop + '_sort');
125
+ },
126
+ shouldShowSortIcon(column) {
127
+ const sortFilter = this.internalFilterByProp(column.prop + '_sort');
128
+ return this.hoveredColumn === column.prop || sortFilter.value;
129
+ },
130
+ getSortIconDirection(column) {
131
+ const sortFilter = this.internalFilterByProp(column.prop + '_sort');
132
+ const sortValue = sortFilter.value;
133
+
134
+ if (sortValue === 'DESC') {
135
+ return 'down';
136
+ } else if (sortValue === 'ASC') {
137
+ return 'up';
138
+ } else if (this.hoveredColumn === column.prop) {
139
+ return 'up';
140
+ }
141
+ return null;
142
+ }
143
+ }
144
+ };
145
+ </script>
@@ -0,0 +1,43 @@
1
+ <template>
2
+ <tr
3
+ @mouseover="onRowHover(item, index)"
4
+ @click="onRowClick(item, index)"
5
+ :class="['item', { 'selected': item.selected }]"
6
+ >
7
+ <th :colspan="columns.length" v-if="grouped && item.crudgroup">
8
+ <span>{{ item.crudgrouplabel }}</span>
9
+ </th>
10
+
11
+ <slot name="row" v-bind:item="item" v-else>
12
+ <TableCell
13
+ v-for="(column, indexc) in columns"
14
+ :key="indexc"
15
+ :column="column"
16
+ :item="item"
17
+ :index="index"
18
+ :columnIndex="indexc"
19
+ />
20
+ </slot>
21
+ </tr>
22
+ </template>
23
+
24
+ <script>
25
+ import TableCell from './TableCell.vue';
26
+
27
+ export default {
28
+ name: 'TableRow',
29
+ components: {
30
+ TableCell
31
+ },
32
+ props: {
33
+ item: Object,
34
+ index: Number,
35
+ grouped: Boolean
36
+ },
37
+ inject: [
38
+ 'columns',
39
+ 'onRowHover',
40
+ 'onRowClick'
41
+ ]
42
+ };
43
+ </script>