wini-web-components 2.8.1 → 2.8.4

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 (65) hide show
  1. package/package.json +6 -2
  2. package/src/component/button/button.module.css +210 -0
  3. package/src/component/button/button.tsx +57 -0
  4. package/src/component/calendar/calendar.module.css +153 -0
  5. package/src/component/calendar/calendar.tsx +389 -0
  6. package/src/component/carousel/carousel.css +622 -0
  7. package/src/component/carousel/carousel.tsx +91 -0
  8. package/src/component/checkbox/checkbox.module.css +48 -0
  9. package/src/component/checkbox/checkbox.tsx +80 -0
  10. package/src/component/ck-editor/ck-editor.css +206 -0
  11. package/src/component/ck-editor/ckeditor.tsx +522 -0
  12. package/src/component/component-status.tsx +53 -0
  13. package/src/component/date-time-picker/date-time-picker.module.css +94 -0
  14. package/src/component/date-time-picker/date-time-picker.tsx +663 -0
  15. package/src/component/dialog/dialog.module.css +111 -0
  16. package/src/component/dialog/dialog.tsx +109 -0
  17. package/src/component/import-file/import-file.module.css +83 -0
  18. package/src/component/import-file/import-file.tsx +174 -0
  19. package/src/component/infinite-scroll/infinite-scroll.module.css +34 -0
  20. package/src/component/infinite-scroll/infinite-scroll.tsx +35 -0
  21. package/src/component/input-multi-select/input-multi-select.module.css +121 -0
  22. package/src/component/input-multi-select/input-multi-select.tsx +263 -0
  23. package/src/component/input-otp/input-otp.module.css +41 -0
  24. package/src/component/input-otp/input-otp.tsx +110 -0
  25. package/src/component/number-picker/number-picker.module.css +137 -0
  26. package/src/component/number-picker/number-picker.tsx +107 -0
  27. package/src/component/pagination/pagination.module.css +48 -0
  28. package/src/component/pagination/pagination.tsx +88 -0
  29. package/src/component/popup/popup.css +136 -0
  30. package/src/component/popup/popup.tsx +125 -0
  31. package/src/component/progress-bar/progress-bar.module.css +42 -0
  32. package/src/component/progress-bar/progress-bar.tsx +33 -0
  33. package/src/component/progress-circle/progress-circle.css +0 -0
  34. package/src/component/progress-circle/progress-circle.tsx +25 -0
  35. package/src/component/radio-button/radio-button.module.css +51 -0
  36. package/src/component/radio-button/radio-button.tsx +60 -0
  37. package/src/component/rating/rating.module.css +11 -0
  38. package/src/component/rating/rating.tsx +65 -0
  39. package/src/component/select1/select1.module.css +108 -0
  40. package/src/component/select1/select1.tsx +271 -0
  41. package/src/component/switch/switch.module.css +53 -0
  42. package/src/component/switch/switch.tsx +68 -0
  43. package/src/component/table/table.css +74 -0
  44. package/src/component/table/table.tsx +108 -0
  45. package/src/component/tag/tag.module.css +108 -0
  46. package/src/component/tag/tag.tsx +31 -0
  47. package/src/component/text/text.css +27 -0
  48. package/src/component/text/text.tsx +24 -0
  49. package/src/component/text-area/text-area.module.css +57 -0
  50. package/src/component/text-area/text-area.tsx +65 -0
  51. package/src/component/text-field/text-field.module.css +71 -0
  52. package/src/component/text-field/text-field.tsx +102 -0
  53. package/src/component/toast-noti/toast-noti.css +866 -0
  54. package/src/component/toast-noti/toast-noti.tsx +22 -0
  55. package/src/component/wini-icon/winicon.module.css +110 -0
  56. package/src/component/wini-icon/winicon.tsx +9424 -0
  57. package/src/form/login/view.module.css +80 -0
  58. package/src/form/login/view.tsx +138 -0
  59. package/src/global.d.ts +5 -0
  60. package/src/index.tsx +66 -0
  61. package/src/language/i18n.tsx +143 -0
  62. package/src/skin/layout.css +649 -0
  63. package/src/skin/root.css +294 -0
  64. package/src/skin/typography.css +314 -0
  65. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,663 @@
1
+ import styles from "./date-time-picker.module.css"
2
+ import { CSSProperties, forwardRef, ReactNode, useEffect, useMemo, useRef, useState } from "react"
3
+ import { useForm } from "react-hook-form"
4
+ import { useTranslation } from "react-i18next"
5
+ import { differenceInCalendarDays } from "date-fns"
6
+ import { closePopup, Popup, showPopup } from "../popup/popup"
7
+ import { Text } from "../text/text"
8
+ import { Winicon } from "../wini-icon/winicon"
9
+ import { TextField } from "../text-field/text-field"
10
+ import { Calendar } from "../calendar/calendar"
11
+ import { Button } from "../button/button"
12
+ import { Checkbox } from "../checkbox/checkbox"
13
+
14
+ const today = new Date()
15
+ const startDate = new Date(
16
+ today.getFullYear() - 100,
17
+ today.getMonth(),
18
+ today.getDate()
19
+ )
20
+ const endDate = new Date(
21
+ today.getFullYear() + 100,
22
+ today.getMonth(),
23
+ today.getDate()
24
+ )
25
+
26
+ const dateToString = (x: Date, y: string = "dd/mm/yyyy") => {
27
+ let splitDateTime: Array<string> = y.split(" ");
28
+ let dateFormat = splitDateTime[0]
29
+ let timeFormat = splitDateTime[1]
30
+ if (dateFormat.includes('hh')) {
31
+ dateFormat = splitDateTime[1]
32
+ timeFormat = splitDateTime[0]
33
+ }
34
+ let dateConvert: string = dateFormat.split(y.includes("/") ? "/" : "-").map(type => {
35
+ switch (type.toLowerCase()) {
36
+ case "dd":
37
+ return x.getDate() < 10 ? `0${x.getDate()}` : `${x.getDate()}`;
38
+ case "mm":
39
+ return (x.getMonth() + 1) < 10 ? `0${(x.getMonth() + 1)}` : `${(x.getMonth() + 1)}`;
40
+ case "yyyy":
41
+ return `${x.getFullYear()}`;
42
+ default:
43
+ return ''
44
+ }
45
+ }).join(y.includes("/") ? "/" : "-");
46
+ if (timeFormat) {
47
+ let timeConvert = timeFormat.split(":").map(type => {
48
+ switch (type) {
49
+ case "hh":
50
+ return x.getHours() < 10 ? `0${x.getHours()}` : `${x.getHours()}`;
51
+ case "mm":
52
+ return x.getMinutes() < 10 ? `0${x.getMinutes()}` : `${x.getMinutes()}`;
53
+ case "ss":
54
+ return x.getSeconds() < 10 ? `0${x.getSeconds()}` : `${x.getSeconds()}`;
55
+ default:
56
+ return ''
57
+ }
58
+ }).join(":")
59
+ return dateConvert + " " + timeConvert;
60
+ }
61
+ return dateConvert;
62
+ }
63
+
64
+ const stringToDate = (_date: string, _format: string = "dd/mm/yyyy", _delimiter: string = "/") => {
65
+ let dayformat: string = _format;
66
+ let hourformat: string = '';
67
+ let day: string = _date;
68
+ let hours: string = '';
69
+ let isHour: boolean = false;
70
+ if (_format.trim().indexOf(" ") > -1) {
71
+ dayformat = _format.trim().split(" ")[0];
72
+ hourformat = _format.trim().split(" ")[1];
73
+ day = _date.trim().split(" ")[0];
74
+ hours = _date.trim().split(" ")[1] ?? '00:00:00';
75
+ isHour = true;
76
+ }
77
+ let formatLowerCase: string = dayformat.toLowerCase();
78
+ let formatItems: Array<string> = formatLowerCase.split(_delimiter);
79
+ let dateItems: Array<string> = day.split(_delimiter);
80
+ let monthIndex: number = formatItems.indexOf("mm");
81
+ let dayIndex: number = formatItems.indexOf("dd");
82
+ let yearIndex: number = formatItems.indexOf("yyyy");
83
+ let hour: number = 0;
84
+ let min: number = 0;
85
+ let sec: number = 0;
86
+ if (isHour) {
87
+ let tmpHour: Array<string> = hourformat.split(":");
88
+ let hourindex: number = tmpHour.indexOf("HH");
89
+ if (hourindex < 0) {
90
+ hourindex = tmpHour.indexOf("hh");
91
+ }
92
+ let mmindex: number = tmpHour.indexOf("mm");
93
+ let ssindex: number = tmpHour.indexOf("ss");
94
+ let time: Array<string> = hours.split(":");
95
+ hour = parseInt(time[hourindex] ?? '0'); min = parseInt(time[mmindex] ?? '0'); sec = parseInt(time[ssindex] ?? '0');
96
+ }
97
+ let month: number = parseInt(dateItems[monthIndex]);
98
+ month -= 1;
99
+ var formatedDate = new Date(parseInt(dateItems[yearIndex]), month, parseInt(dateItems[dayIndex] ?? '0'), hour, min, sec);
100
+ return formatedDate;
101
+ }
102
+ const inRangeTime = (date: Date, startDate: Date, endDate: Date) => (differenceInCalendarDays(date, startDate) > -1 && differenceInCalendarDays(endDate, date) > -1)
103
+
104
+ interface ValueProps {
105
+ start?: Date,
106
+ end?: Date,
107
+ /** type: 1: daily, 2: weekly, 3: monthly */
108
+ repeatData?: { type: 1 | 2 | 3, value: Array<string | number> }
109
+ }
110
+
111
+ interface DateTimePickerProps {
112
+ id?: string,
113
+ value?: Date,
114
+ endValue?: Date,
115
+ min?: Date,
116
+ max?: Date,
117
+ pickOnly?: boolean,
118
+ helperText?: string,
119
+ helperTextColor?: string,
120
+ placeholder?: string,
121
+ className?: string,
122
+ style?: CSSProperties,
123
+ disabled?: boolean,
124
+ pickerType?: "auto" | "date" | "datetime" | "daterange" | "datetimerange",
125
+ enableRepeat?: boolean,
126
+ /** type: 1: daily, 2: weekly, 3: monthly */
127
+ repeatValue?: { type: 1 | 2 | 3, value: Array<"everyday" | "last" | number> },
128
+ prefix?: ReactNode,
129
+ onChange?: (ev?: Date | ValueProps) => void,
130
+ }
131
+
132
+ export function DateTimePicker(props: DateTimePickerProps) {
133
+ const popupRef = useRef<any>(null)
134
+ const inputRef = useRef<HTMLInputElement>(null)
135
+ const [value, setValue] = useState<Date | ValueProps>()
136
+ const txtValue = useMemo(() => {
137
+ if (!value) return <Text className={styles["value"]} style={{ color: "var(--neutral-text-subtitle-color)" }}>{props.placeholder ?? ""}</Text>
138
+ if (value instanceof Date) return <Text className={styles["value"]}>{dateToString(value, `dd/mm/yyyy${props.pickerType?.includes("time") ? " hh:mm" : ""}`)}</Text>
139
+ else return <>
140
+ <Text className={styles["value"]} style={{ flex: "none", width: "fit-content" }}>{dateToString(value.start ?? new Date(), `dd/mm/yyyy${(props.pickerType?.includes("time") || props.pickerType === "auto") ? " hh:mm" : ""}`)} - {dateToString(value.end ?? new Date(), `dd/mm/yyyy${(props.pickerType?.includes("time") || props.pickerType === "auto") ? " hh:mm" : ""}`)}</Text>
141
+ {value.repeatData && <Winicon src="outline/arrows/loop-2" size={"1.2rem"} />}
142
+ </>
143
+ }, [value])
144
+
145
+ useEffect(() => {
146
+ if (inputRef.current) {
147
+ if (value && value instanceof Date) inputRef.current.value = dateToString(value, `dd/mm/yyyy`)
148
+ else inputRef.current.value = ""
149
+ }
150
+ }, [value, inputRef.current])
151
+
152
+ useEffect(() => {
153
+ switch (props.pickerType) {
154
+ case "date":
155
+ case "datetime":
156
+ setValue(props.value)
157
+ break;
158
+ default:
159
+ setValue((!props.value || !props.endValue) ? undefined : { start: props.value, end: props.endValue, repeatData: props.pickerType === "auto" ? props.repeatValue : undefined })
160
+ break;
161
+ }
162
+ }, [props.value, props.endValue, props.repeatValue, props.pickerType])
163
+
164
+ const showCalendar = (rect: any) => {
165
+ showPopup({
166
+ ref: popupRef,
167
+ clickOverlayClosePopup: true,
168
+ content: <PopupDateTimePicker
169
+ ref={popupRef}
170
+ max={props.max}
171
+ min={props.min}
172
+ value={value instanceof Date ? value : value?.start}
173
+ endValue={value instanceof Date ? undefined : value?.end}
174
+ pickerType={props.pickerType}
175
+ enableRepeat={props.enableRepeat}
176
+ style={{ top: rect.bottom + 2, left: rect.left + 16 }}
177
+ onApply={(ev) => {
178
+ setValue(ev)
179
+ closePopup(popupRef)
180
+ if (props.onChange) props.onChange(ev)
181
+ if (inputRef.current) inputRef.current.focus()
182
+ }}
183
+ />
184
+ })
185
+ }
186
+
187
+ const returnUI = () => {
188
+ switch (props.pickerType) {
189
+ case "date":
190
+ return <div
191
+ id={props.id}
192
+ className={`row ${styles["date-time-picker"]} ${props.className ?? "body-3"} ${props.helperText?.length ? styles['helper-text'] : ""}`}
193
+ helper-text={props.helperText}
194
+ style={props.style ? { ...({ '--helper-text-color': props.helperTextColor ?? '#e14337' } as CSSProperties), ...props.style } : ({ '--helper-text-color': props.helperTextColor ?? '#e14337' } as CSSProperties)}
195
+ onClick={(ev: any) => {
196
+ const rect = ev.target.closest("div").getBoundingClientRect()
197
+ showCalendar(rect)
198
+ }}>
199
+ {props.prefix ?? <Winicon className={styles["prefix-icon"]} src="outline/user interface/calendar-date" size={"1.2rem"} />}
200
+ <input
201
+ className={styles["value"]}
202
+ ref={inputRef}
203
+ autoComplete='off'
204
+ disabled={props.disabled}
205
+ placeholder={props.placeholder}
206
+ readOnly={props.pickOnly}
207
+ onKeyDown={(ev: any) => {
208
+ switch (ev.key.toLowerCase()) {
209
+ case "enter":
210
+ ev.target.blur()
211
+ break;
212
+ default:
213
+ break;
214
+ }
215
+ }}
216
+ onBlur={props.pickOnly ? undefined : (ev) => {
217
+ const inputValue = ev.target.value.trim()
218
+ let dateValue: Date | undefined = undefined
219
+ if (inputValue.match(/[0-9]{1,2}(\/|-)[0-9]{1,2}(\/|-)[0-9]{4}/g)) {
220
+ dateValue = stringToDate(inputValue, 'dd/MM/yyyy', '/')
221
+ if (inRangeTime(dateValue, props.min ?? startDate, props.min ?? endDate)) {
222
+ } else if (differenceInCalendarDays(props.min ?? startDate, dateValue) > -1) {
223
+ dateValue = props.min ?? startDate
224
+ } else if (differenceInCalendarDays(dateValue, props.min ?? endDate) > -1) {
225
+ dateValue = props.max ?? endDate
226
+ } else {
227
+ dateValue = undefined
228
+ }
229
+ }
230
+ setValue(dateValue)
231
+ if (props.onChange) props.onChange(dateValue)
232
+ }}
233
+ />
234
+ </div>
235
+ default:
236
+ return <button id={props.id} type="button" disabled={props.disabled} className={`row ${styles["date-time-picker"]} ${props.className ?? "body-3"}`} style={props.style} onClick={(ev: any) => {
237
+ const rect = ev.target.closest("button").getBoundingClientRect()
238
+ showCalendar(rect)
239
+ }}>
240
+ {props.prefix ?? <Winicon className={styles["prefix-icon"]} src="outline/user interface/calendar-date" size={"1.2rem"} />}
241
+ {txtValue}
242
+ </button>
243
+ }
244
+ }
245
+
246
+ return <>
247
+ <Popup ref={popupRef} />
248
+ {returnUI()}
249
+ </>
250
+
251
+ }
252
+
253
+ interface PopupPickerProps {
254
+ value?: Date,
255
+ endValue?: Date,
256
+ min?: Date,
257
+ style?: CSSProperties,
258
+ max?: Date,
259
+ pickerType?: "auto" | "date" | "datetime" | "daterange" | "datetimerange",
260
+ repeatValue?: { type: 1 | 2 | 3, value: Array<"everyday" | "last" | number> },
261
+ onApply?: (ev: Date | ValueProps) => void,
262
+ enableRepeat?: boolean,
263
+ }
264
+
265
+ const PopupDateTimePicker = forwardRef(function PopupDateTimePicker({ value, style, endValue, repeatValue, onApply, pickerType = "auto", enableRepeat = false, min, max }: PopupPickerProps, ref: any) {
266
+ const methods = useForm({ shouldFocusError: false })
267
+ const [selectTime, setSelectTime] = useState(false)
268
+ const [isRepeat, setIsRepeat] = useState(false)
269
+ const today = new Date()
270
+ const [repeatData, setRepeatData] = useState<{ type: 1 | 2 | 3, value: Array<string | number> }>({ type: 1, value: ["everyday"] }) // 1: daily, 2: weekly, 3: monthly
271
+ const popupRef = useRef<any>(null)
272
+ const inputStartRef = useRef<TextField>(null)
273
+ const inputEndRef = useRef<TextField>(null)
274
+ const { t } = useTranslation()
275
+ const regexDate = /[0-9]{1,2}(\/|-)[0-9]{1,2}(\/|-)[0-9]{4}/g
276
+ const regexTime = /^(?:[01]\d|2[0-3]):[0-5]\d(?:[:][0-5]\d)?$/g
277
+
278
+ useEffect(() => {
279
+ if (repeatValue && enableRepeat) {
280
+ setIsRepeat(true)
281
+ setRepeatData(repeatValue)
282
+ } else setIsRepeat(false)
283
+ }, [repeatValue])
284
+
285
+ useEffect(() => {
286
+ if (selectTime && pickerType !== "auto" && !pickerType.includes("time")) {
287
+ setSelectTime(false)
288
+ methods.setValue('time-start', null)
289
+ methods.setValue('time-end', null)
290
+ } else if (!selectTime && pickerType.includes("time")) {
291
+ setSelectTime(true)
292
+ }
293
+ }, [pickerType])
294
+
295
+ const initStartValue = () => {
296
+ if (value) {
297
+ const initStart = new Date(value)
298
+ methods.setValue('date-start', initStart)
299
+ inputStartRef.current!.getInput()!.value = dateToString(initStart)
300
+ if (pickerType.includes("time") || initStart.getSeconds() === 1) {
301
+ setSelectTime(true)
302
+ methods.setValue('time-start', `${initStart.getHours() < 9 ? `0${initStart.getHours()}` : initStart.getHours()}:${initStart.getMinutes() < 9 ? `0${initStart.getMinutes()}` : initStart.getMinutes()}`)
303
+ }
304
+ } else inputStartRef.current!.getInput()!.value = ""
305
+ }
306
+
307
+ const initEndValue = () => {
308
+ if ((pickerType?.includes("range") || pickerType === "auto") && inputEndRef.current) {
309
+ if (endValue) {
310
+ const initEnd = new Date(endValue)
311
+ methods.setValue('date-end', initEnd)
312
+ inputEndRef.current.getInput()!.value = dateToString(initEnd)
313
+ if (pickerType.includes("time") || initEnd.getSeconds() === 59) methods.setValue('time-end', `${initEnd.getHours() < 9 ? `0${initEnd.getHours()}` : initEnd.getHours()}:${initEnd.getMinutes() < 9 ? `0${initEnd.getMinutes()}` : initEnd.getMinutes()}`)
314
+ } else inputEndRef.current.getInput()!.value = ""
315
+ }
316
+ }
317
+
318
+ useEffect(() => {
319
+ if (value && inputStartRef.current) initStartValue()
320
+ }, [value, inputStartRef])
321
+
322
+ useEffect(() => {
323
+ initEndValue()
324
+ }, [endValue, inputEndRef, pickerType])
325
+
326
+ return <div className="col" style={{ width: "31.2rem", ...style }}>
327
+ <Popup ref={popupRef} />
328
+ <Calendar
329
+ min={min}
330
+ max={max}
331
+ range={pickerType.includes("range") || pickerType === "auto"}
332
+ value={pickerType === "date" || pickerType === "datetime" ? methods.watch('date-start') : (methods.watch('date-start') && methods.watch('date-end') ? { sTime: methods.watch('date-start'), eTime: methods.watch('date-end') } : undefined)}
333
+ header={pickerType !== "date" && <div className='row' style={{ flexWrap: "wrap", gap: "0.8rem 1.2rem", padding: "1.6rem", borderBottom: "var(--neutral-main-border)" }}>
334
+ <TextField
335
+ ref={inputStartRef}
336
+ autoComplete="off"
337
+ className='col12 body-3'
338
+ style={{ "--gutter": "1.2rem", padding: "0.4rem 1.2rem" } as any}
339
+ placeholder={pickerType.includes("range") || pickerType === "auto" ? t("start-date") : "dd/mm/yyyy"}
340
+ onComplete={(ev: any) => ev.target.blur()}
341
+ onBlur={(ev) => {
342
+ const inputValue = ev.target.value
343
+ if (regexDate.test(inputValue)) {
344
+ const dateValue = stringToDate(inputValue, 'dd/mm/yyyy', '/')
345
+ if ((pickerType.includes("range") || pickerType === "auto") && differenceInCalendarDays(methods.getValues('date-end'), dateValue) < 0) {
346
+ methods.setValue('date-end', dateValue)
347
+ inputEndRef.current!.getInput()!.value = dateToString(dateValue)
348
+ }
349
+ methods.setValue('date-start', dateValue)
350
+ } else ev.target.value = methods.getValues('date-start') ? dateToString(methods.getValues('date-start')) : ""
351
+ }}
352
+ />
353
+ {(pickerType.includes("range") || pickerType === "auto") &&
354
+ <TextField
355
+ ref={inputEndRef}
356
+ autoComplete="off"
357
+ className='col12 body-3'
358
+ style={{ "--gutter": "1.2rem", padding: "0.4rem 1.2rem" } as any}
359
+ placeholder={t("end-date")}
360
+ onComplete={(ev: any) => ev.target.blur()}
361
+ onBlur={(ev) => {
362
+ const inputValue = ev.target.value
363
+ if (regexDate.test(inputValue)) {
364
+ const dateValue = stringToDate(inputValue, 'dd/mm/yyyy', '/')
365
+ if (differenceInCalendarDays(dateValue, methods.getValues('date-start')) < 0) {
366
+ methods.setValue('date-start', dateValue)
367
+ inputStartRef.current!.getInput()!.value = dateToString(dateValue)
368
+ }
369
+ methods.setValue('date-end', dateValue)
370
+ } else ev.target.value = methods.getValues('date-end') ? dateToString(methods.getValues('date-end')) : ""
371
+ }}
372
+ />}
373
+ {selectTime && <>
374
+ <TextField
375
+ autoComplete="off"
376
+ name='time-start'
377
+ style={{ "--gutter": "1.2rem", padding: "0.4rem 1.2rem" } as any}
378
+ onComplete={(ev: any) => { ev.target.blur() }}
379
+ register={methods.register("time-start", {
380
+ onChange: (ev) => ev.target.value = ev.target.value.trim(),
381
+ onBlur: (ev) => {
382
+ if (regexTime.test(ev.target.value)) {
383
+ methods.setValue('time-start', ev.target.value)
384
+ } else ev.target.value = ""
385
+ }
386
+ }) as any}
387
+ className='col12 body-3'
388
+ placeholder={"hh:mm"}
389
+ onFocus={(ev) => {
390
+ const rect = ev.target.closest("div")!.getBoundingClientRect()
391
+ showPopup({
392
+ ref: popupRef,
393
+ clickOverlayClosePopup: true,
394
+ content: <div className={`col ${styles['popup-actions']}`} style={{ maxHeight: "24rem", top: rect.bottom + 2, right: document.body.offsetWidth - rect.right, width: rect.width, overflow: "hidden auto", border: "var(--neutral-main-border)" }}>
395
+ {Array.from({ length: 48 }).map((_, i) => {
396
+ if (i % 2 === 0) var timeValue = `${(i / 2) < 9 ? `0${i / 2}` : (i / 2)}:00`
397
+ else timeValue = `${((i - 1) / 2) < 9 ? `0${(i - 1) / 2}` : ((i - 1) / 2)}:30`
398
+ return <button key={"time-" + i} type="button" className="row" onClick={() => {
399
+ methods.setValue("time-start", timeValue)
400
+ closePopup(popupRef)
401
+ }}>
402
+ <Text className="body-3">{timeValue}</Text>
403
+ </button>
404
+ })}
405
+ </div>
406
+ })
407
+ }}
408
+ />
409
+ {(pickerType.includes("range") || pickerType === "auto") &&
410
+ <TextField
411
+ autoComplete="off"
412
+ name='time-end'
413
+ style={{ "--gutter": "1.2rem", padding: "0.4rem 1.2rem" } as any}
414
+ onComplete={(ev: any) => { ev.target.blur() }}
415
+ register={methods.register("time-end", {
416
+ onChange: (ev) => ev.target.value = ev.target.value.trim(),
417
+ onBlur: (ev) => {
418
+ if (regexTime.test(ev.target.value)) {
419
+ methods.setValue('time-end', ev.target.value)
420
+ } else ev.target.value = ""
421
+ }
422
+ }) as any}
423
+ className='col12 body-3'
424
+ placeholder={"hh:mm"}
425
+ onFocus={(ev) => {
426
+ const rect = ev.target.closest("div")!.getBoundingClientRect()
427
+ showPopup({
428
+ ref: popupRef,
429
+ clickOverlayClosePopup: true,
430
+ content: <div className={`col ${styles['popup-actions']}`} style={{ maxHeight: "24rem", top: rect.bottom + 2, right: document.body.offsetWidth - rect.right, width: rect.width, overflow: "hidden auto", border: "var(--neutral-main-border)" }}>
431
+ {Array.from({ length: 48 }).map((_, i) => {
432
+ if (i % 2 === 0) var timeValue = `${(i / 2) < 9 ? `0${i / 2}` : (i / 2)}:00`
433
+ else timeValue = `${((i - 1) / 2) < 9 ? `0${(i - 1) / 2}` : ((i - 1) / 2)}:30`
434
+ return <button key={"time-" + i} type="button" className="row" onClick={() => {
435
+ methods.setValue("time-end", timeValue)
436
+ closePopup(popupRef)
437
+ }}>
438
+ <Text className="body-3">{timeValue}</Text>
439
+ </button>
440
+ })}
441
+ </div>
442
+ })
443
+ }}
444
+ />}
445
+ </>}
446
+ </div>}
447
+ footer={pickerType !== "date" && <>
448
+ {isRepeat && <div className='col' style={{ borderTop: "var(--neutral-main-border)" }}>
449
+ <div className='row' style={{ gap: 4, padding: "1.2rem 1.6rem" }}>
450
+ <Text className='heading-8' style={{ flex: 1 }}>Lặp lại</Text>
451
+ <Button
452
+ style={{ padding: 0 }}
453
+ label={(() => {
454
+ switch (repeatData.type) {
455
+ case 1:
456
+ return t("daily")
457
+ case 2:
458
+ return t("weekly")
459
+ case 3:
460
+ return t("monthly")
461
+ default:
462
+ return ""
463
+ }
464
+ })()}
465
+ suffix={<Winicon src='outline/arrows/down-arrow' size={"1.4rem"} style={{ padding: "0.2rem" }} />}
466
+ onClick={(ev: any) => {
467
+ const rect = ev.target.closest("button").getBoundingClientRect()
468
+ showPopup({
469
+ ref: popupRef,
470
+ clickOverlayClosePopup: true,
471
+ style: { position: "absolute", top: rect.bottom + 2, left: rect.x + 8 },
472
+ body: <div className="col popup-actions">
473
+ {Array.from({ length: 3 }).map((_, num) => {
474
+ let label = ""
475
+ switch (num) {
476
+ case 0:
477
+ label = t("daily")
478
+ break;
479
+ case 1:
480
+ label = t("weekly")
481
+ break;
482
+ case 2:
483
+ label = t("monthly")
484
+ break;
485
+ default:
486
+ break;
487
+ }
488
+ return <button key={"tStatus-" + num} type="button" className="row" onClick={() => {
489
+ let newValue: any = ["everyday"]
490
+ switch (num) {
491
+ case 0:
492
+ newValue = ["everyday"]
493
+ break;
494
+ case 1:
495
+ newValue = today.getDay()
496
+ break;
497
+ case 2:
498
+ newValue = today.getDate()
499
+ break;
500
+ default:
501
+ break;
502
+ }
503
+ setRepeatData({ type: (num + 1) as any, value: [newValue] })
504
+ closePopup(popupRef)
505
+ }}>
506
+ <Text className="button-text-3">{label}</Text>
507
+ </button>
508
+ })}
509
+ </div>
510
+ })
511
+ }}
512
+ />
513
+ </div>
514
+ {(() => {
515
+ switch (repeatData.type) {
516
+ case 2:
517
+ return <>
518
+ <Text className='heading-8' style={{ padding: "0 1.6rem" }}>{t("on") + " " + t("date").toLowerCase()}</Text>
519
+ <div className='row' style={{ justifyContent: "space-between", padding: "0.4rem 1.6rem" }}>
520
+ {Array.from({ length: 7 }).map((_, i) => {
521
+ switch (i) {
522
+ case 0:
523
+ var weekdayTitle = t("su")
524
+ break
525
+ case 1:
526
+ weekdayTitle = t("mo")
527
+ break
528
+ case 2:
529
+ weekdayTitle = t("tu")
530
+ break
531
+ case 3:
532
+ weekdayTitle = t("we")
533
+ break
534
+ case 4:
535
+ weekdayTitle = t("th")
536
+ break
537
+ case 5:
538
+ weekdayTitle = t("fr")
539
+ break
540
+ case 6:
541
+ weekdayTitle = t("sa")
542
+ break
543
+ default:
544
+ weekdayTitle = ''
545
+ break
546
+ }
547
+ return <div key={"weekday-" + i} className='col' style={{ gap: 4, alignItems: "center" }}>
548
+ <Checkbox size={"1.8rem"} value={repeatData.value.includes(i)} disabled={repeatData.value.includes(i) && repeatData.value.length === 1} onChange={(v) => {
549
+ if (v) setRepeatData({ type: 2, value: [...repeatData.value, i] })
550
+ else setRepeatData({ type: 2, value: repeatData.value.filter(id => id !== i) })
551
+ }} />
552
+ <Text className='placeholder-2'>{weekdayTitle}</Text>
553
+ </div>
554
+ })}
555
+ </div>
556
+ </>
557
+ case 3:
558
+ return <div className='row' style={{ justifyContent: "space-between", padding: "0.4rem 1.6rem", gap: "1.2rem" }}>
559
+ <Text className='heading-8' style={{ flex: 1 }}>{t("on") + " " + t("date").toLowerCase()}</Text>
560
+ <Button
561
+ style={{ padding: 0 }}
562
+ label={repeatData.value[0] === "last" ? t("Last") : `${repeatData.value[0]}`}
563
+ suffix={<Winicon src='outline/arrows/down-arrow' size={"1.4rem"} style={{ padding: "0.2rem" }} />}
564
+ onClick={(ev: any) => {
565
+ const rect = ev.target.closest("button").getBoundingClientRect()
566
+ showPopup({
567
+ ref: popupRef,
568
+ clickOverlayClosePopup: true,
569
+ style: { top: rect.bottom + 2, right: document.body.offsetWidth - rect.right, maxHeight: "30.4rem" },
570
+ body: <div className="col popup-actions" style={{ flex: 1, overflow: "hidden auto" }}>
571
+ {Array.from({ length: 29 }).map((_, num) => {
572
+ switch (num) {
573
+ case 28:
574
+ var label = t("Last")
575
+ break;
576
+ default:
577
+ label = `${num + 1}`
578
+ break;
579
+ }
580
+ return <button key={"date-" + num} type="button" className="row" onClick={() => {
581
+ setRepeatData({ type: 3, value: [num === 28 ? "last" : (num + 1)] })
582
+ closePopup(popupRef)
583
+ }}>
584
+ <Text className="button-text-3">{label}</Text>
585
+ </button>
586
+ })}
587
+ </div>
588
+ })
589
+ }}
590
+ />
591
+ </div>
592
+ default:
593
+ return null
594
+ }
595
+ })()}
596
+ </div>}
597
+ {onApply && <div className='row' style={{ gap: "0.8rem", padding: "1.2rem 1.6rem", borderTop: "var(--neutral-main-border)" }}>
598
+ {pickerType === "auto" && <div className='row' style={{ gap: 4 }}>
599
+ <Winicon
600
+ src='outline/user interface/time-alarm'
601
+ size={"1.6rem"}
602
+ style={{ padding: "0.7rem", borderRadius: "50%", backgroundColor: selectTime ? "var(--neutral-disable-background-color)" : undefined }}
603
+ onClick={() => { setSelectTime(!selectTime) }}
604
+ />
605
+ {(enableRepeat || pickerType === "auto") && <Winicon
606
+ src='outline/arrows/loop-2'
607
+ size={"1.6rem"}
608
+ style={{ padding: "0.7rem", borderRadius: "50%", backgroundColor: isRepeat ? "var(--neutral-disable-background-color)" : undefined }}
609
+ onClick={() => { setIsRepeat(!isRepeat) }}
610
+ />}
611
+ </div>}
612
+ <div style={{ flex: 1 }} />
613
+ <Button
614
+ label={t("reset")}
615
+ onClick={() => {
616
+ methods.setValue("date-start", null)
617
+ methods.setValue("date-end", null)
618
+ methods.setValue("time-start", null)
619
+ methods.setValue("time-end", null)
620
+ initStartValue()
621
+ initEndValue()
622
+ }}
623
+ />
624
+ <Button
625
+ label={t("apply")}
626
+ disabled={!methods.watch("date-start") || (!methods.watch("date-end") && (pickerType.includes("range") || pickerType === "auto"))}
627
+ className={`button-text-3 button-primary`}
628
+ onClick={() => {
629
+ let dateStartValue = methods.getValues("date-start")
630
+ let timeStartValue = selectTime ? (methods.getValues("time-start")?.length ? methods.getValues("time-start") : "00:00") : "00:00"
631
+ dateStartValue.setHours(parseInt(timeStartValue.split(':')[0]), parseInt(timeStartValue.split(':')[1]), selectTime ? 1 : 0, 0)
632
+ if (pickerType.includes("range") || pickerType === "auto") {
633
+ var dateEndValue = methods.getValues("date-end")
634
+ let timeEndValue = selectTime ? (methods.getValues("time-end")?.length ? methods.getValues("time-end") : "23:59") : "23:59"
635
+ dateEndValue.setHours(parseInt(timeEndValue.split(':')[0]), parseInt(timeEndValue.split(':')[1]), selectTime ? 59 : 0, 0)
636
+ }
637
+ onApply(!pickerType.includes("range") && pickerType !== "auto" ? dateStartValue : { start: dateStartValue, end: dateEndValue, repeatData: isRepeat ? repeatData : undefined })
638
+ closePopup(ref)
639
+ }}
640
+ />
641
+ </div>}
642
+ </>}
643
+ onSelect={(ev: any) => {
644
+ if (pickerType !== "date") {
645
+ if (ev instanceof Date) {
646
+ methods.setValue('date-start', ev)
647
+ if (inputStartRef.current) inputStartRef.current.getInput()!.value = dateToString(ev)
648
+ } else {
649
+ methods.setValue('date-start', ev.sTime)
650
+ if (inputStartRef.current) inputStartRef.current.getInput()!.value = dateToString(ev.sTime)
651
+ if (pickerType.includes("range") || pickerType === "auto") {
652
+ methods.setValue('date-end', ev.eTime)
653
+ if (inputEndRef.current) inputEndRef.current.getInput()!.value = dateToString(ev.eTime)
654
+ }
655
+ }
656
+ } else if (onApply) {
657
+ onApply(ev)
658
+ closePopup(ref)
659
+ }
660
+ }}
661
+ />
662
+ </div>
663
+ })