@nixweb/nixloc-ui 1.3.0 → 1.5.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.
@@ -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>