quasar-ui-sellmate-ui-kit 2.2.33 → 2.3.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.
Files changed (62) hide show
  1. package/README.md +142 -142
  2. package/dist/index.common.js +3 -3
  3. package/dist/index.css +2 -2
  4. package/dist/index.esm.js +3 -3
  5. package/dist/index.min.css +2 -2
  6. package/dist/index.rtl.css +2 -2
  7. package/dist/index.rtl.min.css +2 -2
  8. package/dist/index.umd.js +3558 -3550
  9. package/dist/index.umd.min.js +3 -3
  10. package/package.json +75 -75
  11. package/src/components/SBreadcrumbs.vue +55 -55
  12. package/src/components/SButton.vue +206 -206
  13. package/src/components/SButtonGroup.vue +41 -41
  14. package/src/components/SCaution.vue +102 -102
  15. package/src/components/SCheckbox.vue +123 -123
  16. package/src/components/SChip.vue +99 -99
  17. package/src/components/SDate.vue +717 -717
  18. package/src/components/SDateAutoRangePicker.vue +341 -341
  19. package/src/components/SDatePicker.vue +472 -472
  20. package/src/components/SDateRange.vue +470 -470
  21. package/src/components/SDateRangePicker.vue +660 -660
  22. package/src/components/SDateTimePicker.vue +349 -349
  23. package/src/components/SDialog.vue +250 -250
  24. package/src/components/SDropdown.vue +216 -216
  25. package/src/components/SEditor.vue +490 -490
  26. package/src/components/SFilePicker.vue +207 -207
  27. package/src/components/SHelp.vue +146 -146
  28. package/src/components/SInput.vue +343 -343
  29. package/src/components/SInputCounter.vue +46 -46
  30. package/src/components/SInputNumber.vue +179 -179
  31. package/src/components/SList.vue +29 -29
  32. package/src/components/SMarkupTable.vue +141 -141
  33. package/src/components/SPagination.vue +266 -266
  34. package/src/components/SRadio.vue +78 -78
  35. package/src/components/SRouteTab.vue +67 -67
  36. package/src/components/SSelect.vue +294 -294
  37. package/src/components/SSelectCheckbox.vue +222 -225
  38. package/src/components/SSelectCustom.vue +25 -17
  39. package/src/components/SSelectGroupCheckbox.vue +235 -235
  40. package/src/components/SSelectSearch.vue +261 -261
  41. package/src/components/SSelectSearchAutoComplete.vue +172 -172
  42. package/src/components/SSelectSearchCheckbox.vue +356 -356
  43. package/src/components/SStringToInput.vue +66 -66
  44. package/src/components/STab.vue +80 -80
  45. package/src/components/STableTree.vue +208 -208
  46. package/src/components/STimePicker.vue +159 -159
  47. package/src/components/SToggle.vue +68 -68
  48. package/src/components/STooltip.vue +209 -209
  49. package/src/components/SelelctItem.vue +21 -21
  50. package/src/components/TimePickerCard.vue +352 -352
  51. package/src/composables/date.js +11 -11
  52. package/src/composables/modelBinder.js +13 -13
  53. package/src/composables/table/use-navigator.js +110 -110
  54. package/src/composables/table/use-resizable.js +80 -80
  55. package/src/css/app.scss +90 -90
  56. package/src/css/extends.scss +154 -154
  57. package/src/css/quasar.variables.scss +189 -189
  58. package/src/index.common.js +1 -1
  59. package/src/index.esm.js +4 -4
  60. package/src/index.scss +9 -9
  61. package/src/index.umd.js +3 -3
  62. package/src/vue-plugin.js +92 -92
@@ -1,717 +1,717 @@
1
- <template>
2
- <q-input
3
- class="s-date q-pa-none bg-white"
4
- :class="{ 'has-label': insideLabel, range: isRange }"
5
- dense
6
- outlined
7
- readonly
8
- :disable="isDisable"
9
- ref="wrapperInputRef"
10
- no-error-icon
11
- >
12
- <template v-slot:before v-if="insideLabel">
13
- <div class="date-picker-label">
14
- {{ insideLabel }}
15
- </div>
16
- </template>
17
-
18
- <template #default>
19
- <div class="flex no-wrap items-center" style="flex-grow: 1; justify-content: center">
20
- <template v-if="isRange">
21
- <q-input
22
- class="inner-input"
23
- v-model="dateModel.from"
24
- dense
25
- borderless
26
- mask="####-##-##"
27
- autocomplete="off"
28
- @click="menuModel.from = true"
29
- @keyup.enter="settingFromDate()"
30
- >
31
- <template #prepend>
32
- <q-icon
33
- :name="dateRangeIcon"
34
- size="20px"
35
- color="Grey_Darken-1"
36
- class="cursor-pointer"
37
- @click="fromMenuRef[menuModel.from ? 'hide' : 'show']()"
38
- />
39
- </template>
40
-
41
- <template #default>
42
- <q-menu
43
- v-model="menuModel.from"
44
- ref="fromMenuRef"
45
- class="s-date-menu"
46
- no-focus
47
- no-refocus
48
- :offset="[0, 6]"
49
- no-parent-event
50
- @hide="settingFromDate()"
51
- >
52
- <div class="reset-btn-area flex justify-end">
53
- <q-btn
54
- class="reset-btn no-hover"
55
- flat
56
- :label="lang ? locale[lang].today : '오늘'"
57
- text-color="Grey_Darken-4"
58
- :ripple="false"
59
- @click="resetDate('from')"
60
- />
61
- </div>
62
- <q-date
63
- v-model="dateModel.from"
64
- minimal
65
- square
66
- noUnset
67
- flat
68
- color="positive"
69
- class="q-pa-none"
70
- mask="YYYY-MM-DD"
71
- :locale="lang ? locale[lang] : {}"
72
- :options="optionsStartFn"
73
- @update:modelValue="menuModel.from = false"
74
- >
75
- </q-date>
76
- </q-menu>
77
- </template>
78
- </q-input>
79
- <span class="q-mx-sm text-Grey_Darken-4">~</span>
80
- <q-input
81
- v-model="dateModel.to"
82
- dense
83
- borderless
84
- mask="####-##-##"
85
- autocomplete="off"
86
- class="inner-input"
87
- @click="menuModel.to = true"
88
- @keyup.enter="settingToDate()"
89
- >
90
- <template #prepend>
91
- <q-icon
92
- :name="dateRangeIcon"
93
- size="20px"
94
- color="Grey_Darken-1"
95
- class="cursor-pointer"
96
- @click="toMenuRef[menuModel.to ? 'hide' : 'show']()"
97
- />
98
- </template>
99
-
100
- <template #default>
101
- <q-menu
102
- v-model="menuModel.to"
103
- ref="toMenuRef"
104
- class="s-date-menu"
105
- no-focus
106
- no-refocus
107
- :offset="[0, 6]"
108
- no-parent-event
109
- @hide="settingToDate()"
110
- >
111
- <div class="reset-btn-area flex justify-end">
112
- <q-btn
113
- class="reset-btn no-hover"
114
- flat
115
- :label="lang ? locale[lang].today : '오늘'"
116
- text-color="Grey_Darken-4"
117
- :ripple="false"
118
- @click="resetDate('to')"
119
- />
120
- </div>
121
- <q-date
122
- v-model="dateModel.to"
123
- minimal
124
- square
125
- noUnset
126
- flat
127
- color="positive"
128
- class="q-pa-none"
129
- mask="YYYY-MM-DD"
130
- :locale="lang ? locale[lang] : {}"
131
- :options="optionsEndFn"
132
- @update:modelValue="menuModel.to = false"
133
- />
134
- </q-menu>
135
- </template>
136
- </q-input>
137
- </template>
138
- <template v-else>
139
- <q-input
140
- class="inner-input"
141
- v-model="dateModel"
142
- dense
143
- borderless
144
- mask="####-##-##"
145
- autocomplete="off"
146
- @click="menuModel.single = true"
147
- @keyup.enter="menuModel.single = false"
148
- >
149
- <template #prepend>
150
- <q-icon
151
- :name="dateRangeIcon"
152
- size="20px"
153
- color="Grey_Darken-1"
154
- class="cursor-pointer"
155
- @click="singleMenuRef[menuModel.single ? 'hide' : 'show']()"
156
- />
157
- </template>
158
-
159
- <template #default>
160
- <q-menu
161
- v-model="menuModel.single"
162
- ref="singleMenuRef"
163
- class="s-date-menu"
164
- no-focus
165
- no-refocus
166
- :offset="[0, 6]"
167
- no-parent-event
168
- @hide="triggerValidation"
169
- >
170
- <div class="reset-btn-area flex justify-end">
171
- <q-btn
172
- class="reset-btn no-hover"
173
- flat
174
- :label="lang ? locale[lang].today : '오늘'"
175
- text-color="Grey_Darken-4"
176
- :ripple="false"
177
- @click="resetDate()"
178
- />
179
- </div>
180
- <q-date
181
- v-model="dateModel"
182
- minimal
183
- square
184
- noUnset
185
- flat
186
- color="positive"
187
- class="q-pa-none"
188
- mask="YYYY-MM-DD"
189
- :locale="lang ? locale[lang] : {}"
190
- :options="optionsFn"
191
- @update:modelValue="menuModel.single = false"
192
- >
193
- </q-date>
194
- </q-menu>
195
- </template>
196
- </q-input>
197
- </template>
198
- </div>
199
- </template>
200
- <template v-if="useDelete" v-slot:append>
201
- <q-icon :name="closeIcon" size="16px" class="cursor-pointer" @click="deleteDate" />
202
- </template>
203
- </q-input>
204
- </template>
205
-
206
- <script>
207
- import { ref } from 'vue';
208
- import { QIcon, QInput, QDate, QMenu, QBtn, date } from 'quasar';
209
- import { dateRangeIcon, closeIcon } from '../assets/icons';
210
- import { useModelBinder } from '../composables/modelBinder';
211
-
212
- const locale = {
213
- ko: {
214
- today: '오늘',
215
- daysShort: ['일', '월', '화', '수', '목', '금', '토'],
216
- months: [
217
- '1월',
218
- '2월',
219
- '3월',
220
- '4월',
221
- '5월',
222
- '6월',
223
- '7월',
224
- '8월',
225
- '9월',
226
- '10월',
227
- '11월',
228
- '12월',
229
- ],
230
- monthsShort: [
231
- '1월',
232
- '2월',
233
- '3월',
234
- '4월',
235
- '5월',
236
- '6월',
237
- '7월',
238
- '8월',
239
- '9월',
240
- '10월',
241
- '11월',
242
- '12월',
243
- ],
244
- },
245
- ja: {
246
- today: '今日',
247
- daysShort: ['日', '月', '火', '水', '木', '金', '土'],
248
- months: [
249
- '1月',
250
- '2月',
251
- '3月',
252
- '4月',
253
- '5月',
254
- '6月',
255
- '7月',
256
- '8月',
257
- '9月',
258
- '10月',
259
- '11月',
260
- '12月',
261
- ],
262
- monthsShort: [
263
- '1月',
264
- '2月',
265
- '3月',
266
- '4月',
267
- '5月',
268
- '6月',
269
- '7月',
270
- '8月',
271
- '9月',
272
- '10月',
273
- '11月',
274
- '12月',
275
- ],
276
- },
277
- };
278
-
279
- export default {
280
- name: 'SDate',
281
- components: {
282
- QDate,
283
- QIcon,
284
- QInput,
285
- QMenu,
286
- QBtn,
287
- },
288
-
289
- props: {
290
- modelValue: {
291
- type: [String, Object],
292
- },
293
- isRange: {
294
- type: Boolean,
295
- default: false,
296
- },
297
- isDisable: {
298
- type: Boolean,
299
- default: false,
300
- },
301
- insideLabel: {
302
- type: String,
303
- },
304
- lang: {
305
- type: String,
306
- default: 'ko',
307
- },
308
- useDelete: {
309
- type: Boolean,
310
- default: false,
311
- },
312
- noLimit: {
313
- type: Boolean,
314
- default: false,
315
- },
316
- dateRangeLimit: { type: [Number, Object], default: 1 },
317
- minMaxDate: {
318
- type: Object,
319
- default: () => ({ minDate: '', maxDate: '' }),
320
- },
321
- },
322
-
323
- setup(props, { emit }) {
324
- const dateModel = useModelBinder(props);
325
- const fromMenuRef = ref(null);
326
- const toMenuRef = ref(null);
327
- const wrapperInputRef = ref(null);
328
- const menuModel = ref({
329
- single: false,
330
- from: false,
331
- to: false,
332
- });
333
-
334
- function resetDate(key) {
335
- const todayDate = date.formatDate(new Date(), 'YYYY-MM-DD');
336
-
337
- if (!props.isRange) {
338
- dateModel.value = todayDate;
339
- return;
340
- }
341
-
342
- const durationBeforeToday = date.formatDate(
343
- date.subtractFromDate(new Date(), { month: props.dateRangeLimit }),
344
- 'YYYY-MM-DD',
345
- );
346
-
347
- if (props.noLimit && !!props.dateRangeLimit) {
348
- if (typeof dateModel.value !== 'object' || !dateModel.value.from) {
349
- dateModel.value = { from: durationBeforeToday, to: todayDate };
350
- return;
351
- }
352
-
353
- if (key === 'from') {
354
- dateModel.value.from = todayDate;
355
- dateModel.value.to = todayDate; // from이 리셋되면 to도 오늘 날짜로 설정
356
- return;
357
- }
358
-
359
- const fromDate = new Date(dateModel.value.from);
360
- const toDate = new Date(todayDate);
361
- const monthDifference = toDate.getMonth()
362
- - fromDate.getMonth()
363
- + 12 * (toDate.getFullYear() - fromDate.getFullYear());
364
-
365
- if (monthDifference >= props.dateRangeLimit) {
366
- dateModel.value.from = date.formatDate(
367
- date.subtractFromDate(toDate, { month: props.dateRangeLimit }),
368
- 'YYYY-MM-DD',
369
- );
370
- }
371
- dateModel.value.to = todayDate;
372
- }
373
-
374
- dateModel.value[key] = todayDate;
375
- if (key === 'from') {
376
- dateModel.value.to = todayDate; // from이 리셋되면 to도 오늘 날짜로 설정
377
- }
378
-
379
- fromMenuRef.value.hide();
380
- toMenuRef.value.hide();
381
- }
382
-
383
- function optionsFn(day) {
384
- if (props.noLimit) return true;
385
-
386
- const minDate = props.minMaxDate.minDate
387
- && date.formatDate(new Date(props.minMaxDate.minDate), 'YYYY/MM/DD');
388
- const maxDate = props.minMaxDate.maxDate
389
- && date.formatDate(new Date(props.minMaxDate.maxDate), 'YYYY/MM/DD');
390
-
391
- if (minDate && maxDate) {
392
- return minDate <= day && day <= maxDate;
393
- }
394
-
395
- if (minDate) {
396
- return minDate <= day;
397
- }
398
-
399
- if (maxDate) {
400
- return day <= maxDate;
401
- }
402
-
403
- return true;
404
- }
405
-
406
- function optionsStartFn(day) {
407
- if (props.noLimit) {
408
- return true;
409
- }
410
-
411
- const dayTo = date.formatDate(dateModel.value.to, 'YYYY/MM/DD');
412
- let maxFromDate = dayTo
413
- ? date.formatDate(
414
- date.subtractFromDate(
415
- new Date(dateModel.value.to),
416
- typeof props.dateRangeLimit === 'number'
417
- ? { month: props.dateRangeLimit }
418
- : props.dateRangeLimit,
419
- ),
420
- 'YYYY/MM/DD',
421
- )
422
- : null;
423
-
424
- if (props.minMaxDate.minDate) {
425
- maxFromDate = maxFromDate
426
- ? date.formatDate(
427
- new Date(Math.max(new Date(maxFromDate), new Date(props.minMaxDate.minDate))),
428
- 'YYYY/MM/DD',
429
- )
430
- : date.formatDate(new Date(props.minMaxDate.minDate), 'YYYY/MM/DD');
431
- }
432
-
433
- if (maxFromDate && dayTo) {
434
- return maxFromDate <= day && day <= dayTo;
435
- }
436
-
437
- if (maxFromDate) {
438
- return maxFromDate <= day;
439
- }
440
-
441
- if (dayTo) {
442
- return day <= dayTo;
443
- }
444
-
445
- return true;
446
- }
447
-
448
- function optionsEndFn(day) {
449
- const dayFrom = date.formatDate(dateModel.value.from, 'YYYY/MM/DD');
450
-
451
- let maxToDate = dayFrom
452
- ? date.formatDate(
453
- date.addToDate(
454
- new Date(dateModel.value.from),
455
- typeof props.dateRangeLimit === 'number'
456
- ? { month: props.dateRangeLimit }
457
- : props.dateRangeLimit,
458
- ),
459
- 'YYYY/MM/DD',
460
- )
461
- : null;
462
-
463
- if (props.minMaxDate.maxDate) {
464
- maxToDate = maxToDate
465
- ? date.formatDate(
466
- new Date(Math.min(new Date(maxToDate), new Date(props.minMaxDate.maxDate))),
467
- 'YYYY/MM/DD',
468
- )
469
- : date.formatDate(new Date(props.minMaxDate.maxDate), 'YYYY/MM/DD');
470
- }
471
-
472
- if (dayFrom && maxToDate) {
473
- return dayFrom <= day && day <= maxToDate;
474
- }
475
-
476
- if (dayFrom) {
477
- return dayFrom <= day;
478
- }
479
-
480
- if (maxToDate) {
481
- return day <= maxToDate;
482
- }
483
-
484
- return true;
485
- }
486
-
487
- function triggerValidation() {
488
- if (wrapperInputRef.value) wrapperInputRef.value.validate();
489
- }
490
-
491
- function settingFromDate() {
492
- menuModel.value.from = false;
493
- triggerValidation();
494
-
495
- if (props.noLimit && !!props.dateRangeLimit) {
496
- const fromDate = new Date(dateModel.value.from);
497
- const monthsLater = new Date(fromDate).setMonth(
498
- fromDate.getMonth() + props.dateRangeLimit,
499
- );
500
- const maxToDate = date.formatDate(monthsLater, 'YYYY-MM-DD'); // 최대개월 후 또는 오늘 중 더 이른 날짜
501
- const toDate = new Date(dateModel.value.to);
502
- if (toDate.getTime() > monthsLater || fromDate > toDate) {
503
- // 선택된 'to' 날짜가 최대개월을 초과하는 경우
504
- dateModel.value.to = maxToDate;
505
- }
506
- }
507
- }
508
-
509
- function settingToDate() {
510
- menuModel.value.to = false;
511
- triggerValidation();
512
-
513
- // FIXME: 종료 일자는 보통 시작 일자에 의해 제한 되기 때문에
514
- // 종료 일자가 시작 일자를 제한하는 경우는 없을 것으로 보임, 추후 확인 후 삭제
515
- if (props.noLimit && !!props.dateRangeLimit) {
516
- const toDate = new Date(dateModel.value.to);
517
- const fromDate = new Date(dateModel.value.from);
518
- const monthsBefore = new Date(toDate).setMonth(toDate.getMonth() - props.dateRangeLimit);
519
- const minFromDate = date.formatDate(monthsBefore, 'YYYY-MM-DD'); // 'to' 날짜로부터 최대개월 이전
520
- if (fromDate.getTime() < monthsBefore) {
521
- // 선택된 'from' 날짜가 'to' 날짜로부터 최대개월을 미만인 경우
522
- dateModel.value.from = minFromDate;
523
- }
524
- }
525
- }
526
-
527
- function deleteDate() {
528
- if (!props.isRange) {
529
- dateModel.value = '';
530
- emit('update:modelValue', dateModel.value);
531
- return;
532
- }
533
-
534
- dateModel.value.from = '';
535
- dateModel.value.to = '';
536
- emit('update:modelValue', dateModel.value);
537
- }
538
-
539
- return {
540
- locale,
541
- menuModel,
542
- dateModel,
543
- singleMenuRef: ref(null),
544
- fromMenuRef,
545
- toMenuRef,
546
- wrapperInputRef,
547
- dateRangeIcon,
548
- closeIcon,
549
-
550
- resetDate,
551
- optionsFn,
552
- optionsStartFn,
553
- optionsEndFn,
554
- settingFromDate,
555
- settingToDate,
556
- deleteDate,
557
- triggerValidation,
558
- };
559
- },
560
- };
561
- </script>
562
-
563
- <style lang="scss">
564
- @import '../css/quasar.variables.scss';
565
-
566
- .s-date {
567
- width: fit-content;
568
- height: $default-height;
569
- border-radius: 4px;
570
-
571
- &.has-label {
572
- .q-field__inner {
573
- .q-field__control {
574
- border-radius: 0 2px 2px 0 !important;
575
- }
576
- }
577
-
578
- .q-field__before {
579
- height: 100%;
580
- padding: 4px 12px !important;
581
- border: 1px solid $Grey_Lighten-1;
582
- border-right: none;
583
- border-radius: 2px 0 0 2px;
584
- background: $Grey_Lighten-5;
585
-
586
- .date-picker-label {
587
- font-size: $default-font;
588
- font-weight: $default-font-weight;
589
- color: $Grey_Darken-4;
590
- }
591
- }
592
- }
593
-
594
- &.q-field--disabled {
595
- .q-field__before {
596
- border: 1px solid $Grey_Lighten-2;
597
- border-right: none;
598
- }
599
-
600
- .q-field__inner {
601
- .q-field__control {
602
- &:after {
603
- border: 1px solid $Grey_Lighten-2;
604
- }
605
- }
606
- }
607
- }
608
-
609
- &.q-field--error {
610
- .q-field__control {
611
- &::after {
612
- border-color: red !important;
613
- }
614
- }
615
-
616
- &.q-field--highlighted {
617
- .q-field__control {
618
- &:after {
619
- box-shadow: 0 0 2px red;
620
- }
621
- }
622
- }
623
- }
624
-
625
- .q-field__bottom {
626
- display: none;
627
- }
628
-
629
- .q-field__inner {
630
- font-size: 12px;
631
- text-align: center;
632
-
633
- .q-field__control {
634
- padding: 4px 16px 4px 8px !important;
635
- height: 100%;
636
-
637
- &:after {
638
- border: 1px solid $Grey_Lighten-1;
639
- box-shadow: none;
640
- }
641
-
642
- .q-field__prepend {
643
- height: 20px;
644
- }
645
-
646
- .q-field__append {
647
- height: 20px;
648
-
649
- .q-icon {
650
- margin-right: -8px;
651
- }
652
- }
653
-
654
- &-container {
655
- > input {
656
- display: none;
657
- }
658
-
659
- .inner-input {
660
- width: 100%;
661
-
662
- // flex-grow: 1;
663
- > .q-field__inner {
664
- > .q-field__control {
665
- width: 100%;
666
- min-width: 72px;
667
- height: 20px;
668
- padding: 0px !important;
669
-
670
- &::before,
671
- &::after {
672
- border: none;
673
- }
674
-
675
- .q-field__control-container {
676
- justify-content: center;
677
-
678
- input {
679
- width: 72px;
680
- display: block;
681
- text-align: center;
682
- }
683
- }
684
-
685
- &:after {
686
- box-shadow: none !important;
687
- }
688
- }
689
- }
690
- }
691
- }
692
- }
693
- }
694
- }
695
-
696
- .s-date-menu {
697
- .reset-btn-area {
698
- padding: 20px 24px 0px;
699
-
700
- .reset-btn {
701
- width: 28px;
702
- height: 22px;
703
- min-height: 22px;
704
- padding: 0;
705
- text-decoration: underline;
706
- font-weight: 400;
707
-
708
- &:hover {
709
- .q-btn__content {
710
- text-decoration: underline;
711
- color: $positive;
712
- }
713
- }
714
- }
715
- }
716
- }
717
- </style>
1
+ <template>
2
+ <q-input
3
+ class="s-date q-pa-none bg-white"
4
+ :class="{ 'has-label': insideLabel, range: isRange }"
5
+ dense
6
+ outlined
7
+ readonly
8
+ :disable="isDisable"
9
+ ref="wrapperInputRef"
10
+ no-error-icon
11
+ >
12
+ <template v-slot:before v-if="insideLabel">
13
+ <div class="date-picker-label">
14
+ {{ insideLabel }}
15
+ </div>
16
+ </template>
17
+
18
+ <template #default>
19
+ <div class="flex no-wrap items-center" style="flex-grow: 1; justify-content: center">
20
+ <template v-if="isRange">
21
+ <q-input
22
+ class="inner-input"
23
+ v-model="dateModel.from"
24
+ dense
25
+ borderless
26
+ mask="####-##-##"
27
+ autocomplete="off"
28
+ @click="menuModel.from = true"
29
+ @keyup.enter="settingFromDate()"
30
+ >
31
+ <template #prepend>
32
+ <q-icon
33
+ :name="dateRangeIcon"
34
+ size="20px"
35
+ color="Grey_Darken-1"
36
+ class="cursor-pointer"
37
+ @click="fromMenuRef[menuModel.from ? 'hide' : 'show']()"
38
+ />
39
+ </template>
40
+
41
+ <template #default>
42
+ <q-menu
43
+ v-model="menuModel.from"
44
+ ref="fromMenuRef"
45
+ class="s-date-menu"
46
+ no-focus
47
+ no-refocus
48
+ :offset="[0, 6]"
49
+ no-parent-event
50
+ @hide="settingFromDate()"
51
+ >
52
+ <div class="reset-btn-area flex justify-end">
53
+ <q-btn
54
+ class="reset-btn no-hover"
55
+ flat
56
+ :label="lang ? locale[lang].today : '오늘'"
57
+ text-color="Grey_Darken-4"
58
+ :ripple="false"
59
+ @click="resetDate('from')"
60
+ />
61
+ </div>
62
+ <q-date
63
+ v-model="dateModel.from"
64
+ minimal
65
+ square
66
+ noUnset
67
+ flat
68
+ color="positive"
69
+ class="q-pa-none"
70
+ mask="YYYY-MM-DD"
71
+ :locale="lang ? locale[lang] : {}"
72
+ :options="optionsStartFn"
73
+ @update:modelValue="menuModel.from = false"
74
+ >
75
+ </q-date>
76
+ </q-menu>
77
+ </template>
78
+ </q-input>
79
+ <span class="q-mx-sm text-Grey_Darken-4">~</span>
80
+ <q-input
81
+ v-model="dateModel.to"
82
+ dense
83
+ borderless
84
+ mask="####-##-##"
85
+ autocomplete="off"
86
+ class="inner-input"
87
+ @click="menuModel.to = true"
88
+ @keyup.enter="settingToDate()"
89
+ >
90
+ <template #prepend>
91
+ <q-icon
92
+ :name="dateRangeIcon"
93
+ size="20px"
94
+ color="Grey_Darken-1"
95
+ class="cursor-pointer"
96
+ @click="toMenuRef[menuModel.to ? 'hide' : 'show']()"
97
+ />
98
+ </template>
99
+
100
+ <template #default>
101
+ <q-menu
102
+ v-model="menuModel.to"
103
+ ref="toMenuRef"
104
+ class="s-date-menu"
105
+ no-focus
106
+ no-refocus
107
+ :offset="[0, 6]"
108
+ no-parent-event
109
+ @hide="settingToDate()"
110
+ >
111
+ <div class="reset-btn-area flex justify-end">
112
+ <q-btn
113
+ class="reset-btn no-hover"
114
+ flat
115
+ :label="lang ? locale[lang].today : '오늘'"
116
+ text-color="Grey_Darken-4"
117
+ :ripple="false"
118
+ @click="resetDate('to')"
119
+ />
120
+ </div>
121
+ <q-date
122
+ v-model="dateModel.to"
123
+ minimal
124
+ square
125
+ noUnset
126
+ flat
127
+ color="positive"
128
+ class="q-pa-none"
129
+ mask="YYYY-MM-DD"
130
+ :locale="lang ? locale[lang] : {}"
131
+ :options="optionsEndFn"
132
+ @update:modelValue="menuModel.to = false"
133
+ />
134
+ </q-menu>
135
+ </template>
136
+ </q-input>
137
+ </template>
138
+ <template v-else>
139
+ <q-input
140
+ class="inner-input"
141
+ v-model="dateModel"
142
+ dense
143
+ borderless
144
+ mask="####-##-##"
145
+ autocomplete="off"
146
+ @click="menuModel.single = true"
147
+ @keyup.enter="menuModel.single = false"
148
+ >
149
+ <template #prepend>
150
+ <q-icon
151
+ :name="dateRangeIcon"
152
+ size="20px"
153
+ color="Grey_Darken-1"
154
+ class="cursor-pointer"
155
+ @click="singleMenuRef[menuModel.single ? 'hide' : 'show']()"
156
+ />
157
+ </template>
158
+
159
+ <template #default>
160
+ <q-menu
161
+ v-model="menuModel.single"
162
+ ref="singleMenuRef"
163
+ class="s-date-menu"
164
+ no-focus
165
+ no-refocus
166
+ :offset="[0, 6]"
167
+ no-parent-event
168
+ @hide="triggerValidation"
169
+ >
170
+ <div class="reset-btn-area flex justify-end">
171
+ <q-btn
172
+ class="reset-btn no-hover"
173
+ flat
174
+ :label="lang ? locale[lang].today : '오늘'"
175
+ text-color="Grey_Darken-4"
176
+ :ripple="false"
177
+ @click="resetDate()"
178
+ />
179
+ </div>
180
+ <q-date
181
+ v-model="dateModel"
182
+ minimal
183
+ square
184
+ noUnset
185
+ flat
186
+ color="positive"
187
+ class="q-pa-none"
188
+ mask="YYYY-MM-DD"
189
+ :locale="lang ? locale[lang] : {}"
190
+ :options="optionsFn"
191
+ @update:modelValue="menuModel.single = false"
192
+ >
193
+ </q-date>
194
+ </q-menu>
195
+ </template>
196
+ </q-input>
197
+ </template>
198
+ </div>
199
+ </template>
200
+ <template v-if="useDelete" v-slot:append>
201
+ <q-icon :name="closeIcon" size="16px" class="cursor-pointer" @click="deleteDate" />
202
+ </template>
203
+ </q-input>
204
+ </template>
205
+
206
+ <script>
207
+ import { ref } from 'vue';
208
+ import { QIcon, QInput, QDate, QMenu, QBtn, date } from 'quasar';
209
+ import { dateRangeIcon, closeIcon } from '../assets/icons';
210
+ import { useModelBinder } from '../composables/modelBinder';
211
+
212
+ const locale = {
213
+ ko: {
214
+ today: '오늘',
215
+ daysShort: ['일', '월', '화', '수', '목', '금', '토'],
216
+ months: [
217
+ '1월',
218
+ '2월',
219
+ '3월',
220
+ '4월',
221
+ '5월',
222
+ '6월',
223
+ '7월',
224
+ '8월',
225
+ '9월',
226
+ '10월',
227
+ '11월',
228
+ '12월',
229
+ ],
230
+ monthsShort: [
231
+ '1월',
232
+ '2월',
233
+ '3월',
234
+ '4월',
235
+ '5월',
236
+ '6월',
237
+ '7월',
238
+ '8월',
239
+ '9월',
240
+ '10월',
241
+ '11월',
242
+ '12월',
243
+ ],
244
+ },
245
+ ja: {
246
+ today: '今日',
247
+ daysShort: ['日', '月', '火', '水', '木', '金', '土'],
248
+ months: [
249
+ '1月',
250
+ '2月',
251
+ '3月',
252
+ '4月',
253
+ '5月',
254
+ '6月',
255
+ '7月',
256
+ '8月',
257
+ '9月',
258
+ '10月',
259
+ '11月',
260
+ '12月',
261
+ ],
262
+ monthsShort: [
263
+ '1月',
264
+ '2月',
265
+ '3月',
266
+ '4月',
267
+ '5月',
268
+ '6月',
269
+ '7月',
270
+ '8月',
271
+ '9月',
272
+ '10月',
273
+ '11月',
274
+ '12月',
275
+ ],
276
+ },
277
+ };
278
+
279
+ export default {
280
+ name: 'SDate',
281
+ components: {
282
+ QDate,
283
+ QIcon,
284
+ QInput,
285
+ QMenu,
286
+ QBtn,
287
+ },
288
+
289
+ props: {
290
+ modelValue: {
291
+ type: [String, Object],
292
+ },
293
+ isRange: {
294
+ type: Boolean,
295
+ default: false,
296
+ },
297
+ isDisable: {
298
+ type: Boolean,
299
+ default: false,
300
+ },
301
+ insideLabel: {
302
+ type: String,
303
+ },
304
+ lang: {
305
+ type: String,
306
+ default: 'ko',
307
+ },
308
+ useDelete: {
309
+ type: Boolean,
310
+ default: false,
311
+ },
312
+ noLimit: {
313
+ type: Boolean,
314
+ default: false,
315
+ },
316
+ dateRangeLimit: { type: [Number, Object], default: 1 },
317
+ minMaxDate: {
318
+ type: Object,
319
+ default: () => ({ minDate: '', maxDate: '' }),
320
+ },
321
+ },
322
+
323
+ setup(props, { emit }) {
324
+ const dateModel = useModelBinder(props);
325
+ const fromMenuRef = ref(null);
326
+ const toMenuRef = ref(null);
327
+ const wrapperInputRef = ref(null);
328
+ const menuModel = ref({
329
+ single: false,
330
+ from: false,
331
+ to: false,
332
+ });
333
+
334
+ function resetDate(key) {
335
+ const todayDate = date.formatDate(new Date(), 'YYYY-MM-DD');
336
+
337
+ if (!props.isRange) {
338
+ dateModel.value = todayDate;
339
+ return;
340
+ }
341
+
342
+ const durationBeforeToday = date.formatDate(
343
+ date.subtractFromDate(new Date(), { month: props.dateRangeLimit }),
344
+ 'YYYY-MM-DD',
345
+ );
346
+
347
+ if (props.noLimit && !!props.dateRangeLimit) {
348
+ if (typeof dateModel.value !== 'object' || !dateModel.value.from) {
349
+ dateModel.value = { from: durationBeforeToday, to: todayDate };
350
+ return;
351
+ }
352
+
353
+ if (key === 'from') {
354
+ dateModel.value.from = todayDate;
355
+ dateModel.value.to = todayDate; // from이 리셋되면 to도 오늘 날짜로 설정
356
+ return;
357
+ }
358
+
359
+ const fromDate = new Date(dateModel.value.from);
360
+ const toDate = new Date(todayDate);
361
+ const monthDifference = toDate.getMonth()
362
+ - fromDate.getMonth()
363
+ + 12 * (toDate.getFullYear() - fromDate.getFullYear());
364
+
365
+ if (monthDifference >= props.dateRangeLimit) {
366
+ dateModel.value.from = date.formatDate(
367
+ date.subtractFromDate(toDate, { month: props.dateRangeLimit }),
368
+ 'YYYY-MM-DD',
369
+ );
370
+ }
371
+ dateModel.value.to = todayDate;
372
+ }
373
+
374
+ dateModel.value[key] = todayDate;
375
+ if (key === 'from') {
376
+ dateModel.value.to = todayDate; // from이 리셋되면 to도 오늘 날짜로 설정
377
+ }
378
+
379
+ fromMenuRef.value.hide();
380
+ toMenuRef.value.hide();
381
+ }
382
+
383
+ function optionsFn(day) {
384
+ if (props.noLimit) return true;
385
+
386
+ const minDate = props.minMaxDate.minDate
387
+ && date.formatDate(new Date(props.minMaxDate.minDate), 'YYYY/MM/DD');
388
+ const maxDate = props.minMaxDate.maxDate
389
+ && date.formatDate(new Date(props.minMaxDate.maxDate), 'YYYY/MM/DD');
390
+
391
+ if (minDate && maxDate) {
392
+ return minDate <= day && day <= maxDate;
393
+ }
394
+
395
+ if (minDate) {
396
+ return minDate <= day;
397
+ }
398
+
399
+ if (maxDate) {
400
+ return day <= maxDate;
401
+ }
402
+
403
+ return true;
404
+ }
405
+
406
+ function optionsStartFn(day) {
407
+ if (props.noLimit) {
408
+ return true;
409
+ }
410
+
411
+ const dayTo = date.formatDate(dateModel.value.to, 'YYYY/MM/DD');
412
+ let maxFromDate = dayTo
413
+ ? date.formatDate(
414
+ date.subtractFromDate(
415
+ new Date(dateModel.value.to),
416
+ typeof props.dateRangeLimit === 'number'
417
+ ? { month: props.dateRangeLimit }
418
+ : props.dateRangeLimit,
419
+ ),
420
+ 'YYYY/MM/DD',
421
+ )
422
+ : null;
423
+
424
+ if (props.minMaxDate.minDate) {
425
+ maxFromDate = maxFromDate
426
+ ? date.formatDate(
427
+ new Date(Math.max(new Date(maxFromDate), new Date(props.minMaxDate.minDate))),
428
+ 'YYYY/MM/DD',
429
+ )
430
+ : date.formatDate(new Date(props.minMaxDate.minDate), 'YYYY/MM/DD');
431
+ }
432
+
433
+ if (maxFromDate && dayTo) {
434
+ return maxFromDate <= day && day <= dayTo;
435
+ }
436
+
437
+ if (maxFromDate) {
438
+ return maxFromDate <= day;
439
+ }
440
+
441
+ if (dayTo) {
442
+ return day <= dayTo;
443
+ }
444
+
445
+ return true;
446
+ }
447
+
448
+ function optionsEndFn(day) {
449
+ const dayFrom = date.formatDate(dateModel.value.from, 'YYYY/MM/DD');
450
+
451
+ let maxToDate = dayFrom
452
+ ? date.formatDate(
453
+ date.addToDate(
454
+ new Date(dateModel.value.from),
455
+ typeof props.dateRangeLimit === 'number'
456
+ ? { month: props.dateRangeLimit }
457
+ : props.dateRangeLimit,
458
+ ),
459
+ 'YYYY/MM/DD',
460
+ )
461
+ : null;
462
+
463
+ if (props.minMaxDate.maxDate) {
464
+ maxToDate = maxToDate
465
+ ? date.formatDate(
466
+ new Date(Math.min(new Date(maxToDate), new Date(props.minMaxDate.maxDate))),
467
+ 'YYYY/MM/DD',
468
+ )
469
+ : date.formatDate(new Date(props.minMaxDate.maxDate), 'YYYY/MM/DD');
470
+ }
471
+
472
+ if (dayFrom && maxToDate) {
473
+ return dayFrom <= day && day <= maxToDate;
474
+ }
475
+
476
+ if (dayFrom) {
477
+ return dayFrom <= day;
478
+ }
479
+
480
+ if (maxToDate) {
481
+ return day <= maxToDate;
482
+ }
483
+
484
+ return true;
485
+ }
486
+
487
+ function triggerValidation() {
488
+ if (wrapperInputRef.value) wrapperInputRef.value.validate();
489
+ }
490
+
491
+ function settingFromDate() {
492
+ menuModel.value.from = false;
493
+ triggerValidation();
494
+
495
+ if (props.noLimit && !!props.dateRangeLimit) {
496
+ const fromDate = new Date(dateModel.value.from);
497
+ const monthsLater = new Date(fromDate).setMonth(
498
+ fromDate.getMonth() + props.dateRangeLimit,
499
+ );
500
+ const maxToDate = date.formatDate(monthsLater, 'YYYY-MM-DD'); // 최대개월 후 또는 오늘 중 더 이른 날짜
501
+ const toDate = new Date(dateModel.value.to);
502
+ if (toDate.getTime() > monthsLater || fromDate > toDate) {
503
+ // 선택된 'to' 날짜가 최대개월을 초과하는 경우
504
+ dateModel.value.to = maxToDate;
505
+ }
506
+ }
507
+ }
508
+
509
+ function settingToDate() {
510
+ menuModel.value.to = false;
511
+ triggerValidation();
512
+
513
+ // FIXME: 종료 일자는 보통 시작 일자에 의해 제한 되기 때문에
514
+ // 종료 일자가 시작 일자를 제한하는 경우는 없을 것으로 보임, 추후 확인 후 삭제
515
+ if (props.noLimit && !!props.dateRangeLimit) {
516
+ const toDate = new Date(dateModel.value.to);
517
+ const fromDate = new Date(dateModel.value.from);
518
+ const monthsBefore = new Date(toDate).setMonth(toDate.getMonth() - props.dateRangeLimit);
519
+ const minFromDate = date.formatDate(monthsBefore, 'YYYY-MM-DD'); // 'to' 날짜로부터 최대개월 이전
520
+ if (fromDate.getTime() < monthsBefore) {
521
+ // 선택된 'from' 날짜가 'to' 날짜로부터 최대개월을 미만인 경우
522
+ dateModel.value.from = minFromDate;
523
+ }
524
+ }
525
+ }
526
+
527
+ function deleteDate() {
528
+ if (!props.isRange) {
529
+ dateModel.value = '';
530
+ emit('update:modelValue', dateModel.value);
531
+ return;
532
+ }
533
+
534
+ dateModel.value.from = '';
535
+ dateModel.value.to = '';
536
+ emit('update:modelValue', dateModel.value);
537
+ }
538
+
539
+ return {
540
+ locale,
541
+ menuModel,
542
+ dateModel,
543
+ singleMenuRef: ref(null),
544
+ fromMenuRef,
545
+ toMenuRef,
546
+ wrapperInputRef,
547
+ dateRangeIcon,
548
+ closeIcon,
549
+
550
+ resetDate,
551
+ optionsFn,
552
+ optionsStartFn,
553
+ optionsEndFn,
554
+ settingFromDate,
555
+ settingToDate,
556
+ deleteDate,
557
+ triggerValidation,
558
+ };
559
+ },
560
+ };
561
+ </script>
562
+
563
+ <style lang="scss">
564
+ @import '../css/quasar.variables.scss';
565
+
566
+ .s-date {
567
+ width: fit-content;
568
+ height: $default-height;
569
+ border-radius: 4px;
570
+
571
+ &.has-label {
572
+ .q-field__inner {
573
+ .q-field__control {
574
+ border-radius: 0 2px 2px 0 !important;
575
+ }
576
+ }
577
+
578
+ .q-field__before {
579
+ height: 100%;
580
+ padding: 4px 12px !important;
581
+ border: 1px solid $Grey_Lighten-1;
582
+ border-right: none;
583
+ border-radius: 2px 0 0 2px;
584
+ background: $Grey_Lighten-5;
585
+
586
+ .date-picker-label {
587
+ font-size: $default-font;
588
+ font-weight: $default-font-weight;
589
+ color: $Grey_Darken-4;
590
+ }
591
+ }
592
+ }
593
+
594
+ &.q-field--disabled {
595
+ .q-field__before {
596
+ border: 1px solid $Grey_Lighten-2;
597
+ border-right: none;
598
+ }
599
+
600
+ .q-field__inner {
601
+ .q-field__control {
602
+ &:after {
603
+ border: 1px solid $Grey_Lighten-2;
604
+ }
605
+ }
606
+ }
607
+ }
608
+
609
+ &.q-field--error {
610
+ .q-field__control {
611
+ &::after {
612
+ border-color: red !important;
613
+ }
614
+ }
615
+
616
+ &.q-field--highlighted {
617
+ .q-field__control {
618
+ &:after {
619
+ box-shadow: 0 0 2px red;
620
+ }
621
+ }
622
+ }
623
+ }
624
+
625
+ .q-field__bottom {
626
+ display: none;
627
+ }
628
+
629
+ .q-field__inner {
630
+ font-size: 12px;
631
+ text-align: center;
632
+
633
+ .q-field__control {
634
+ padding: 4px 16px 4px 8px !important;
635
+ height: 100%;
636
+
637
+ &:after {
638
+ border: 1px solid $Grey_Lighten-1;
639
+ box-shadow: none;
640
+ }
641
+
642
+ .q-field__prepend {
643
+ height: 20px;
644
+ }
645
+
646
+ .q-field__append {
647
+ height: 20px;
648
+
649
+ .q-icon {
650
+ margin-right: -8px;
651
+ }
652
+ }
653
+
654
+ &-container {
655
+ > input {
656
+ display: none;
657
+ }
658
+
659
+ .inner-input {
660
+ width: 100%;
661
+
662
+ // flex-grow: 1;
663
+ > .q-field__inner {
664
+ > .q-field__control {
665
+ width: 100%;
666
+ min-width: 72px;
667
+ height: 20px;
668
+ padding: 0px !important;
669
+
670
+ &::before,
671
+ &::after {
672
+ border: none;
673
+ }
674
+
675
+ .q-field__control-container {
676
+ justify-content: center;
677
+
678
+ input {
679
+ width: 72px;
680
+ display: block;
681
+ text-align: center;
682
+ }
683
+ }
684
+
685
+ &:after {
686
+ box-shadow: none !important;
687
+ }
688
+ }
689
+ }
690
+ }
691
+ }
692
+ }
693
+ }
694
+ }
695
+
696
+ .s-date-menu {
697
+ .reset-btn-area {
698
+ padding: 20px 24px 0px;
699
+
700
+ .reset-btn {
701
+ width: 28px;
702
+ height: 22px;
703
+ min-height: 22px;
704
+ padding: 0;
705
+ text-decoration: underline;
706
+ font-weight: 400;
707
+
708
+ &:hover {
709
+ .q-btn__content {
710
+ text-decoration: underline;
711
+ color: $positive;
712
+ }
713
+ }
714
+ }
715
+ }
716
+ }
717
+ </style>