@nixweb/nixloc-ui 1.2.0 → 1.4.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nixweb/nixloc-ui",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Componentes UI",
5
5
  "author": "Fábio Ávila <fabio@nixweb.com.br>",
6
6
  "private": false,
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div class="c-div-button" v-b-tooltip.hover :title="tooltip">
3
- <button :style="'background-color:' + backGroundColor + ';color:' + color" class="button" :class="{
3
+ <button :id="_key" :style="'background-color:' + backGroundColor + ';color:' + color" class="button" :class="{
4
4
  small: size === 'small',
5
5
  medium: size === 'medium',
6
6
  large: size === 'large',
@@ -1,49 +1,77 @@
1
1
  <template>
2
2
  <div>
3
3
  <div class="btn-container side-by-side text-center">
4
- <div v-for="option in options" class="btn-item-button-filter side-by-side"
5
- :class="{ 'selected-button-filter': option.value == selected }" @click="execute(option)" :key="option.title">
4
+ <div v-for="option in options" :key="option.value || option.title" class="btn-item-button-filter side-by-side"
5
+ :class="{ 'selected-button-filter': option.value === selected }" @click="execute(option)">
6
6
  <i :class="option.classIcon"></i>
7
7
  <span class="btn-title">{{ option.title }}</span>
8
8
  </div>
9
9
  </div>
10
10
  </div>
11
11
  </template>
12
+
12
13
  <script>
13
14
  import { mapMutations } from "vuex";
14
15
 
15
16
  export default {
16
17
  name: "ButtonFilter",
17
- props: {
18
- initialOption: String,
19
- options: {
20
- type: Array,
21
- default: [],
22
- },
23
- value: String,
24
- params: Object,
25
- clicked: Function,
26
- },
18
+ props: ["initialOption", "options", "value", "params", "clicked"],
27
19
  data() {
28
20
  return {
29
- selected: "",
21
+ selected: null,
30
22
  };
31
23
  },
32
24
  created() {
33
- if (this.initialOption) this.selected = this.initialOption;
25
+ this.setSelectedFromProps();
26
+ },
27
+ watch: {
28
+ // se o pai atualizar o v-model (prop value), sincroniza
29
+ value() {
30
+ this.setSelectedFromProps();
31
+ },
32
+ // se o pai trocar a initialOption depois, sincroniza também
33
+ initialOption() {
34
+ this.setSelectedFromProps();
35
+ },
34
36
  },
35
37
  methods: {
36
38
  ...mapMutations("generic", ["addEvent"]),
39
+
40
+ setSelectedFromProps() {
41
+ // prioridade para v-model (value)
42
+ if (this.value !== undefined) {
43
+ this.selected = this.value;
44
+ return;
45
+ }
46
+
47
+ // aceita initialOption como valor (0, "", "x", etc.) ou como objeto { value: ... }
48
+ if (this.initialOption !== undefined && this.initialOption !== null) {
49
+ this.selected =
50
+ typeof this.initialOption === "object"
51
+ ? this.initialOption.value
52
+ : this.initialOption;
53
+ }
54
+ },
55
+
37
56
  execute(option) {
38
- if (this.initialOption) this.selected = option.value;
39
- this.addEvent({ name: option.eventName, data: option });
57
+ // SEMPRE atualiza o selecionado
58
+ this.selected = option.value;
59
+
60
+ // eventos
61
+ if (option.eventName) {
62
+ this.addEvent({ name: option.eventName, data: option });
63
+ }
64
+
65
+ // emite para v-model do pai
40
66
  this.$emit("input", option.value);
67
+
68
+ // callback opcional
41
69
  if (this.clicked) this.clicked(this.params);
42
70
  },
43
71
  },
44
-
45
72
  };
46
73
  </script>
74
+
47
75
  <style scoped>
48
76
  .btn-container {
49
77
  margin-left: 10px;
@@ -67,10 +95,10 @@ export default {
67
95
  }
68
96
 
69
97
  .selected-button-filter {
70
- background-color: #4BB4E2;
71
- color: white;
98
+ background-color: #4bb4e2;
99
+ color: #fff;
72
100
  padding-left: 10px;
73
101
  padding-right: 10px;
74
102
  border-radius: 15px;
75
103
  }
76
- </style>
104
+ </style>
@@ -0,0 +1,210 @@
1
+ <template>
2
+ <div class="button-inline-group" ref="wrap">
3
+ <button class="button-inline-trigger" title="Mais opções" @click.stop="toggleMenu" ref="triggerBtn">
4
+ <i class="fa-solid fa-ellipsis-vertical"></i>
5
+ </button>
6
+
7
+ <transition name="button-inline-fade">
8
+ <div v-if="showMenu" class="button-inline-popover-fixed" :style="popoverStyle" ref="popover" role="menu">
9
+ <div class="button-inline-popover-inner">
10
+ <div class="button-inline-buttons">
11
+ <button v-for="(btn, i) in buttons" :key="btn.id || i" class="button-inline-btn"
12
+ :title="btn.title" :style="buttonStyle(btn, i)" @click="onAction(btn)">
13
+ <i :class="btn.icon"></i>
14
+ </button>
15
+ </div>
16
+ </div>
17
+ </div>
18
+ </transition>
19
+ </div>
20
+ </template>
21
+
22
+ <script>
23
+
24
+ import { mapMutations } from "vuex";
25
+
26
+ export default {
27
+ name: "ButtonGroupInline",
28
+ props: {
29
+ buttons: { type: Array, required: true },
30
+ defaultSize: { type: Number, default: 35 }
31
+ },
32
+ data() {
33
+ return {
34
+ showMenu: false,
35
+ popoverStyle: {
36
+ position: "fixed",
37
+ top: "0px",
38
+ left: "0px",
39
+ transform: "translateX(-50%)",
40
+ zIndex: 9999
41
+ }
42
+ };
43
+ },
44
+ methods: {
45
+ ...mapMutations("generic", ["addEvent"]),
46
+ toggleMenu() {
47
+ this.showMenu = !this.showMenu;
48
+ if (this.showMenu) {
49
+ this.$nextTick(() => {
50
+ this.positionPopover();
51
+ window.addEventListener("scroll", this.positionPopover, true);
52
+ window.addEventListener("resize", this.positionPopover, true);
53
+ document.addEventListener("click", this.onOutsideClick, true);
54
+ document.addEventListener("keydown", this.onKeydown, true);
55
+ });
56
+ } else {
57
+ this.unbindGlobal();
58
+ }
59
+ },
60
+ positionPopover() {
61
+ const trigger = this.$refs.triggerBtn;
62
+ const pop = this.$refs.popover;
63
+ if (!trigger || !pop) return;
64
+
65
+ const rect = trigger.getBoundingClientRect();
66
+ let top = rect.bottom + 6;
67
+ let center = rect.left + rect.width / 2;
68
+
69
+ const popWidth = pop.offsetWidth || 0;
70
+ const vw = window.innerWidth || document.documentElement.clientWidth;
71
+ const margin = 8;
72
+
73
+ const minCenter = margin + popWidth / 2;
74
+ const maxCenter = vw - margin - popWidth / 2;
75
+ center = Math.max(minCenter, Math.min(maxCenter, center));
76
+
77
+ const popHeight = pop.offsetHeight || 0;
78
+ const vh = window.innerHeight || document.documentElement.clientHeight;
79
+ const spaceBelow = vh - rect.bottom;
80
+ const spaceAbove = rect.top;
81
+
82
+ if (spaceBelow < popHeight + 12 && spaceAbove > popHeight + 12) {
83
+ top = rect.top - 6 - popHeight;
84
+ }
85
+
86
+ this.popoverStyle = {
87
+ position: "fixed",
88
+ top: `${Math.round(top)}px`,
89
+ left: `${Math.round(center)}px`,
90
+ transform: "translateX(-50%)",
91
+ zIndex: 9999
92
+ };
93
+ },
94
+ onOutsideClick(e) {
95
+ const wrap = this.$refs.wrap;
96
+ const pop = this.$refs.popover;
97
+ if ((wrap && wrap.contains(e.target)) || (pop && pop.contains(e.target))) return;
98
+ this.showMenu = false;
99
+ this.unbindGlobal();
100
+
101
+ },
102
+ onKeydown(e) {
103
+ if (e.key === "Escape") {
104
+ this.showMenu = false;
105
+ this.unbindGlobal();
106
+ }
107
+ },
108
+ unbindGlobal() {
109
+ window.removeEventListener("scroll", this.positionPopover, true);
110
+ window.removeEventListener("resize", this.positionPopover, true);
111
+ document.removeEventListener("click", this.onOutsideClick, true);
112
+ document.removeEventListener("keydown", this.onKeydown, true);
113
+ },
114
+ onAction(btn) {
115
+ this.$emit("action", btn);
116
+ this.showMenu = false;
117
+ this.unbindGlobal();
118
+ this.addEvent({ name: btn.eventName, data: btn.eventData });
119
+ },
120
+ buttonStyle(btn, i) {
121
+ const size = btn.size || this.defaultSize;
122
+ return {
123
+ backgroundColor: btn.color || "#577696",
124
+ width: size + "px",
125
+ height: size + "px",
126
+ transitionDelay: `${i * 45}ms`
127
+ };
128
+ }
129
+ },
130
+ beforeDestroy() {
131
+ this.unbindGlobal();
132
+ }
133
+ };
134
+ </script>
135
+
136
+ <style scoped>
137
+ .button-inline-group {
138
+ position: relative;
139
+ display: inline-block;
140
+ }
141
+
142
+ .button-inline-trigger {
143
+ display: inline-flex;
144
+ align-items: center;
145
+ justify-content: center;
146
+ width: 34px;
147
+ height: 34px;
148
+ border-radius: 50%;
149
+ background: transparent;
150
+ border: none;
151
+ color: #9b9ea3;
152
+ font-size: 16px;
153
+ cursor: pointer;
154
+ transition: all 0.2s ease;
155
+ }
156
+
157
+ .button-inline-trigger:hover {
158
+ background: #f3f4f6;
159
+ color: #111827;
160
+ }
161
+
162
+ .button-inline-popover-fixed {
163
+ position: fixed;
164
+ pointer-events: auto;
165
+ }
166
+
167
+ .button-inline-popover-inner {
168
+ background: #ffffff;
169
+ border: 1px solid #e6ebf2;
170
+ border-radius: 50px;
171
+ box-shadow: 0 8px 24px rgba(16, 24, 40, 0.12);
172
+ padding: 2px;
173
+ }
174
+
175
+ .button-inline-buttons {
176
+ display: flex;
177
+ justify-content: center;
178
+ align-items: center;
179
+ gap: 12px;
180
+ padding: 4px;
181
+ }
182
+
183
+ .button-inline-btn {
184
+ border-radius: 50%;
185
+ border: none;
186
+ color: #fff;
187
+ display: flex;
188
+ align-items: center;
189
+ justify-content: center;
190
+ font-size: 15px;
191
+ cursor: pointer;
192
+ transition: all 0.18s ease;
193
+ }
194
+
195
+ .button-inline-btn:hover {
196
+ filter: brightness(0.9);
197
+ transform: translateY(-2px);
198
+ }
199
+
200
+ /* Fade animado */
201
+ .button-inline-fade-enter-active,
202
+ .button-inline-fade-leave-active {
203
+ transition: opacity 0.25s ease;
204
+ }
205
+
206
+ .button-inline-fade-enter,
207
+ .button-inline-fade-leave-to {
208
+ opacity: 0;
209
+ }
210
+ </style>
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div class="form-group">
3
- <label>
3
+ <label v-if="title">
4
4
  <span class="title">{{ title }} </span>
5
5
  <span class="required" v-if="required">*</span>
6
6
  <Tip :field="field" :formName="formName" />
@@ -1,193 +1,452 @@
1
1
  <template>
2
- <div class="date-filter">
3
- <button @click="previous" class="navigation-button" :class="[buttonClass, { 'disabled': disabled }]"
4
- :disabled="disabled">
5
- <i class="fa-solid fa-chevron-left"></i>
2
+ <div class="date-year-month-wrap">
3
+ <div v-if="hasSelection" class="date-year-month-row">
4
+ <div class="date-year-month-chip">
5
+ <i class="fa-regular fa-calendar"></i>
6
+ <span>{{ badgeLabel }}</span>
7
+ <button class="date-year-month-chip-close" @click="clearSelection" :disabled="disabled">
8
+ <i class="fa-solid fa-xmark"></i>
6
9
  </button>
7
- <span class="date-display" :class="{ 'title-disabled': disabled }">{{ displayValue }} </span>
8
- <span class="date-icon" @click="resetSelection()" :class="{ 'disabled': disabled }">
9
- <i class="fa-solid fa-calendar-week"></i>
10
- </span>
11
- <button @click="next" class="navigation-button" :class="[buttonClass, { 'disabled': disabled }]"
12
- :disabled="disabled">
13
- <i class="fa-solid fa-chevron-right"></i>
10
+ </div>
11
+ </div>
12
+
13
+ <div v-else class="date-year-month-row" ref="anchor">
14
+ <div class="date-year-month-group">
15
+ <button class="date-year-month-nav date-year-month-year" @click="decYear" :disabled="disabled">
16
+ <i class="fa-solid fa-angle-left"></i>
17
+ </button>
18
+ <span class="date-year-month-label" :class="{ 'date-year-month-muted': disabled }">{{ year }}</span>
19
+ <button class="date-year-month-nav date-year-month-year" @click="incYear" :disabled="disabled">
20
+ <i class="fa-solid fa-angle-right"></i>
21
+ </button>
22
+ </div>
23
+
24
+ <div class="date-year-month-sep" v-if="mode === 'month'"></div>
25
+
26
+ <div class="date-year-month-group" v-if="mode === 'month'">
27
+ <button class="date-year-month-nav date-year-month-month" @click="decMonth" :disabled="disabled">
28
+ <i class="fa-solid fa-angle-left"></i>
29
+ </button>
30
+ <span class="date-year-month-label" :class="{ 'date-year-month-muted': disabled }">{{ monthName }}</span>
31
+ <button class="date-year-month-nav date-year-month-month" @click="incMonth" :disabled="disabled">
32
+ <i class="fa-solid fa-angle-right"></i>
14
33
  </button>
34
+ </div>
35
+
36
+ <div class="date-year-month-actions">
37
+ <div class="date-year-month-dropdown" ref="dd">
38
+ <button class="date-year-month-btn" @click="toggleMenu" :disabled="disabled">
39
+ <i class="fa-solid fa-filters"></i>
40
+ <span>{{ currentOptionLabel }}</span>
41
+ <i class="fa-solid fa-chevron-down"></i>
42
+ </button>
43
+ <ul v-if="menuOpen" class="date-year-month-menu">
44
+ <li class="date-year-month-option" @click="selectOption('today')">Hoje</li>
45
+ <li class="date-year-month-option" @click="selectOption('year')">Ano</li>
46
+ <li class="date-year-month-option" @click="selectOption('all')">Todos</li>
47
+ <li class="date-year-month-option" @click="selectOption('periodCustom')">Vencimento</li>
48
+ </ul>
49
+ </div>
50
+ </div>
15
51
  </div>
52
+
53
+ <transition name="date-year-month-pop">
54
+ <div v-if="showRange && !disabled" class="date-year-month-popover" :style="popoverStyle">
55
+ <div class="date-year-month-title">Período</div>
56
+ <DateTime :confirm="true" :range="true" field="dueDate" format="DD/MM/YYYY" type="date" placeholder
57
+ :required="false" v-model="startEnd" />
58
+ <div class="date-year-month-actions-row">
59
+ <button class="date-year-month-btn date-year-month-btn-ghost" @click="closeRange">Cancelar</button>
60
+ <button class="date-year-month-btn date-year-month-btn-apply" :disabled="!startEnd.length"
61
+ @click="applyStaticRange">Aplicar</button>
62
+ </div>
63
+ </div>
64
+ </transition>
65
+
66
+ <transition name="date-year-month-pop">
67
+ <div v-if="showYearPicker && !disabled" class="date-year-month-popover" :style="popoverStyle">
68
+ <div class="date-year-month-title">Ano</div>
69
+ <div class="date-year-month-year-div">
70
+ <div class="date-year-month-year-row">
71
+ <button class="date-year-month-year-ctrl" @click="tempYear--" :disabled="tempYear <= 0"><i
72
+ class="fa-solid fa-minus"></i></button>
73
+ <input class="date-year-month-year-input" type="number" v-model.number="tempYear" />
74
+ <button class="date-year-month-year-ctrl" @click="tempYear++"><i class="fa-solid fa-plus"></i></button>
75
+ </div>
76
+ <div class="date-year-month-actions-row">
77
+ <button class="date-year-month-btn date-year-month-btn-ghost" @click="closeYearPicker">Cancelar</button>
78
+ <button class="date-year-month-btn date-year-month-btn-apply" @click="applyYear">Aplicar</button>
79
+ </div>
80
+ </div>
81
+ </div>
82
+ </transition>
83
+ </div>
16
84
  </template>
17
85
 
18
86
  <script>
19
- import { mapMutations } from "vuex";
87
+
88
+ import DateTime from "@nixweb/nixloc-ui/src/component/forms/DateTime";
89
+
90
+ const PT_BR_MONTHS = ["Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"];
20
91
 
21
92
  export default {
22
- props: {
23
- mode: {
24
- type: String,
25
- required: true,
26
- validator(value) {
27
- return ['month', 'year'].includes(value);
28
- }
29
- },
30
- value: {
31
- type: Number,
32
- required: false
33
- },
34
- disabled: {
35
- type: Boolean,
36
- default: false
37
- }
93
+ name: "DateYearMonth",
94
+ components: {
95
+ DateTime
96
+ },
97
+ model: { prop: "value", event: "input" },
98
+ props: {
99
+ mode: { type: String, required: true, validator: v => ["year", "month"].includes(v) },
100
+ value: {
101
+ type: Object,
102
+ default: () => ({
103
+ optionSelected: "",
104
+ startEnd: [],
105
+ year: new Date().getFullYear(),
106
+ month: new Date().getMonth() + 1
107
+ })
108
+ },
109
+ disabled: { type: Boolean, default: false }
110
+ },
111
+ data() {
112
+ const v = this.value || {};
113
+ return {
114
+ optionSelected: v.optionSelected || "",
115
+ startEnd: Array.isArray(v.startEnd) ? v.startEnd : [],
116
+ year: Number.isFinite(v.year) ? v.year : new Date().getFullYear(),
117
+ month: Number.isFinite(v.month) ? v.month : (new Date().getMonth() + 1),
118
+ showRange: false,
119
+ showYearPicker: false,
120
+ tempYear: Number.isFinite(v.year) ? v.year : new Date().getFullYear(),
121
+ popoverStyle: {},
122
+ menuOpen: false
123
+ };
124
+ },
125
+ created() { this.emitChanged(); },
126
+ computed: {
127
+ monthName() { return PT_BR_MONTHS[(this.month - 1 + 12) % 12]; },
128
+ hasSelection() { return ["all", "today", "periodCustom", "year"].includes(this.optionSelected); },
129
+ badgeLabel() {
130
+ if (this.optionSelected === "all") return "Todos";
131
+ if (this.optionSelected === "today") return "Hoje";
132
+ if (this.optionSelected === "year") return `Ano ${this.year}`;
133
+ if (this.optionSelected === "periodCustom") return `${this.startEnd[0]} — ${this.startEnd[1]}`;
134
+ return "";
135
+ },
136
+ currentOptionLabel() {
137
+ if (this.optionSelected === "all") return "Todos";
138
+ if (this.optionSelected === "today") return "Hoje";
139
+ if (this.optionSelected === "year") return "Ano";
140
+ if (this.optionSelected === "periodCustom") return "Personalizado";
141
+ return "Mais";
142
+ }
143
+ },
144
+ watch: {
145
+ value: {
146
+ deep: true,
147
+ handler(v) {
148
+ if (!v) return;
149
+ this.optionSelected = v.optionSelected || "";
150
+ this.startEnd = Array.isArray(v.startEnd) ? v.startEnd : [];
151
+ if (Number.isFinite(v.year)) { this.year = v.year; this.tempYear = v.year; }
152
+ if (Number.isFinite(v.month)) this.month = v.month;
153
+ }
154
+ }
155
+ },
156
+ methods: {
157
+ payload() {
158
+ return {
159
+ optionSelected: this.optionSelected,
160
+ startEnd: this.optionSelected === "periodCustom" ? this.startEnd : [],
161
+ year: this.year,
162
+ month: this.month
163
+ };
38
164
  },
39
- data() {
40
- return {
41
- currentMonth: new Date().getMonth(),
42
- currentYear: new Date().getFullYear()
43
- };
165
+ emitChanged() {
166
+ const p = this.payload();
167
+ this.$emit("input", p);
168
+ this.$emit("changed", p);
44
169
  },
45
- created() {
46
- this.resetSelection();
170
+ // navegação padrão
171
+ decYear() { if (this.disabled) return; this.year--; this.emitChanged(); },
172
+ incYear() { if (this.disabled) return; this.year++; this.emitChanged(); },
173
+ decMonth() {
174
+ if (this.disabled || this.mode !== "month") return;
175
+ if (this.month === 1) { this.month = 12; this.year--; } else { this.month--; }
176
+ this.emitChanged();
47
177
  },
48
- mounted() {
49
- this.sendEvent();
178
+ incMonth() {
179
+ if (this.disabled || this.mode !== "month") return;
180
+ if (this.month === 12) { this.month = 1; this.year++; } else { this.month++; }
181
+ this.emitChanged();
50
182
  },
51
- computed: {
52
- displayValue() {
53
- if (this.mode === 'month') {
54
- return this.monthNames[this.currentMonth];
55
- } else {
56
- return this.currentYear;
57
- }
58
- },
59
- monthNames() {
60
- return ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'];
61
- },
62
- buttonClass() {
63
- return this.mode === 'month' ? 'month-mode' : 'year-mode';
64
- }
183
+ // dropdown
184
+ toggleMenu() {
185
+ if (this.disabled) return;
186
+ this.menuOpen = !this.menuOpen;
187
+ if (this.menuOpen) document.addEventListener("click", this.onClickOutside);
65
188
  },
66
- methods: {
67
- ...mapMutations("generic", ["addEvent"]),
68
- previous() {
69
- if (this.disabled) return;
70
- if (this.mode === 'month') {
71
- this.currentMonth = (this.currentMonth + 11) % 12;
72
- if (this.currentMonth === 11) {
73
- this.currentYear--;
74
- }
75
- } else {
76
- this.currentYear--;
77
- }
78
- this.$emit('input', this.getValue());
79
- this.sendEvent();
80
- },
81
- next() {
82
- if (this.disabled) return;
83
- if (this.mode === 'month') {
84
- this.currentMonth = (this.currentMonth + 1) % 12;
85
- if (this.currentMonth === 0) {
86
- this.currentYear++;
87
- }
88
- } else {
89
- this.currentYear++;
90
- }
91
- this.$emit('input', this.getValue());
92
- this.sendEvent();
93
- },
94
- resetSelection() {
95
- const now = new Date();
96
- if (this.mode === 'month') {
97
- this.currentMonth = now.getMonth();
98
- this.currentYear = now.getFullYear();
99
- } else {
100
- this.currentYear = now.getFullYear();
101
- }
102
- this.sendEvent();
103
- },
104
- getValue() {
105
- if (this.mode === 'month') {
106
- return (this.currentMonth + 1).toString();
107
- } else {
108
- return this.currentYear.toString();
109
- }
110
- },
111
- sendEvent() {
112
- this.addEvent({ name: "dateYearMonth", data: { mode: this.mode, value: this.getValue() } });
113
- this.$emit('input', this.getValue());
114
- }
189
+ onClickOutside(e) {
190
+ const root = this.$refs.dd;
191
+ if (root && !root.contains(e.target)) {
192
+ this.menuOpen = false;
193
+ document.removeEventListener("click", this.onClickOutside);
194
+ }
115
195
  },
116
- watch: {
117
- mode(newMode) {
118
- this.resetSelection();
119
- },
120
- value(newValue) {
121
- if (this.mode === 'month') {
122
- this.currentMonth = newValue - 1;
123
- } else {
124
- this.currentYear = newValue;
125
- }
126
- }
196
+ selectOption(opt) {
197
+ this.menuOpen = false;
198
+ if (opt === "all") { this.optionSelected = "all"; this.startEnd = []; this.emitChanged(); return; }
199
+ if (opt === "today") { this.optionSelected = "today"; this.startEnd = []; this.emitChanged(); return; }
200
+ if (opt === "year") { this.openYearPicker(); return; }
201
+ this.openCustom();
127
202
  },
203
+ // período custom (estático)
204
+ openCustom() { this.positionPopover(); this.showRange = true; },
205
+ closeRange() { this.showRange = false; },
206
+ applyStaticRange() {
207
+ this.optionSelected = "periodCustom";
208
+ this.showRange = false;
209
+ this.emitChanged();
210
+ },
211
+ // ano custom
212
+ openYearPicker() {
213
+ this.positionPopover();
214
+ this.tempYear = this.year || new Date().getFullYear();
215
+ this.showYearPicker = true;
216
+ },
217
+ closeYearPicker() { this.showYearPicker = false; },
218
+ applyYear() {
219
+ const y = Number(this.tempYear);
220
+ if (Number.isFinite(y)) {
221
+ this.year = y;
222
+ this.optionSelected = "year";
223
+ this.emitChanged();
224
+ }
225
+ this.showYearPicker = false;
226
+ },
227
+ // limpar seleção volta pro padrão (mês/ano atual)
228
+ clearSelection() {
229
+ this.optionSelected = "";
230
+ this.startEnd = [];
231
+ const now = new Date();
232
+ this.year = now.getFullYear();
233
+ this.month = now.getMonth() + 1;
234
+ this.showRange = false;
235
+ this.showYearPicker = false;
236
+ this.emitChanged();
237
+ },
238
+ positionPopover() {
239
+ this.$nextTick(() => {
240
+ const el = this.$refs.anchor; if (!el) return;
241
+ const r = el.getBoundingClientRect();
242
+ this.popoverStyle = { left: r.left + "px", top: (r.bottom + 8) + "px", minWidth: r.width + "px", position: "fixed" };
243
+ });
244
+ }
245
+ }
128
246
  };
129
247
  </script>
130
248
 
131
249
  <style scoped>
132
- .date-filter {
133
- display: flex;
134
- align-items: center;
135
- justify-content: center;
136
- gap: 20px;
250
+ .date-year-month-wrap {
251
+ position: relative;
252
+ display: inline-block
253
+ }
254
+
255
+ .date-year-month-row {
256
+ display: flex;
257
+ align-items: center;
258
+ gap: 12px;
259
+ flex-wrap: wrap;
260
+ justify-content: flex-start
261
+ }
262
+
263
+ .date-year-month-actions {
264
+ margin-left: auto;
265
+ position: relative
266
+ }
267
+
268
+ .date-year-month-btn {
269
+ display: inline-flex;
270
+ align-items: center;
271
+ gap: 8px;
272
+ height: 32px;
273
+ padding: 0 12px;
274
+ border-radius: 999px;
275
+ background: #F0F0F0;
276
+ border: 1px solid #e5e7eb;
277
+ font-size: 13px;
278
+ letter-spacing: 1px;
279
+ cursor: pointer
280
+ }
281
+
282
+ .date-year-month-dropdown {
283
+ position: relative
284
+ }
285
+
286
+ .date-year-month-menu {
287
+ position: absolute;
288
+ top: 38px;
289
+ right: 0;
290
+ list-style: none;
291
+ margin: 0;
292
+ padding: 6px;
293
+ background: #fff;
294
+ border: 1px solid #e5e7eb;
295
+ border-radius: 10px;
296
+ min-width: 150px;
297
+ box-shadow: 0 10px 24px rgba(16, 24, 40, .12);
298
+ z-index: 1001
299
+ }
300
+
301
+ .date-year-month-menu li {
302
+ padding: 8px 10px;
303
+ border-radius: 8px;
304
+ cursor: pointer;
305
+ font-weight: 500;
306
+ color: #374151
307
+ }
308
+
309
+ .date-year-month-menu li:hover {
310
+ background: #f3f4f6
311
+ }
312
+
313
+ .date-year-month-group {
314
+ display: inline-flex;
315
+ align-items: center;
316
+ gap: 8px
317
+ }
318
+
319
+ .date-year-month-label {
320
+ font-weight: 700;
321
+ color: #1f2937
322
+ }
323
+
324
+ .date-year-month-muted {
325
+ color: #9ca3af
326
+ }
327
+
328
+ .date-year-month-sep {
329
+ width: 1px;
330
+ height: 22px;
331
+ background: #e5e7eb;
332
+ border-radius: 1px
333
+ }
334
+
335
+ .date-year-month-nav {
336
+ width: 32px;
337
+ height: 32px;
338
+ border-radius: 999px;
339
+ border: none;
340
+ cursor: pointer;
341
+ color: #fff;
342
+ display: inline-flex;
343
+ align-items: center;
344
+ justify-content: center
345
+ }
346
+
347
+ .date-year-month-year {
348
+ background: #3b82f6
349
+ }
350
+
351
+ .date-year-month-month {
352
+ background: #f59e0b
353
+ }
354
+
355
+ .date-year-month-chip {
356
+ display: inline-flex;
357
+ align-items: center;
358
+ gap: 8px;
359
+ padding: 6px 10px;
360
+ border-radius: 999px;
361
+ background: #f3f4f6;
362
+ border: 1px solid #e5e7eb;
363
+ font-weight: 600;
364
+ color: #111827
365
+ }
366
+
367
+ .date-year-month-chip-close {
368
+ border: none;
369
+ background: transparent;
370
+ cursor: pointer;
371
+ color: #6b7280;
372
+ display: inline-flex;
373
+ align-items: center
374
+ }
375
+
376
+ .date-year-month-pop-enter-active,
377
+ .date-year-month-pop-leave-active {
378
+ transition: all .18s ease
379
+ }
380
+
381
+ .date-year-month-pop-enter-from,
382
+ .date-year-month-pop-leave-to {
383
+ opacity: 0;
384
+ transform: translateY(-4px)
137
385
  }
138
386
 
139
- .navigation-button {
140
- color: white;
141
- border: none;
142
- border-radius: 50%;
143
- width: 30px;
144
- height: 30px;
145
- display: flex;
146
- align-items: center;
147
- justify-content: center;
148
- font-size: 1.2em;
149
- cursor: pointer;
150
- transition: background-color 0.3s, transform 0.2s;
387
+ .date-year-month-popover {
388
+ z-index: 1000;
389
+ background: #fff;
390
+ border: 1px solid #e5e7eb;
391
+ border-radius: 12px;
392
+ padding: 12px;
393
+ box-shadow: 0 10px 24px rgba(16, 24, 40, .12)
151
394
  }
152
395
 
153
- .navigation-button:hover {
154
- opacity: 0.8;
396
+ .date-year-month-title {
397
+ font-weight: 700;
398
+ color: #1f2937;
399
+ margin-bottom: 8px
155
400
  }
156
401
 
157
- .navigation-button:active {
158
- transform: scale(0.95);
402
+ .date-year-month-year-div {
403
+ margin: 0px 0px 0px 100px;
159
404
  }
160
405
 
161
- .navigation-button.disabled {
162
- background-color: #b0b0b0;
163
- opacity: 0.5;
164
- cursor: not-allowed;
406
+ .date-year-month-actions-row {
407
+ display: flex;
408
+ justify-content: flex-end;
409
+ gap: 8px;
410
+ margin-top: 20px
165
411
  }
166
412
 
167
- .date-display {
168
- font-size: 1.5em;
169
- font-weight: bold;
413
+ .date-year-month-btn-ghost {
414
+ background: transparent;
415
+ border: 1px solid #e5e7eb
170
416
  }
171
417
 
172
- .month-mode {
173
- background-color: #ffa500;
418
+ .date-year-month-btn-apply {
419
+ background: #0ea5e9;
420
+ border: 1px solid #fff;
421
+ color: #fff
174
422
  }
175
423
 
176
- .year-mode {
177
- background-color: #007bff;
424
+ .date-year-month-btn-apply:hover {
425
+ background: #3E90B3;
178
426
  }
179
427
 
180
- .date-icon {
181
- cursor: pointer;
182
- color: rgb(73, 72, 72);
428
+ .date-year-month-year-row {
429
+ display: flex;
430
+ align-items: center;
431
+ gap: 8px;
432
+ margin: 6px 0 2px
183
433
  }
184
434
 
185
- .title-disabled {
186
- color: #b0b0b0;
435
+ .date-year-month-year-input {
436
+ height: 34px;
437
+ width: 110px;
438
+ text-align: center;
439
+ border: 1px solid #e5e7eb;
440
+ border-radius: 8px
187
441
  }
188
442
 
189
- .date-icon.disabled {
190
- color: #b0b0b0;
191
- cursor: not-allowed;
443
+ .date-year-month-year-ctrl {
444
+ height: 32px;
445
+ width: 32px;
446
+ border-radius: 50px;
447
+ border: 1px solid #3C82F6;
448
+ color: white;
449
+ background: #3C82F6;
450
+ cursor: pointer
192
451
  }
193
452
  </style>
@@ -4,9 +4,14 @@
4
4
  :closeButton="closeButton" :closeOnClickMask="false">
5
5
  <div class="d-block text-left">
6
6
  <Messages v-if="!vodal.open && showValidation" />
7
- <div class="title">{{ title }}</div>
8
- <hr class="hr" />
9
- <slot></slot>
7
+ <div class="modal-title title">
8
+ <h5>
9
+ {{ title }}
10
+ </h5>
11
+ </div>
12
+ <div class="modal-body">
13
+ <slot></slot>
14
+ </div>
10
15
  </div>
11
16
  <div slot="modal-footer"></div>
12
17
  </vodal>
@@ -58,4 +63,17 @@ export default {
58
63
  font-size: 18px;
59
64
  margin-bottom: 5px;
60
65
  }
66
+
67
+ .modal-body {
68
+ margin-bottom: 0px;
69
+ border: 0px solid #f1f5f9;
70
+ border-radius: 0px 0px 10px 10px;
71
+ }
72
+
73
+ .modal-title {
74
+ padding: 15px;
75
+ margin: 0;
76
+ font-size: 16px;
77
+ }
78
+
61
79
  </style>
@@ -78,13 +78,6 @@ export default {
78
78
  };
79
79
  </script>
80
80
  <style>
81
- .custom-control-label {
82
- font-size: 13px !important;
83
- font-weight: 400 !important;
84
- text-transform: uppercase;
85
- color: #778498;
86
- }
87
-
88
81
  .options {
89
82
  margin-top: 5px;
90
83
  }
@@ -5,69 +5,27 @@
5
5
  <span class="required" v-if="required">*</span>
6
6
  <Tip :field="field" :formName="formName" />
7
7
  </label>
8
- <div
9
- class="select"
10
- :class="{
11
- 'select-invalid': notifications.length > 0 && formDirty,
12
- border: showBorder,
13
- }"
14
- >
15
- <multiselect
16
- @open="onOpen"
17
- @select="onSelected"
18
- @search-change="onSearch"
19
- :options="data"
20
- v-model.trim="currentValue"
21
- :custom-label="label"
22
- placeholder
23
- :disabled="disabled"
24
- :showLabels="true"
25
- :noResult="false"
26
- :maxHeight="250"
27
- ref="multiselect"
28
- selectLabel
29
- deselectLabel
30
- selectedLabel
31
- >
8
+ <div class="select" :class="{
9
+ 'select-invalid': notifications.length > 0 && formDirty,
10
+ border: showBorder,
11
+ }">
12
+ <multiselect @open="onOpen" @select="onSelected" @search-change="onSearch" :options="data"
13
+ v-model.trim="currentValue" :custom-label="label" placeholder :disabled="disabled" :showLabels="true"
14
+ :noResult="false" :maxHeight="250" ref="multiselect" selectLabel deselectLabel selectedLabel>
32
15
  <div slot="beforeList">
33
- <vue-loading
34
- v-if="loading"
35
- type="bubbles"
36
- color="#577696"
37
- :size="{ width: '40px', height: '40px' }"
38
- ></vue-loading>
16
+ <vue-loading v-if="loading" type="bubbles" color="#577696"
17
+ :size="{ width: '40px', height: '40px' }"></vue-loading>
39
18
  <div class="before-list" v-if="!loading">
40
- <Button
41
- v-if="showNewRegister"
42
- key="addNewRegister"
43
- type="primary"
44
- title="Novo"
45
- size="small"
46
- :clicked="addNewRegister"
47
- />
48
- <Button
49
- key="cleanSelect"
50
- title="Limpar"
51
- type="warning"
52
- classIcon="fas fa-broom"
53
- size="small"
54
- :clicked="cleanSelect"
55
- />
19
+ <Button v-if="showNewRegister" key="addNewRegister" type="primary" title="Novo" size="small"
20
+ :clicked="addNewRegister" />
21
+ <Button key="cleanSelect" title="Limpar" type="warning" classIcon="fas fa-broom" size="small"
22
+ :clicked="cleanSelect" />
56
23
  </div>
57
24
  </div>
58
- <div
59
- slot="afterList"
60
- v-if="totalRecords > 20 && data.length < totalRecords"
61
- >
25
+ <div slot="afterList" v-if="totalRecords > 20 && data.length < totalRecords">
62
26
  <div class="after-list text-center">
63
- <Button
64
- key="loadingMore"
65
- type="info"
66
- title="Carregar mais..."
67
- classIcon="fas fa-redo-alt"
68
- size="small"
69
- :clicked="loadingMore"
70
- />
27
+ <Button key="loadingMore" type="info" title="Carregar mais..." classIcon="fas fa-redo-alt" size="small"
28
+ :clicked="loadingMore" />
71
29
  </div>
72
30
  </div>
73
31
  <div slot="noOptions">Lista está vazia</div>
@@ -80,20 +38,13 @@
80
38
  </div>
81
39
  </div>
82
40
 
83
- <vodal
84
- v-if="nameNewRegister"
85
- :duration="50"
86
- :show="showVodal(nameNewRegister)"
87
- @hide="hide()"
88
- :width="widthNewRegister"
89
- :height="heightNewRegister"
90
- :closeOnEsc="true"
91
- :closeButton="true"
92
- >
41
+ <vodal v-if="nameNewRegister" :duration="50" :show="showVodal(nameNewRegister)" @hide="hide()"
42
+ :width="widthNewRegister" :height="heightNewRegister" :closeOnEsc="true" :closeButton="true">
93
43
  <Messages v-if="vodal.open" />
94
- <div class="title-new-form">{{ titleNewRegister }}</div>
95
- <hr class="hr" />
96
- <slot></slot>
44
+ <div class="select-vodal">
45
+ <div class="title-new-form">{{ titleNewRegister }}</div>
46
+ <slot></slot>
47
+ </div>
97
48
  </vodal>
98
49
  </div>
99
50
  </template>
@@ -375,4 +326,8 @@ div.after-list {
375
326
  border-radius: 5px !important;
376
327
  height: 38px !important;
377
328
  }
329
+
330
+ .select-vodal {
331
+ padding: 10px;
332
+ }
378
333
  </style>
@@ -35,7 +35,7 @@ export default {
35
35
  box-sizing: border-box;
36
36
  margin-bottom: 10px;
37
37
  color: rgba(0, 0, 0, 0.65);
38
- font-size: 14px;
38
+ font-size: 13.5px;
39
39
  font-variant: tabular-nums;
40
40
  line-height: 1.5;
41
41
  list-style: none;
@@ -43,7 +43,7 @@ export default {
43
43
  position: relative;
44
44
  padding: 8px 15px 8px 10px;
45
45
  word-wrap: break-word;
46
- border-radius: 4px;
46
+ border-radius: 10px;
47
47
  }
48
48
 
49
49
  .message {
@@ -94,7 +94,7 @@ export default {
94
94
  width: 38px;
95
95
  height: 38px;
96
96
  border-radius: 50%;
97
- background: #007bff;
97
+ background: #D98621;
98
98
  display: flex;
99
99
  align-items: center;
100
100
  justify-content: center;
@@ -110,7 +110,7 @@ export default {
110
110
  top: -5px;
111
111
  right: -5px;
112
112
  background: #fff;
113
- color: #007bff;
113
+ color: #D98621;
114
114
  font-size: 11px;
115
115
  font-weight: bold;
116
116
  border-radius: 50%;
@@ -119,7 +119,7 @@ export default {
119
119
  display: flex;
120
120
  align-items: center;
121
121
  justify-content: center;
122
- border: 2px solid #007bff;
122
+ border: 2px solid #D98621;
123
123
  }
124
124
 
125
125
  .bab-title {
@@ -152,7 +152,7 @@ table tbody tr td {
152
152
  }
153
153
 
154
154
  .td-checkbox {
155
- width: 30px;
155
+ width: 0px;
156
156
  }
157
157
 
158
158
  tr:hover {
@@ -226,7 +226,7 @@ tr:hover {
226
226
  padding: 3px 10px;
227
227
  border-radius: 999px;
228
228
  font-size: 12px;
229
- width: 150px;
229
+ min-width: 150px;
230
230
  margin-bottom: 10px;
231
231
  }
232
232
  </style>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div>
2
+ <div class="item-main">
3
3
  <div v-if="obj.type === 'period-rent'" class="period-rent" @click="navegateTo(obj, row)" :style="obj.styleBody"
4
4
  :class="convertClass(row[obj.fieldComparison], obj.classCssBody)">
5
5
  <DisplayPeriodRent :periodRent="row[obj.field]" :fontSize="14" :showDeliveryDevolution="true" />
@@ -18,7 +18,7 @@
18
18
  {{ row[obj.field] }}
19
19
  </div>
20
20
  </div>
21
- <div v-if="obj.type === 'html' && row[obj.field]" :style="obj.styleBody"
21
+ <div class="item-align-center" 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>
@@ -149,6 +149,7 @@ export default {
149
149
  <style scoped>
150
150
  .field-second {
151
151
  color: rgb(83, 82, 82);
152
+ font-size: 13px;
152
153
  }
153
154
 
154
155
  .table-currency {
@@ -213,4 +214,15 @@ export default {
213
214
  margin-top: 11px;
214
215
  width: 200px;
215
216
  }
217
+
218
+ .item-main {
219
+ display: table;
220
+ height: 100%;
221
+ width: 100%;
222
+ }
223
+
224
+ .item-main>* {
225
+ display: table-cell;
226
+ vertical-align: middle;
227
+ }
216
228
  </style>
@@ -23,8 +23,8 @@
23
23
  </ScrollBar>
24
24
 
25
25
  <div class="text-center">
26
- <Button key="closeErrors" title="Fechar" color="white" backGroundColor="#6B7280" size="small"
27
- :clicked="() => $emit('close')" />
26
+ <Button key="closeErrors" title="Fechar" classIcon="fa-solid fa-circle-xmark" color="white"
27
+ backGroundColor="#6D757B" size="small" :clicked="() => $emit('close')" />
28
28
  </div>
29
29
  </div>
30
30
  </template>
@@ -63,8 +63,8 @@ export default {
63
63
  }
64
64
 
65
65
  .error-details-title {
66
- font-weight: 600;
67
- color: #111827;
66
+ font-weight: 500;
67
+ color: #6b7280;
68
68
  }
69
69
 
70
70
  .error-details-sub {
@@ -10,7 +10,7 @@
10
10
  </div>
11
11
 
12
12
  <div v-else class="text-center">
13
- <Button key="finish" title="Fechar" classIcon="fa-solid fa-xmark" color="white" backGroundColor="#6D757B" size="medium"
13
+ <Button key="finish" title="Fechar" classIcon="fa-solid fa-circle-xmark" color="white" backGroundColor="#6D757B" size="medium"
14
14
  :clicked="finished" />
15
15
  </div>
16
16
  </div>
@@ -452,12 +452,13 @@ export default {
452
452
  } else {
453
453
 
454
454
  context.commit('addNotifications', response.data.notifications)
455
- context.commit('addToast', 'messageError');
455
+
456
+ if (!params.notNotifyToast)
457
+ context.commit('addToast', 'messageError');
456
458
 
457
459
  return response.data;
458
460
  }
459
461
  }, (err) => {
460
- console.log(err);
461
462
  if (err.response)
462
463
  if (err.response.status === 403) {
463
464
  context.commit('addNotifications', [{ message: "Usuário sem permissão para executar esta ação!" }])