@nixweb/nixloc-ui 1.1.0 → 1.2.0

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.
@@ -18,10 +18,11 @@
18
18
  {{ row[obj.field] }}
19
19
  </div>
20
20
  </div>
21
- <div v-if="obj.type === 'html'" :style="obj.styleBody"
21
+ <div v-if="obj.type === 'html' && row[obj.field]" :style="obj.styleBody"
22
22
  :class="convertClass(row[obj.fieldComparison], obj.classCssBody)">
23
23
  <div v-if="row[obj.field]" v-html="row[obj.field]"></div>
24
24
  <div v-else v-html="obj.html"></div>
25
+ <div v-html="row[obj.fieldSecond]"></div>
25
26
  </div>
26
27
  <div v-if="obj.type === 'select'" :style="obj.styleBody"
27
28
  :class="convertClass(row[obj.fieldComparison], obj.classCssBody)">
@@ -1,78 +1,114 @@
1
1
  <template>
2
- <div class="filter-horizontal">
3
- <div class="side-by-side div-title">
4
- <span class="title-page"> Mostrar</span>
5
- </div>
6
- <div class="side-by-side div-select">
7
- <SelectStatic fieldTarget="totalPerPage" :initialValue="initialValue" :data="[
8
- { content: '10', id: 10 },
9
- { content: '20', id: 20 },
10
- { content: '50', id: 50 },
11
- { content: '100', id: 100 },
12
- ]" v-model="totalPerPage" />
13
- </div>
14
-
2
+ <div v-if="showTotalPerPage && totalRecords > 0" class="pagination-wrapper">
3
+ <ul class="pagination-list">
4
+ <li v-for="opt in filteredOptions" :key="opt">
5
+ <button class="pagination-pill" :class="{ 'is-selected': value === opt }" type="button"
6
+ @click="select(opt)">
7
+ {{ opt }}
8
+ </button>
9
+ </li>
10
+ </ul>
15
11
  </div>
16
12
  </template>
17
13
 
18
14
  <script>
19
- import SelectStatic from "../forms/SelectStatic.vue";
20
-
21
- import { mapMutations, mapState } from "vuex";
15
+ import { mapState, mapMutations } from "vuex";
22
16
 
23
17
  export default {
24
- name: "TableTotalPerPage",
25
- components: { SelectStatic },
18
+ name: "PaginationPerPage",
26
19
  props: {
27
- showTotalPerPage: {
28
- type: Boolean,
29
- default: true,
30
- },
20
+ totalRecords: { type: Number, default: 0 },
21
+ showTotalPerPage: { type: Boolean, default: true },
31
22
  },
32
23
  data() {
33
24
  return {
34
- initialValue: {}
35
- }
36
- },
37
- created() {
38
- this.initialValue = { content: this.paginations.totalPerPage, id: this.paginations.totalPerPage }
25
+ value: 10,
26
+ options: [10, 20, 50, 100],
27
+ };
39
28
  },
40
29
  computed: {
41
30
  ...mapState("generic", ["paginations"]),
42
- totalPerPage: {
43
- get() {
44
- return this.$store.state.generic.paginations.totalPerPage;
45
- },
46
- set(value) {
47
- this.updateTotalPerPage(value);
48
- },
31
+ filteredOptions() {
32
+ const total = this.totalRecords;
33
+
34
+ if (total <= 10) return [10];
35
+ if (total <= 20) return [10, 20];
36
+ if (total <= 50) return [10, 20, 50];
37
+ if (total <= 100) return [10, 20, 50, 100];
38
+ return this.options;
49
39
  },
50
40
  },
41
+ created() {
42
+ const fromStore = Number(this.paginations?.totalPerPage);
43
+ if (Number.isFinite(fromStore)) {
44
+ this.value = fromStore;
45
+ }
46
+ this.updateTotalPerPage({ content: String(this.value), id: this.value });
47
+ },
51
48
  methods: {
52
- ...mapMutations("generic", [
53
- "updateTotalPerPage",
54
- ]),
55
- }
49
+ ...mapMutations("generic", ["executedSearch", "updateTotalPerPage"]),
50
+ select(opt) {
51
+ if (this.value === opt) return;
52
+ this.value = opt;
53
+ this.updateTotalPerPage({ content: String(opt), id: opt });
54
+ this.executedSearch();
55
+ },
56
+ },
57
+ watch: {
58
+ totalRecords() {
59
+ if (this.totalRecords <= 0) return;
60
+ if (!this.filteredOptions.includes(this.value)) {
61
+ const next = this.filteredOptions[this.filteredOptions.length - 1];
62
+ this.value = next;
63
+ this.updateTotalPerPage({ content: String(next), id: next });
64
+ }
65
+ },
66
+ },
56
67
  };
57
68
  </script>
58
69
 
59
70
  <style scoped>
60
- .filter-horizontal {
71
+ .pagination-wrapper {
61
72
  display: flex;
73
+ align-items: center;
62
74
  justify-content: flex-end;
63
- margin-bottom: -30px;
75
+ gap: 8px;
76
+ width: 100%;
77
+ font-family: inherit;
78
+ }
79
+
80
+ .pagination-list {
81
+ display: flex;
82
+ align-items: center;
83
+ gap: 6px;
84
+ margin: 0;
85
+ padding: 0;
86
+ list-style: none;
64
87
  }
65
88
 
66
- .div-title {
67
- margin-right: 20px;
68
- margin-top: 35px;
89
+ .pagination-pill {
90
+ width: 31px;
91
+ height: 31px;
92
+ border-radius: 50%;
93
+ background: #f5f5f5;
94
+ border: none;
95
+ color: #444;
96
+ font-size: 13px;
97
+ font-weight: 500;
98
+ cursor: pointer;
99
+ transition: background 0.2s, color 0.2s, transform 0.1s;
69
100
  }
70
101
 
71
- .div-select {
72
- width: 100px;
102
+ .pagination-pill:hover {
103
+ background: #e0e0e0;
73
104
  }
74
105
 
75
- .title-page {
76
- font-size: 13px;
106
+ .pagination-pill.is-selected {
107
+ background: #D98621;
108
+ color: #fff;
109
+ }
110
+
111
+ .pagination-pill:active {
112
+ transform: scale(0.95);
77
113
  }
78
- </style>
114
+ </style>
@@ -1,12 +1,9 @@
1
1
  <template>
2
2
  <div>
3
- <div
4
- class="badge-side-by-side text-center"
5
- v-for="item in totalization"
6
- :key="item.title"
7
- >
3
+ <div class="badge-side-by-side text-center" v-for="item in totalization" :key="item.title">
8
4
  <div class="badge-totalization" :class="item.classCss">
9
- <span>{{ item.title }} </span>
5
+ <i :class="item.classIcon"></i>
6
+ <span class="title-totalization"> {{ item.title }} </span>
10
7
  <span>{{ item.value | currency }}</span>
11
8
  </div>
12
9
  </div>
@@ -25,16 +22,19 @@ export default {
25
22
  <style scoped>
26
23
  .badge-side-by-side {
27
24
  display: inline-block;
28
- margin-right: 3px;
25
+ margin-right: 8px;
29
26
  margin-bottom: 10px;
30
27
  }
31
28
 
32
29
  .badge-totalization {
33
- border: 1px solid #dbdee0;
34
- padding-left: 5px;
35
- padding-right: 5px;
30
+ border: 1px solid #e6e7eb;
31
+ padding: 3px 8px 3px 8px;
36
32
  border-radius: 30px;
37
- font-size: 14px;
33
+ font-size: 13px;
34
+ }
35
+
36
+ .title-totalization{
37
+ margin-left: 1px;
38
38
  }
39
39
 
40
40
  .revenue {
@@ -0,0 +1,29 @@
1
+ <template>
2
+ <div>
3
+ <div class="side-by-side" v-for="item in actions" :key="item.type">
4
+
5
+ <Button v-if="item.type === 'delete'" key="remove" classIcon="fa-solid fa-trash" :title="item.title"
6
+ type="danger" size="small" :params="item" :clicked="() => $emit('confirm', item)" />
7
+
8
+ <Button v-if="item.type === 'options'" key="options" :title="item.title" :classIcon="item.classIcon"
9
+ type="primary" size="small" :params="item" :clicked="() => $emit('confirm', item)" />
10
+
11
+ </div>
12
+ </div>
13
+ </template>
14
+
15
+ <script>
16
+ import Button from "@nixweb/nixloc-ui/src/component/forms/Button";
17
+
18
+ export default {
19
+ name: "ActionButtons",
20
+ components: { Button },
21
+ props: {
22
+ actions: { type: Array, default: () => [] },
23
+ },
24
+ };
25
+ </script>
26
+
27
+ <style scoped>
28
+
29
+ </style>
@@ -0,0 +1,103 @@
1
+ <template>
2
+ <div>
3
+ <div class="error-details-header">
4
+ <i class="fas fa-triangle-exclamation"></i>
5
+ <div>
6
+ <div class="error-details-title title">O que deu errado?</div>
7
+ <!-- <div class="error-details-sub">ID: {{ id }}</div> -->
8
+ </div>
9
+ </div>
10
+
11
+ <ScrollBar :minHeight="190" :maxHeight="190">
12
+ <ul class="error-list">
13
+ <li v-for="(msg, i) in safeMessages" :key="i" class="error-item">
14
+ <i class="fas fa-info-circle"></i>
15
+ <span>{{ msg }}</span>
16
+ </li>
17
+
18
+ <li v-if="safeMessages.length === 0" class="error-item muted">
19
+ <i class="fas fa-info-circle"></i>
20
+ <span>Algo deu errado, mas não recebemos detalhes do servidor.</span>
21
+ </li>
22
+ </ul>
23
+ </ScrollBar>
24
+
25
+ <div class="text-center">
26
+ <Button key="closeErrors" title="Fechar" color="white" backGroundColor="#6B7280" size="small"
27
+ :clicked="() => $emit('close')" />
28
+ </div>
29
+ </div>
30
+ </template>
31
+
32
+ <script>
33
+ import ScrollBar from "@nixweb/nixloc-ui/src/component/layout/ScrollBar.vue";
34
+ import Button from "@nixweb/nixloc-ui/src/component/forms/Button";
35
+
36
+ export default {
37
+ name: "ActionErrorContent",
38
+ components: { ScrollBar, Button },
39
+ props: {
40
+ id: { type: [String, Number], required: true },
41
+ index: { type: Number, default: -1 },
42
+ messages: { type: Array, default: () => [] },
43
+ },
44
+ computed: {
45
+ safeMessages() {
46
+ return (this.messages || []).filter(Boolean);
47
+ },
48
+ },
49
+ };
50
+ </script>
51
+
52
+ <style scoped>
53
+ .error-details-header {
54
+ display: flex;
55
+ gap: 10px;
56
+ align-items: center;
57
+ margin-bottom: 12px;
58
+ color: #dc2626;
59
+ }
60
+
61
+ .error-details-header i {
62
+ font-size: 18px;
63
+ }
64
+
65
+ .error-details-title {
66
+ font-weight: 600;
67
+ color: #111827;
68
+ }
69
+
70
+ .error-details-sub {
71
+ font-size: 12px;
72
+ color: #6b7280;
73
+ }
74
+
75
+ .error-list {
76
+ list-style: none;
77
+ margin: 0;
78
+ padding: 0;
79
+ }
80
+
81
+ .error-item {
82
+ display: flex;
83
+ gap: 8px;
84
+ align-items: flex-start;
85
+ padding: 8px 10px;
86
+ background: #fff7f7;
87
+ border: 1px solid #fde2e2;
88
+ border-radius: 8px;
89
+ margin-bottom: 8px;
90
+ font-size: 13px;
91
+ color: #dc2626;
92
+ }
93
+
94
+ .error-item i {
95
+ margin-top: 2px;
96
+ }
97
+
98
+ .error-item.muted {
99
+ background: #f9fafb;
100
+ border-color: #e5e7eb;
101
+ color: #6b7280;
102
+ }
103
+ </style>
@@ -0,0 +1,55 @@
1
+ <template>
2
+ <div>
3
+ <div v-if="!isFinished" class="actions">
4
+ <div class="side-by-side">
5
+ <Button key="cancel" title="Cancelar" type="danger" size="small"
6
+ :disabled="processing || selected.length === 0" :clicked="hideModal" />
7
+ <Button key="ok" title="Processar" color="white" classIcon="fa-solid fa-play" backGroundColor="#007BFF" size="large"
8
+ :disabled="processing || selected.length === 0" :clicked="ok" />
9
+ </div>
10
+ </div>
11
+
12
+ <div v-else class="text-center">
13
+ <Button key="finish" title="Fechar" classIcon="fa-solid fa-xmark" color="white" backGroundColor="#6D757B" size="medium"
14
+ :clicked="finished" />
15
+ </div>
16
+ </div>
17
+ </template>
18
+
19
+ <script>
20
+ import Button from "@nixweb/nixloc-ui/src/component/forms/Button";
21
+
22
+ export default {
23
+ name: "ActionFooter",
24
+ components: { Button },
25
+ props: {
26
+ isFinished: { type: Boolean, default: false },
27
+ processing: { type: Boolean, default: false },
28
+ selected: { type: Array, default: () => [] },
29
+ },
30
+ emits: ["ok", "cancel", "finish"],
31
+ methods: {
32
+ ok() {
33
+ this.$emit("ok");
34
+ },
35
+ hideModal() {
36
+ this.$emit("cancel");
37
+ },
38
+ finished() {
39
+ this.$emit("finish");
40
+ },
41
+ },
42
+ };
43
+ </script>
44
+
45
+ <style scoped>
46
+ .actions {
47
+ text-align: center;
48
+ }
49
+
50
+ .side-by-side {
51
+ display: inline-flex;
52
+ align-items: center;
53
+ gap: 8px;
54
+ }
55
+ </style>
@@ -0,0 +1,110 @@
1
+ <template>
2
+ <div>
3
+ <div class="action-banner">
4
+ <span class="action-accent"></span>
5
+ <span class="action-label">Ação</span>
6
+ <strong class="action-value title">{{ description }}</strong>
7
+ </div>
8
+
9
+ <div class="status-header">
10
+ <div class="status-chip">
11
+ <i class="fas fa-list"></i>
12
+ <span>Total {{ total }}</span>
13
+ </div>
14
+ <div class="status-chip success">
15
+ <i class="fas fa-check-circle"></i>
16
+ <span>Concluídos {{ done }}</span>
17
+ </div>
18
+ <div class="status-chip error-chip">
19
+ <i class="fas fa-times-circle"></i>
20
+ <span>Falhas {{ errors }}</span>
21
+ </div>
22
+ </div>
23
+ </div>
24
+ </template>
25
+
26
+ <script>
27
+ export default {
28
+ name: "ActionModalHeader",
29
+ props: {
30
+ description: { type: String, default: "" },
31
+ total: { type: Number, default: 0 },
32
+ done: { type: Number, default: 0 },
33
+ errors: { type: Number, default: 0 },
34
+ },
35
+ };
36
+ </script>
37
+
38
+ <style scoped>
39
+ .status-header {
40
+ display: flex;
41
+ align-items: center;
42
+ gap: 10px;
43
+ flex-wrap: wrap;
44
+ margin-bottom: 10px;
45
+ padding: 8px 0;
46
+ }
47
+
48
+ .status-chip {
49
+ display: flex;
50
+ align-items: center;
51
+ gap: 6px;
52
+ background: #f3f4f6;
53
+ border: 1px solid #e5e7eb;
54
+ border-radius: 20px;
55
+ padding: 6px 10px;
56
+ font-size: 13px;
57
+ color: #374151;
58
+ }
59
+
60
+ .status-chip.success {
61
+ background: #ecfdf5;
62
+ border-color: #a7f3d0;
63
+ color: #065f46;
64
+ }
65
+
66
+ .status-chip.success i {
67
+ color: #16a34a;
68
+ }
69
+
70
+ .status-chip.error-chip {
71
+ background: #fef2f2;
72
+ border-color: #fecaca;
73
+ color: #b91c1c;
74
+ }
75
+
76
+ .status-chip.error-chip i {
77
+ color: #dc2626;
78
+ }
79
+
80
+ .action-banner {
81
+ display: flex;
82
+ align-items: center;
83
+ gap: 10px;
84
+ padding: 10px 12px;
85
+ margin-bottom: 12px;
86
+ background: #f8fafc;
87
+ border: 1px solid #e5e7eb;
88
+ border-radius: 12px;
89
+ }
90
+
91
+ .action-accent {
92
+ width: 4px;
93
+ align-self: stretch;
94
+ background: #3b82f6;
95
+ border-radius: 8px;
96
+ }
97
+
98
+ .action-label {
99
+ font-size: 12px;
100
+ text-transform: uppercase;
101
+ letter-spacing: 0.06em;
102
+ color: #64748b;
103
+ }
104
+
105
+ .action-value {
106
+ font-size: 14px;
107
+ color: #0f172a;
108
+ font-weight: 700;
109
+ }
110
+ </style>
@@ -0,0 +1,121 @@
1
+ <template>
2
+ <ScrollBar :minHeight="300" :maxHeight="300">
3
+ <div v-for="(id, index) in selected" :key="id" class="item-row">
4
+ <div class="item-left title">Item {{ index + 1 }}</div>
5
+ <div class="item-right">
6
+ <span v-if="status[id] === 'waiting'" class="waiting">
7
+ <i class="fas fa-hourglass-half"></i>
8
+ </span>
9
+
10
+ <span v-else-if="status[id] === 'running'" class="loading">
11
+ <span class="spinner"></span> Processando...
12
+ </span>
13
+
14
+ <span v-else-if="status[id] === 'done'" class="done">
15
+ <i class="fas fa-check-circle done-icon"></i> Sucesso!
16
+ </span>
17
+
18
+ <span v-else-if="status[id] === 'error'" class="error">
19
+ <i class="fas fa-times-circle"></i> Falhou!
20
+ <button class="error-link" @click="$emit('show-error', { id, index })"><span class="title">Ver
21
+ detalhes</span></button>
22
+ </span>
23
+ </div>
24
+ </div>
25
+ </ScrollBar>
26
+ </template>
27
+
28
+ <script>
29
+ import ScrollBar from "@nixweb/nixloc-ui/src/component/layout/ScrollBar.vue";
30
+
31
+ export default {
32
+ name: "ActionItemList",
33
+ components: { ScrollBar },
34
+ props: { selected: Array, status: Object, errorsMap: Object },
35
+ };
36
+ </script>
37
+
38
+ <style scoped>
39
+ .item-row {
40
+ display: flex;
41
+ align-items: center;
42
+ justify-content: space-between;
43
+ padding: 8px 10px;
44
+ border: 1px solid #eee;
45
+ border-radius: 8px;
46
+ background: #fafafa;
47
+ margin: 6px 0;
48
+ }
49
+
50
+ .item-left {
51
+ font-weight: 500;
52
+ color: #334155;
53
+ }
54
+
55
+ .item-right {
56
+ display: flex;
57
+ align-items: center;
58
+ gap: 8px;
59
+ font-size: 13px;
60
+ }
61
+
62
+ .waiting {
63
+ color: #6b7280;
64
+ }
65
+
66
+ .loading {
67
+ color: #007BFF;
68
+ }
69
+
70
+ .done {
71
+ color: #16a34a;
72
+ font-weight: 500;
73
+ }
74
+
75
+ .done-icon {
76
+ color: #22c55e;
77
+ font-size: 14px;
78
+ }
79
+
80
+ .error {
81
+ color: #dc2626;
82
+ font-weight: 500;
83
+ }
84
+
85
+ .error-link {
86
+ margin-left: 10px;
87
+ background: transparent;
88
+ border: none;
89
+ padding: 0;
90
+ font-size: 13px;
91
+ text-decoration: underline;
92
+ cursor: pointer;
93
+ color: #1f2937;
94
+ }
95
+
96
+ .error-link:hover {
97
+ opacity: 0.9;
98
+ }
99
+
100
+ .spinner {
101
+ display: inline-block;
102
+ width: 14px;
103
+ height: 14px;
104
+ border: 2px solid #007bff;
105
+ border-top: 2px solid transparent;
106
+ border-radius: 50%;
107
+ animation: spin 0.8s linear infinite;
108
+ vertical-align: middle;
109
+ margin-right: 6px;
110
+ }
111
+
112
+ @keyframes spin {
113
+ 0% {
114
+ transform: rotate(0deg);
115
+ }
116
+
117
+ 100% {
118
+ transform: rotate(360deg);
119
+ }
120
+ }
121
+ </style>