@weni/unnnic-system 3.35.2 → 3.35.3

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": "@weni/unnnic-system",
3
- "version": "3.35.2",
3
+ "version": "3.35.3",
4
4
  "type": "commonjs",
5
5
  "files": [
6
6
  "dist",
@@ -88,6 +88,7 @@
88
88
  "@iconify/vue": "^5.0.0",
89
89
  "@vueuse/components": "^10.4.1",
90
90
  "@vueuse/core": "^14.0.0",
91
+ "date-fns": "^4.4.0",
91
92
  "dayjs": "^1.11.23",
92
93
  "emoji-mart-vue-fast": "^15.0.5",
93
94
  "lucide-vue-next": "^0.546.0",
@@ -143,4 +144,4 @@
143
144
  "vue-eslint-parser": "^10.4.0",
144
145
  "vue-tsc": "^3.0.5"
145
146
  }
146
- }
147
+ }
@@ -14,7 +14,7 @@
14
14
  </template>
15
15
 
16
16
  <script>
17
- import dayjs from '@/utils/date';
17
+ import { formatDate } from '@/utils/formatDate';
18
18
  import UnnnicInput from '../Input/Input.vue';
19
19
 
20
20
  export default {
@@ -45,23 +45,26 @@ export default {
45
45
  type: String,
46
46
  default: '',
47
47
  },
48
+ useDateFns: {
49
+ type: Boolean,
50
+ default: false,
51
+ },
48
52
  },
49
53
  computed: {
50
54
  dateRange() {
51
55
  let range = '';
56
+ const outputFormat = this.dateFormat.replaceAll('/', '');
52
57
 
53
58
  if (this.startDate) {
54
- const formattedDate = dayjs(this.startDate).format(
55
- this.dateFormat.replaceAll('/', ''),
56
- );
57
- range += formattedDate.toString();
59
+ range += formatDate(this.startDate, outputFormat, {
60
+ useDateFns: this.useDateFns,
61
+ });
58
62
  }
59
63
 
60
64
  if (this.endDate) {
61
- const formattedDate = dayjs(this.endDate).format(
62
- this.dateFormat.replaceAll('/', ''),
63
- );
64
- range += formattedDate.toString();
65
+ range += formatDate(this.endDate, outputFormat, {
66
+ useDateFns: this.useDateFns,
67
+ });
65
68
  }
66
69
 
67
70
  return range;
@@ -0,0 +1,41 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ import DateFilter from '../DateFilter.vue';
5
+
6
+ const factory = (props = {}) =>
7
+ mount(DateFilter, {
8
+ props,
9
+ });
10
+
11
+ describe('DateFilter.vue', () => {
12
+ it('computes dateRange from start and end dates with dayjs', () => {
13
+ const wrapper = factory({
14
+ startDate: new Date(2025, 0, 10),
15
+ endDate: new Date(2025, 0, 20),
16
+ dateFormat: 'DD/MM/YYYY',
17
+ });
18
+
19
+ expect(wrapper.vm.dateRange).toBe('1001202520012025');
20
+ wrapper.unmount();
21
+ });
22
+
23
+ it('computes dateRange from start and end dates with date-fns', () => {
24
+ const wrapper = factory({
25
+ startDate: new Date(2025, 0, 10),
26
+ endDate: new Date(2025, 0, 20),
27
+ dateFormat: 'DD/MM/YYYY',
28
+ useDateFns: true,
29
+ });
30
+
31
+ expect(wrapper.vm.dateRange).toBe('1001202520012025');
32
+ wrapper.unmount();
33
+ });
34
+
35
+ it('returns an empty dateRange when there is no date selected', () => {
36
+ const wrapper = factory();
37
+
38
+ expect(wrapper.vm.dateRange).toBe('');
39
+ wrapper.unmount();
40
+ });
41
+ });
@@ -53,7 +53,7 @@
53
53
 
54
54
  <script setup lang="ts">
55
55
  import { computed, ref, useSlots } from 'vue';
56
- import dayjs from '@/utils/date';
56
+ import { formatDate } from '@/utils/formatDate';
57
57
 
58
58
  import UnnnicInput from '../Input/Input.vue';
59
59
  import UnnnicDatePicker from '../DatePicker/DatePicker.vue';
@@ -110,6 +110,8 @@ interface InputDatePickerProps {
110
110
  disabled?: boolean;
111
111
 
112
112
  disableShowOverwrittenValue?: boolean;
113
+
114
+ useDateFns?: boolean;
113
115
  }
114
116
 
115
117
  const props = withDefaults(defineProps<InputDatePickerProps>(), {
@@ -133,6 +135,7 @@ const props = withDefaults(defineProps<InputDatePickerProps>(), {
133
135
  periodBaseDate: '',
134
136
  disabled: false,
135
137
  disableShowOverwrittenValue: false,
138
+ useDateFns: false,
136
139
  });
137
140
 
138
141
  const emit = defineEmits<{
@@ -146,6 +149,17 @@ const popoverAlign = computed<'start' | 'end'>(() =>
146
149
  props.position === 'right' ? 'end' : 'start',
147
150
  );
148
151
 
152
+ function formatPickerDate(
153
+ value: string,
154
+ outputFormat: string,
155
+ parseFormat?: string,
156
+ ) {
157
+ return formatDate(value, outputFormat, {
158
+ parseFormat,
159
+ useDateFns: props.useDateFns,
160
+ });
161
+ }
162
+
149
163
  const inputText = computed(() => {
150
164
  if (props.disableShowOverwrittenValue) {
151
165
  return filterText.value || '';
@@ -160,11 +174,11 @@ const filterText = computed(() => {
160
174
  const { start, end } = props.modelValue || {};
161
175
 
162
176
  if (start) {
163
- dates.push(dayjs(start, props.format).format(props.inputFormat || ''));
177
+ dates.push(formatPickerDate(start, props.inputFormat || '', props.format));
164
178
  }
165
179
 
166
180
  if (end) {
167
- dates.push(dayjs(end, props.format).format(props.inputFormat || ''));
181
+ dates.push(formatPickerDate(end, props.inputFormat || '', props.format));
168
182
  }
169
183
 
170
184
  if (!dates.length) {
@@ -178,38 +192,36 @@ const filterText = computed(() => {
178
192
 
179
193
  const initialStartDate = computed<string | undefined>(() => {
180
194
  return props.modelValue.start
181
- ? dayjs(props.modelValue.start, props.format).format('MM DD YYYY')
195
+ ? formatPickerDate(props.modelValue.start, 'MM DD YYYY', props.format)
182
196
  : undefined;
183
197
  });
184
198
 
185
199
  const initialEndDate = computed<string | undefined>(() => {
186
200
  return props.modelValue.end
187
- ? dayjs(props.modelValue.end, props.format).format('MM DD YYYY')
201
+ ? formatPickerDate(props.modelValue.end, 'MM DD YYYY', props.format)
188
202
  : undefined;
189
203
  });
190
204
 
191
205
  function emitSelectDate(date: { startDate: string; endDate: string }) {
192
206
  const { startDate, endDate } = date;
193
207
  const formattedDates: DateRangeValue = {
194
- start: dayjs(startDate, 'MM-DD-YYYY').format(props.format),
195
- end: dayjs(endDate, 'MM-DD-YYYY').format(props.format),
208
+ start: formatPickerDate(startDate, props.format, 'MM-DD-YYYY'),
209
+ end: formatPickerDate(endDate, props.format, 'MM-DD-YYYY'),
196
210
  };
197
211
 
198
212
  emit('selectDate', formattedDates);
199
213
  }
200
214
 
201
215
  function changeDate(value: { startDate: string; endDate: string }) {
202
- const startDate = value.startDate.replace(/(\d+)-(\d+)-(\d+)/, '$3-$1-$2');
203
-
204
- const endDate = value.endDate.replace(/(\d+)-(\d+)-(\d+)/, '$3-$1-$2');
205
-
206
216
  isPopoverOpen.value = false;
207
217
 
208
218
  emit('update:model-value', {
209
- start: startDate
210
- ? dayjs(startDate, 'YYYY-MM-DD').format(props.format)
219
+ start: value.startDate
220
+ ? formatPickerDate(value.startDate, props.format, 'MM-DD-YYYY')
221
+ : null,
222
+ end: value.endDate
223
+ ? formatPickerDate(value.endDate, props.format, 'MM-DD-YYYY')
211
224
  : null,
212
- end: endDate ? dayjs(endDate, 'YYYY-MM-DD').format(props.format) : null,
213
225
  });
214
226
  }
215
227
 
@@ -83,6 +83,21 @@ describe('InputDatePicker.vue', () => {
83
83
  withDates.unmount();
84
84
  });
85
85
 
86
+ it('computes filterText from start and end dates with date-fns', () => {
87
+ const withDates = factory({
88
+ modelValue: {
89
+ start: '2025-01-10',
90
+ end: '2025-01-20',
91
+ },
92
+ format: 'YYYY-MM-DD',
93
+ inputFormat: 'MM-DD-YYYY',
94
+ useDateFns: true,
95
+ });
96
+
97
+ expect(withDates.vm.filterText).toBe('01-10-2025 ~ 01-20-2025');
98
+ withDates.unmount();
99
+ });
100
+
86
101
  it('computes initialStartDate and initialEndDate for DatePicker', () => {
87
102
  const withDates = factory({
88
103
  modelValue: {
@@ -96,6 +111,20 @@ describe('InputDatePicker.vue', () => {
96
111
  withDates.unmount();
97
112
  });
98
113
 
114
+ it('computes initialStartDate and initialEndDate with date-fns', () => {
115
+ const withDates = factory({
116
+ modelValue: {
117
+ start: '2025-01-10',
118
+ end: '2025-01-20',
119
+ },
120
+ useDateFns: true,
121
+ });
122
+
123
+ expect(withDates.vm.initialStartDate).toBe('01 10 2025');
124
+ expect(withDates.vm.initialEndDate).toBe('01 20 2025');
125
+ withDates.unmount();
126
+ });
127
+
99
128
  it('emits selectDate with formatted dates when DatePicker emits change', async () => {
100
129
  wrapper.vm.isPopoverOpen = true;
101
130
  await wrapper.vm.$nextTick();
@@ -119,6 +148,65 @@ describe('InputDatePicker.vue', () => {
119
148
  });
120
149
  });
121
150
 
151
+ it('emits selectDate with formatted dates using date-fns', async () => {
152
+ wrapper = factory({ useDateFns: true });
153
+ wrapper.vm.isPopoverOpen = true;
154
+ await wrapper.vm.$nextTick();
155
+
156
+ const datePicker = wrapper.findComponent({ name: 'UnnnicDatePicker' });
157
+
158
+ await datePicker.vm.$emit('change', {
159
+ startDate: '01-10-2025',
160
+ endDate: '01-20-2025',
161
+ });
162
+
163
+ const emitted = wrapper.emitted('selectDate');
164
+ expect(emitted).toBeTruthy();
165
+
166
+ const [formatted] = emitted[0];
167
+ expect(formatted).toEqual({
168
+ start: '2025-01-10',
169
+ end: '2025-01-20',
170
+ });
171
+ });
172
+
173
+ it('formats unpadded DatePicker dates on selectDate', async () => {
174
+ wrapper.vm.isPopoverOpen = true;
175
+ await wrapper.vm.$nextTick();
176
+
177
+ const datePicker = wrapper.findComponent({ name: 'UnnnicDatePicker' });
178
+
179
+ await datePicker.vm.$emit('change', {
180
+ startDate: '9-21-2026',
181
+ endDate: '9-22-2026',
182
+ });
183
+
184
+ const [formatted] = wrapper.emitted('selectDate')[0];
185
+ expect(formatted).toEqual({
186
+ start: '2026-09-21',
187
+ end: '2026-09-22',
188
+ });
189
+ });
190
+
191
+ it('formats unpadded DatePicker dates on submit with date-fns', async () => {
192
+ wrapper = factory({ useDateFns: true });
193
+ wrapper.vm.isPopoverOpen = true;
194
+ await wrapper.vm.$nextTick();
195
+
196
+ const datePicker = wrapper.findComponent({ name: 'UnnnicDatePicker' });
197
+
198
+ await datePicker.vm.$emit('submit', {
199
+ startDate: '9-21-2026',
200
+ endDate: '9-22-2026',
201
+ });
202
+
203
+ const [newValue] = wrapper.emitted('update:model-value')[0];
204
+ expect(newValue).toEqual({
205
+ start: '2026-09-21',
206
+ end: '2026-09-22',
207
+ });
208
+ });
209
+
122
210
  it('emits update:model-value and closes dropdown when DatePicker emits submit', async () => {
123
211
  wrapper.vm.isPopoverOpen = true;
124
212
  await wrapper.vm.$nextTick();
@@ -144,6 +232,30 @@ describe('InputDatePicker.vue', () => {
144
232
  expect(wrapper.vm.isPopoverOpen).toBe(false);
145
233
  });
146
234
 
235
+ it('emits update:model-value with date-fns and closes dropdown on submit', async () => {
236
+ wrapper = factory({ useDateFns: true });
237
+ wrapper.vm.isPopoverOpen = true;
238
+ await wrapper.vm.$nextTick();
239
+
240
+ const datePicker = wrapper.findComponent({ name: 'UnnnicDatePicker' });
241
+
242
+ await datePicker.vm.$emit('submit', {
243
+ startDate: '01-10-2025',
244
+ endDate: '01-20-2025',
245
+ });
246
+
247
+ const emitted = wrapper.emitted('update:model-value');
248
+ expect(emitted).toBeTruthy();
249
+
250
+ const [newValue] = emitted[0];
251
+ expect(newValue).toEqual({
252
+ start: '2025-01-10',
253
+ end: '2025-01-20',
254
+ });
255
+
256
+ expect(wrapper.vm.isPopoverOpen).toBe(false);
257
+ });
258
+
147
259
  it('passes minDate, maxDate, options and periodBaseDate down to DatePicker', async () => {
148
260
  const props = {
149
261
  minDate: '2025-01-01',
@@ -0,0 +1,75 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import {
4
+ formatDate,
5
+ formatWithDateFns,
6
+ formatWithDayjs,
7
+ } from '@/utils/formatDate';
8
+
9
+ describe('formatDate helpers', () => {
10
+ it('formats ISO dates to input format with dayjs', () => {
11
+ expect(formatWithDayjs('2025-01-10', 'MM-DD-YYYY', 'YYYY-MM-DD')).toBe(
12
+ '01-10-2025',
13
+ );
14
+ });
15
+
16
+ it('formats ISO dates to input format with date-fns using dayjs tokens', () => {
17
+ expect(formatWithDateFns('2025-01-10', 'MM-DD-YYYY', 'YYYY-MM-DD')).toBe(
18
+ '01-10-2025',
19
+ );
20
+ });
21
+
22
+ it('formats picker dates to MM DD YYYY with both libraries', () => {
23
+ expect(formatWithDayjs('2025-01-10', 'MM DD YYYY', 'YYYY-MM-DD')).toBe(
24
+ '01 10 2025',
25
+ );
26
+ expect(formatWithDateFns('2025-01-10', 'MM DD YYYY', 'YYYY-MM-DD')).toBe(
27
+ '01 10 2025',
28
+ );
29
+ });
30
+
31
+ it('parses MM-DD-YYYY into YYYY-MM-DD with both libraries', () => {
32
+ expect(formatWithDayjs('01-10-2025', 'YYYY-MM-DD', 'MM-DD-YYYY')).toBe(
33
+ '2025-01-10',
34
+ );
35
+ expect(formatWithDateFns('01-10-2025', 'YYYY-MM-DD', 'MM-DD-YYYY')).toBe(
36
+ '2025-01-10',
37
+ );
38
+ });
39
+
40
+ it('parses unpadded DatePicker values like 9-21-2026 with both libraries', () => {
41
+ expect(formatWithDayjs('9-21-2026', 'YYYY-MM-DD', 'MM-DD-YYYY')).toBe(
42
+ '2026-09-21',
43
+ );
44
+ expect(formatWithDateFns('9-21-2026', 'YYYY-MM-DD', 'MM-DD-YYYY')).toBe(
45
+ '2026-09-21',
46
+ );
47
+ expect(formatWithDayjs('9-5-2026', 'YYYY-MM-DD', 'MM-DD-YYYY')).toBe(
48
+ '2026-09-05',
49
+ );
50
+ expect(formatWithDateFns('9-5-2026', 'YYYY-MM-DD', 'MM-DD-YYYY')).toBe(
51
+ '2026-09-05',
52
+ );
53
+ });
54
+
55
+ it('dispatches to dayjs by default and to date-fns when useDateFns is true', () => {
56
+ expect(
57
+ formatDate('2025-01-10', 'MM-DD-YYYY', { parseFormat: 'YYYY-MM-DD' }),
58
+ ).toBe('01-10-2025');
59
+
60
+ expect(
61
+ formatDate('2025-01-10', 'MM-DD-YYYY', {
62
+ parseFormat: 'YYYY-MM-DD',
63
+ useDateFns: true,
64
+ }),
65
+ ).toBe('01-10-2025');
66
+ });
67
+
68
+ it('formats Date objects with both libraries', () => {
69
+ const date = new Date(2025, 0, 10);
70
+
71
+ expect(formatWithDayjs(date, 'DDMMYYYY')).toBe('10012025');
72
+ expect(formatWithDateFns(date, 'DDMMYYYY')).toBe('10012025');
73
+ expect(formatDate(date, 'DDMMYYYY', { useDateFns: true })).toBe('10012025');
74
+ });
75
+ });
@@ -7,6 +7,7 @@ export default {
7
7
  dateFormat: { control: { type: 'text' } },
8
8
  startDate: { control: { type: 'date' } },
9
9
  endDate: { control: { type: 'date' } },
10
+ useDateFns: { control: 'boolean' },
10
11
  },
11
12
  };
12
13
 
@@ -17,6 +17,9 @@ export default {
17
17
  options: ['left', 'right'],
18
18
  },
19
19
  },
20
+ useDateFns: {
21
+ control: 'boolean',
22
+ },
20
23
  },
21
24
  render: (args) => ({
22
25
  components: {
@@ -0,0 +1,76 @@
1
+ import {
2
+ format as dateFnsFormat,
3
+ isValid,
4
+ parse as dateFnsParse,
5
+ } from 'date-fns';
6
+
7
+ import dayjs from '@/utils/date';
8
+
9
+ export type DateInput = string | Date;
10
+
11
+ export type FormatDateOptions = {
12
+ parseFormat?: string;
13
+ useDateFns?: boolean;
14
+ };
15
+
16
+ function toDateFnsFormatPattern(dayjsPattern: string): string {
17
+ return dayjsPattern.replaceAll('YYYY', 'yyyy').replaceAll('DD', 'dd');
18
+ }
19
+
20
+ // DatePicker emits unpadded values like "9-21-2026". Padded tokens (MM/DD)
21
+ // fail to parse those in dayjs; unpadded tokens still accept "09-21-2026".
22
+ function toFlexibleDayjsParsePattern(dayjsPattern: string): string {
23
+ return dayjsPattern.replaceAll('DD', 'D').replaceAll('MM', 'M');
24
+ }
25
+
26
+ function toDateFnsParsePattern(dayjsPattern: string): string {
27
+ return toFlexibleDayjsParsePattern(dayjsPattern)
28
+ .replaceAll('YYYY', 'yyyy')
29
+ .replaceAll('D', 'd');
30
+ }
31
+
32
+ export function formatWithDayjs(
33
+ value: DateInput,
34
+ outputFormat: string,
35
+ parseFormat?: string,
36
+ ): string {
37
+ const parsed =
38
+ parseFormat && typeof value === 'string'
39
+ ? dayjs(value, toFlexibleDayjsParsePattern(parseFormat))
40
+ : dayjs(value);
41
+ return parsed.format(outputFormat);
42
+ }
43
+
44
+ export function formatWithDateFns(
45
+ value: DateInput,
46
+ outputFormat: string,
47
+ parseFormat?: string,
48
+ ): string {
49
+ let date: Date;
50
+
51
+ if (value instanceof Date) {
52
+ date = value;
53
+ } else if (parseFormat) {
54
+ date = dateFnsParse(value, toDateFnsParsePattern(parseFormat), new Date());
55
+ } else {
56
+ date = new Date(value);
57
+ }
58
+
59
+ if (!isValid(date)) {
60
+ return 'Invalid Date';
61
+ }
62
+
63
+ return dateFnsFormat(date, toDateFnsFormatPattern(outputFormat));
64
+ }
65
+
66
+ export function formatDate(
67
+ value: DateInput,
68
+ outputFormat: string,
69
+ options: FormatDateOptions = {},
70
+ ): string {
71
+ const { parseFormat, useDateFns = false } = options;
72
+
73
+ return useDateFns
74
+ ? formatWithDateFns(value, outputFormat, parseFormat)
75
+ : formatWithDayjs(value, outputFormat, parseFormat);
76
+ }