@skalfa/skalfa-component 1.0.53 → 1.0.54
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/dist/input/InputDate.component.d.ts +1 -0
- package/dist/input/InputDate.component.js +47 -29
- package/dist/input/InputDatetime.component.js +2 -2
- package/dist/input/InputTime.component.d.ts +5 -3
- package/dist/input/InputTime.component.js +109 -28
- package/dist/input/Select.component.d.ts +7 -2
- package/dist/input/Select.component.js +91 -38
- package/package.json +1 -1
- package/src/input/InputDate.component.tsx +95 -55
- package/src/input/InputDatetime.component.tsx +2 -0
- package/src/input/InputTime.component.tsx +218 -58
- package/src/input/Select.component.tsx +111 -53
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
|
-
import { FC, InputHTMLAttributes, ReactNode, useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { FC, InputHTMLAttributes, ReactNode, useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { Icon } from "@skalfa/skalfa-icon";
|
|
5
5
|
import { cn, pcn, useInputHandler, useInputRandomId, useResponsive, useValidation, validation, ValidationRules } from "@utils";
|
|
6
6
|
import { BottomSheetComponent } from "../modal/BottomSheet.component";
|
|
@@ -12,21 +12,22 @@ import { OutsideClickComponent } from "../wrap/OutsideClick.component";
|
|
|
12
12
|
type CT = "label" | "tip" | "error" | "input"| "icon";
|
|
13
13
|
|
|
14
14
|
export interface InputTimeProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "onChange"> {
|
|
15
|
-
label
|
|
16
|
-
tip
|
|
17
|
-
leftIcon
|
|
18
|
-
rightIcon
|
|
15
|
+
label ?: string;
|
|
16
|
+
tip ?: string | ReactNode;
|
|
17
|
+
leftIcon ?: any;
|
|
18
|
+
rightIcon ?: any;
|
|
19
19
|
|
|
20
20
|
value ?: string;
|
|
21
21
|
invalid ?: string;
|
|
22
22
|
validations ?: ValidationRules;
|
|
23
|
+
showSeconds ?: boolean;
|
|
23
24
|
|
|
24
|
-
onChange
|
|
25
|
+
onChange ?: (value: string) => any;
|
|
25
26
|
register ?: (name: string, validations?: ValidationRules) => void;
|
|
26
27
|
unregister ?: (name: string) => void;
|
|
27
28
|
|
|
28
29
|
/** Use custom class with: "label::", "tip::", "error::", "icon::". */
|
|
29
|
-
className
|
|
30
|
+
className ?: string;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
|
|
@@ -40,6 +41,7 @@ export function InputTimeComponent({
|
|
|
40
41
|
value,
|
|
41
42
|
invalid,
|
|
42
43
|
validations,
|
|
44
|
+
showSeconds,
|
|
43
45
|
|
|
44
46
|
register,
|
|
45
47
|
unregister,
|
|
@@ -145,8 +147,11 @@ export function InputTimeComponent({
|
|
|
145
147
|
{!isSm && inputHandler.focus && (
|
|
146
148
|
<div className="input-time-picker-popover">
|
|
147
149
|
<InputTimePickerComponent
|
|
150
|
+
value={inputHandler.value}
|
|
151
|
+
showSeconds={showSeconds}
|
|
148
152
|
onChange={(time) => {
|
|
149
153
|
inputHandler.setValue(time);
|
|
154
|
+
inputHandler.setIdle(false);
|
|
150
155
|
onChange?.(time);
|
|
151
156
|
}}
|
|
152
157
|
/>
|
|
@@ -178,8 +183,11 @@ export function InputTimeComponent({
|
|
|
178
183
|
>
|
|
179
184
|
<div className="p-4">
|
|
180
185
|
<InputTimePickerComponent
|
|
186
|
+
value={inputHandler.value}
|
|
187
|
+
showSeconds={showSeconds}
|
|
181
188
|
onChange={(time) => {
|
|
182
189
|
inputHandler.setValue(time);
|
|
190
|
+
inputHandler.setIdle(false);
|
|
183
191
|
onChange?.(time);
|
|
184
192
|
}}
|
|
185
193
|
/>
|
|
@@ -192,7 +200,9 @@ export function InputTimeComponent({
|
|
|
192
200
|
|
|
193
201
|
|
|
194
202
|
|
|
195
|
-
interface InputTimePickerProps {
|
|
203
|
+
export interface InputTimePickerProps {
|
|
204
|
+
value ?: string;
|
|
205
|
+
showSeconds ?: boolean;
|
|
196
206
|
onChange ?: (time: string) => void;
|
|
197
207
|
rightElement ?: ReactNode;
|
|
198
208
|
};
|
|
@@ -201,79 +211,229 @@ interface InputTimePickerProps {
|
|
|
201
211
|
|
|
202
212
|
|
|
203
213
|
export const InputTimePickerComponent: FC<InputTimePickerProps> = ({
|
|
214
|
+
value,
|
|
215
|
+
showSeconds = false,
|
|
204
216
|
onChange,
|
|
205
217
|
rightElement,
|
|
206
218
|
}) => {
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
219
|
+
const parsed = useMemo(() => {
|
|
220
|
+
if (!value || typeof value !== "string") {
|
|
221
|
+
return { h: 0, m: 0, s: 0, hasValue: false };
|
|
222
|
+
}
|
|
223
|
+
const parts = value.split(":").map((p) => parseInt(p, 10));
|
|
224
|
+
return {
|
|
225
|
+
h: isNaN(parts[0]) ? 0 : Math.min(23, Math.max(0, parts[0])),
|
|
226
|
+
m: isNaN(parts[1]) ? 0 : Math.min(59, Math.max(0, parts[1])),
|
|
227
|
+
s: isNaN(parts[2]) ? 0 : Math.min(59, Math.max(0, parts[2])),
|
|
228
|
+
hasValue: true,
|
|
229
|
+
};
|
|
230
|
+
}, [value]);
|
|
231
|
+
|
|
232
|
+
const [hour, setHour] = useState(parsed.h);
|
|
233
|
+
const [minute, setMinute] = useState(parsed.m);
|
|
234
|
+
const [second, setSecond] = useState(parsed.s);
|
|
235
|
+
|
|
236
|
+
const shouldShowSeconds = showSeconds || (typeof value === "string" && value.split(":").length >= 3 && value.split(":")[2] !== "");
|
|
237
|
+
|
|
238
|
+
useEffect(() => {
|
|
239
|
+
if (parsed.hasValue) {
|
|
240
|
+
setHour(parsed.h);
|
|
241
|
+
setMinute(parsed.m);
|
|
242
|
+
setSecond(parsed.s);
|
|
243
|
+
}
|
|
244
|
+
}, [parsed]);
|
|
210
245
|
|
|
211
246
|
const hours = Array.from({ length: 24 }, (_, i) => String(i).padStart(2, "0"));
|
|
212
247
|
const minutes = Array.from({ length: 60 }, (_, i) => String(i).padStart(2, "0"));
|
|
213
248
|
const seconds = Array.from({ length: 60 }, (_, i) => String(i).padStart(2, "0"));
|
|
214
249
|
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
250
|
+
const presetContainerRef = useRef<HTMLDivElement | null>(null);
|
|
251
|
+
const activePresetRef = useRef<HTMLDivElement | null>(null);
|
|
252
|
+
|
|
253
|
+
const hourContainerRef = useRef<HTMLDivElement | null>(null);
|
|
254
|
+
const activeHourRef = useRef<HTMLDivElement | null>(null);
|
|
255
|
+
|
|
256
|
+
const minuteContainerRef = useRef<HTMLDivElement | null>(null);
|
|
257
|
+
const activeMinuteRef = useRef<HTMLDivElement | null>(null);
|
|
258
|
+
|
|
259
|
+
const secondContainerRef = useRef<HTMLDivElement | null>(null);
|
|
260
|
+
const activeSecondRef = useRef<HTMLDivElement | null>(null);
|
|
261
|
+
|
|
262
|
+
const scrollToActive = (container: HTMLDivElement | null, active: HTMLDivElement | null) => {
|
|
263
|
+
if (container && active) {
|
|
264
|
+
container.scrollTop = active.offsetTop - container.offsetTop - (container.clientHeight / 2) + (active.clientHeight / 2);
|
|
265
|
+
}
|
|
219
266
|
};
|
|
220
267
|
|
|
221
268
|
useEffect(() => {
|
|
222
|
-
|
|
269
|
+
scrollToActive(hourContainerRef.current, activeHourRef.current);
|
|
270
|
+
scrollToActive(minuteContainerRef.current, activeMinuteRef.current);
|
|
271
|
+
if (shouldShowSeconds) {
|
|
272
|
+
scrollToActive(secondContainerRef.current, activeSecondRef.current);
|
|
273
|
+
}
|
|
274
|
+
scrollToActive(presetContainerRef.current, activePresetRef.current);
|
|
275
|
+
}, []);
|
|
276
|
+
|
|
277
|
+
const emitChange = (newH: number, newM: number, newS: number) => {
|
|
278
|
+
const hh = String(newH).padStart(2, "0");
|
|
279
|
+
const mm = String(newM).padStart(2, "0");
|
|
280
|
+
const ss = String(newS).padStart(2, "0");
|
|
281
|
+
const formatted = shouldShowSeconds ? `${hh}:${mm}:${ss}` : `${hh}:${mm}`;
|
|
223
282
|
onChange?.(formatted);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const renderColumn = (items: string[], type: "h" | "m" | "s", activeValue: number) => (
|
|
227
|
-
<div className="flex-1 overflow-y-auto text-center input-scroll">
|
|
228
|
-
{items.map((item) => {
|
|
229
|
-
const active = Number(item) === activeValue;
|
|
283
|
+
};
|
|
230
284
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
285
|
+
const handleSelect = (type: "h" | "m" | "s", val: number) => {
|
|
286
|
+
let newH = hour;
|
|
287
|
+
let newM = minute;
|
|
288
|
+
let newS = second;
|
|
289
|
+
|
|
290
|
+
if (type === "h") {
|
|
291
|
+
newH = val;
|
|
292
|
+
setHour(val);
|
|
293
|
+
} else if (type === "m") {
|
|
294
|
+
newM = val;
|
|
295
|
+
setMinute(val);
|
|
296
|
+
} else if (type === "s") {
|
|
297
|
+
newS = val;
|
|
298
|
+
setSecond(val);
|
|
299
|
+
}
|
|
244
300
|
|
|
245
|
-
|
|
246
|
-
|
|
301
|
+
emitChange(newH, newM, newS);
|
|
302
|
+
};
|
|
247
303
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
304
|
+
const handlePresetSelect = (presetStr: string) => {
|
|
305
|
+
const parts = presetStr.split(":").map(Number);
|
|
306
|
+
const newH = parts[0] ?? 0;
|
|
307
|
+
const newM = parts[1] ?? 0;
|
|
308
|
+
const newS = 0;
|
|
309
|
+
setHour(newH);
|
|
310
|
+
setMinute(newM);
|
|
311
|
+
setSecond(newS);
|
|
312
|
+
emitChange(newH, newM, newS);
|
|
313
|
+
};
|
|
252
314
|
|
|
253
|
-
|
|
315
|
+
const timeSlots = useMemo(() => {
|
|
316
|
+
const slots: string[] = [];
|
|
317
|
+
for (let i = 0; i < 24 * 60; i += 30) {
|
|
318
|
+
const h = Math.floor(i / 60);
|
|
319
|
+
const m = i % 60;
|
|
320
|
+
slots.push(`${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`);
|
|
254
321
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}, [])
|
|
322
|
+
return slots;
|
|
323
|
+
}, []);
|
|
258
324
|
|
|
259
325
|
return (
|
|
260
|
-
<div className="w-full
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
326
|
+
<div className="w-full flex gap-2 h-[260px] select-none text-xs">
|
|
327
|
+
{/* 1. Presets Column */}
|
|
328
|
+
<div className="w-20 shrink-0 h-full flex flex-col border-r border-stroke pr-1.5">
|
|
329
|
+
<div className="text-[11px] font-semibold text-light-foreground text-center py-1 mb-1 border-b border-stroke">
|
|
330
|
+
Preset
|
|
331
|
+
</div>
|
|
332
|
+
<div className="flex-1 overflow-y-auto input-scroll flex flex-col gap-0.5" ref={presetContainerRef}>
|
|
333
|
+
{timeSlots.map((time) => {
|
|
334
|
+
const isPresetActive = `${String(hour).padStart(2, "0")}:${String(minute).padStart(2, "0")}` === time;
|
|
335
|
+
return (
|
|
336
|
+
<div
|
|
337
|
+
key={time}
|
|
338
|
+
ref={isPresetActive ? activePresetRef : null}
|
|
339
|
+
className={cn(
|
|
340
|
+
"py-1.5 px-1 text-center font-medium rounded-md cursor-pointer transition-colors",
|
|
341
|
+
isPresetActive
|
|
342
|
+
? "bg-primary text-white font-semibold"
|
|
343
|
+
: "hover:bg-light-primary text-foreground"
|
|
344
|
+
)}
|
|
345
|
+
onClick={() => handlePresetSelect(time)}
|
|
346
|
+
>
|
|
347
|
+
{time}
|
|
348
|
+
</div>
|
|
349
|
+
);
|
|
350
|
+
})}
|
|
351
|
+
</div>
|
|
269
352
|
</div>
|
|
270
353
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
354
|
+
{/* 2. Hour Column */}
|
|
355
|
+
<div className="flex-1 h-full flex flex-col">
|
|
356
|
+
<div className="text-[11px] font-semibold text-light-foreground text-center py-1 mb-1 border-b border-stroke">
|
|
357
|
+
Jam
|
|
358
|
+
</div>
|
|
359
|
+
<div className="flex-1 overflow-y-auto input-scroll flex flex-col gap-0.5" ref={hourContainerRef}>
|
|
360
|
+
{hours.map((item) => {
|
|
361
|
+
const active = Number(item) === hour;
|
|
362
|
+
return (
|
|
363
|
+
<div
|
|
364
|
+
key={item}
|
|
365
|
+
ref={active ? activeHourRef : null}
|
|
366
|
+
onClick={() => handleSelect("h", Number(item))}
|
|
367
|
+
className={cn(
|
|
368
|
+
"py-1.5 text-center font-medium rounded-md cursor-pointer transition-colors",
|
|
369
|
+
active
|
|
370
|
+
? "bg-primary text-white font-semibold"
|
|
371
|
+
: "hover:bg-light-primary text-foreground"
|
|
372
|
+
)}
|
|
373
|
+
>
|
|
374
|
+
{item}
|
|
375
|
+
</div>
|
|
376
|
+
);
|
|
377
|
+
})}
|
|
378
|
+
</div>
|
|
379
|
+
</div>
|
|
380
|
+
|
|
381
|
+
{/* 3. Minute Column */}
|
|
382
|
+
<div className="flex-1 h-full flex flex-col">
|
|
383
|
+
<div className="text-[11px] font-semibold text-light-foreground text-center py-1 mb-1 border-b border-stroke">
|
|
384
|
+
Menit
|
|
385
|
+
</div>
|
|
386
|
+
<div className="flex-1 overflow-y-auto input-scroll flex flex-col gap-0.5" ref={minuteContainerRef}>
|
|
387
|
+
{minutes.map((item) => {
|
|
388
|
+
const active = Number(item) === minute;
|
|
389
|
+
return (
|
|
390
|
+
<div
|
|
391
|
+
key={item}
|
|
392
|
+
ref={active ? activeMinuteRef : null}
|
|
393
|
+
onClick={() => handleSelect("m", Number(item))}
|
|
394
|
+
className={cn(
|
|
395
|
+
"py-1.5 text-center font-medium rounded-md cursor-pointer transition-colors",
|
|
396
|
+
active
|
|
397
|
+
? "bg-primary text-white font-semibold"
|
|
398
|
+
: "hover:bg-light-primary text-foreground"
|
|
399
|
+
)}
|
|
400
|
+
>
|
|
401
|
+
{item}
|
|
402
|
+
</div>
|
|
403
|
+
);
|
|
404
|
+
})}
|
|
405
|
+
</div>
|
|
275
406
|
</div>
|
|
276
407
|
|
|
408
|
+
{/* 4. Second Column (Optional) */}
|
|
409
|
+
{shouldShowSeconds && (
|
|
410
|
+
<div className="flex-1 h-full flex flex-col">
|
|
411
|
+
<div className="text-[11px] font-semibold text-light-foreground text-center py-1 mb-1 border-b border-stroke">
|
|
412
|
+
Detik
|
|
413
|
+
</div>
|
|
414
|
+
<div className="flex-1 overflow-y-auto input-scroll flex flex-col gap-0.5" ref={secondContainerRef}>
|
|
415
|
+
{seconds.map((item) => {
|
|
416
|
+
const active = Number(item) === second;
|
|
417
|
+
return (
|
|
418
|
+
<div
|
|
419
|
+
key={item}
|
|
420
|
+
ref={active ? activeSecondRef : null}
|
|
421
|
+
onClick={() => handleSelect("s", Number(item))}
|
|
422
|
+
className={cn(
|
|
423
|
+
"py-1.5 text-center font-medium rounded-md cursor-pointer transition-colors",
|
|
424
|
+
active
|
|
425
|
+
? "bg-primary text-white font-semibold"
|
|
426
|
+
: "hover:bg-light-primary text-foreground"
|
|
427
|
+
)}
|
|
428
|
+
>
|
|
429
|
+
{item}
|
|
430
|
+
</div>
|
|
431
|
+
);
|
|
432
|
+
})}
|
|
433
|
+
</div>
|
|
434
|
+
</div>
|
|
435
|
+
)}
|
|
436
|
+
|
|
277
437
|
{rightElement && <div>{rightElement}</div>}
|
|
278
438
|
</div>
|
|
279
439
|
);
|
|
@@ -15,6 +15,8 @@ export interface SelectOptionProps {
|
|
|
15
15
|
value : string | number;
|
|
16
16
|
searchable ?: string[];
|
|
17
17
|
customLabel ?: ReactNode;
|
|
18
|
+
customOption ?: ((option: any) => ReactNode) | ReactNode;
|
|
19
|
+
[key: string] : any;
|
|
18
20
|
};
|
|
19
21
|
|
|
20
22
|
export interface SelectProps {
|
|
@@ -35,7 +37,7 @@ export interface SelectProps {
|
|
|
35
37
|
|
|
36
38
|
options ?: SelectOptionProps[];
|
|
37
39
|
searchable ?: boolean;
|
|
38
|
-
serverOptionControl ?: ApiType & { cacheName?: string |
|
|
40
|
+
serverOptionControl ?: ApiType & { cacheName?: string | boolean; selectableOption?: string[] };
|
|
39
41
|
idbOptionControl ?: { store: string, labelKey: string, valueKey: string };
|
|
40
42
|
serverSearchable ?: boolean;
|
|
41
43
|
includedOptions ?: SelectOptionProps[];
|
|
@@ -45,6 +47,8 @@ export interface SelectProps {
|
|
|
45
47
|
maxShowOption ?: number;
|
|
46
48
|
creatable ?: boolean | string;
|
|
47
49
|
creatableLabel ?: string;
|
|
50
|
+
customOption ?: (option: any) => ReactNode;
|
|
51
|
+
selectableOption ?: string[];
|
|
48
52
|
|
|
49
53
|
onChange ?: (value: any, data?: any) => any;
|
|
50
54
|
register ?: (name: string, validations?: ValidationRules) => void;
|
|
@@ -83,9 +87,11 @@ export function SelectComponent({
|
|
|
83
87
|
exceptOptions = [],
|
|
84
88
|
tempOptions,
|
|
85
89
|
newOption,
|
|
86
|
-
maxShowOption
|
|
90
|
+
maxShowOption,
|
|
87
91
|
creatable,
|
|
88
92
|
creatableLabel,
|
|
93
|
+
customOption,
|
|
94
|
+
selectableOption,
|
|
89
95
|
|
|
90
96
|
register,
|
|
91
97
|
unregister,
|
|
@@ -184,13 +190,27 @@ export function SelectComponent({
|
|
|
184
190
|
let newFilteredOptions: SelectOptionProps[] = [];
|
|
185
191
|
|
|
186
192
|
if (searchable && !serverSearchable && dataOptions?.length) {
|
|
187
|
-
if (e
|
|
188
|
-
|
|
193
|
+
if (e?.target?.value) {
|
|
194
|
+
const val = e.target.value.toLowerCase();
|
|
195
|
+
const matched = dataOptions.filter((opt) => {
|
|
196
|
+
if (opt.searchable?.some((s) => s.toLowerCase().indexOf(val) > -1)) return true;
|
|
197
|
+
if (typeof opt.label === "string" && opt.label.toLowerCase().indexOf(val) > -1) return true;
|
|
198
|
+
if (typeof opt.code === "string" && opt.code.toLowerCase().indexOf(val) > -1) return true;
|
|
199
|
+
if (typeof opt.name === "string" && opt.name.toLowerCase().indexOf(val) > -1) return true;
|
|
200
|
+
return false;
|
|
201
|
+
});
|
|
202
|
+
newFilteredOptions = typeof maxShowOption === "number" && maxShowOption > 0
|
|
203
|
+
? matched.slice(0, maxShowOption)
|
|
204
|
+
: matched;
|
|
189
205
|
} else {
|
|
190
|
-
newFilteredOptions =
|
|
206
|
+
newFilteredOptions = typeof maxShowOption === "number" && maxShowOption > 0
|
|
207
|
+
? dataOptions.slice(0, maxShowOption)
|
|
208
|
+
: dataOptions;
|
|
191
209
|
}
|
|
192
210
|
} else {
|
|
193
|
-
newFilteredOptions =
|
|
211
|
+
newFilteredOptions = typeof maxShowOption === "number" && maxShowOption > 0
|
|
212
|
+
? (dataOptions || []).slice(0, maxShowOption)
|
|
213
|
+
: (dataOptions || []);
|
|
194
214
|
}
|
|
195
215
|
|
|
196
216
|
setActiveOption(-1);
|
|
@@ -251,40 +271,68 @@ export function SelectComponent({
|
|
|
251
271
|
const fetchOptions = async () => {
|
|
252
272
|
setLoadingOption(true);
|
|
253
273
|
|
|
274
|
+
const selectableOpt = selectableOption || serverOptionControl?.selectableOption || serverOptionControl?.params?.selectableOption;
|
|
275
|
+
|
|
254
276
|
const serverControl = {
|
|
255
277
|
...serverOptionControl,
|
|
256
|
-
params:
|
|
257
|
-
|
|
278
|
+
params: {
|
|
279
|
+
...(serverOptionControl?.params || {}),
|
|
280
|
+
...(selectableOpt ? { selectableOption: selectableOpt } : {}),
|
|
281
|
+
...(serverSearchable ? { search: keywordSearch } : {}),
|
|
282
|
+
},
|
|
283
|
+
headers: { "X-Option": 1, ...(serverOptionControl?.headers || {}) }
|
|
258
284
|
};
|
|
259
285
|
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
[...(mutateOptions?.data?.data || []), ...(includedOptions || [])].filter(
|
|
286
|
+
const cacheKey = typeof serverOptionControl?.cacheName === "string" && serverOptionControl.cacheName.trim() !== ""
|
|
287
|
+
? serverOptionControl.cacheName
|
|
288
|
+
: serverOptionControl?.cacheName === true
|
|
289
|
+
? `option_${serverOptionControl?.path || serverOptionControl?.url}`
|
|
290
|
+
: null;
|
|
291
|
+
|
|
292
|
+
if (cacheKey) {
|
|
293
|
+
const getCacheOptions = await cavity.get(cacheKey);
|
|
294
|
+
const cacheData = getCacheOptions?.data;
|
|
295
|
+
const cacheOptions = (Array.isArray(cacheData?.data) ? cacheData.data : Array.isArray(cacheData) ? cacheData : []) as SelectOptionProps[];
|
|
296
|
+
|
|
297
|
+
if (cacheOptions?.length) {
|
|
298
|
+
const cached = [...cacheOptions, ...includedOptions].filter(
|
|
274
299
|
(op: SelectOptionProps) => !exceptOptions?.includes(op.value)
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
|
|
300
|
+
);
|
|
301
|
+
setDataOptions(cached);
|
|
302
|
+
setFilteredOptions(typeof maxShowOption === "number" && maxShowOption > 0 ? cached.slice(0, maxShowOption) : cached);
|
|
303
|
+
setLoadingOption(false);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
278
307
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
308
|
+
const mutateOptions = await api(serverControl || {});
|
|
309
|
+
const fetchedData = mutateOptions?.data?.data || mutateOptions?.data || [];
|
|
310
|
+
const optionsData = Array.isArray(fetchedData) ? fetchedData.map((item: any) => {
|
|
311
|
+
if (item && typeof item === "object") {
|
|
312
|
+
return {
|
|
313
|
+
...item,
|
|
314
|
+
name: item.name ?? item.label,
|
|
315
|
+
label: item.label ?? item.name ?? item.code ?? item.value,
|
|
316
|
+
};
|
|
285
317
|
}
|
|
286
|
-
|
|
318
|
+
return item;
|
|
319
|
+
}) : [];
|
|
320
|
+
|
|
321
|
+
const finalOptions = [...optionsData, ...(includedOptions || [])].filter(
|
|
322
|
+
(op: SelectOptionProps) => !exceptOptions?.includes(op.value)
|
|
323
|
+
);
|
|
324
|
+
setDataOptions(finalOptions);
|
|
325
|
+
setFilteredOptions(typeof maxShowOption === "number" && maxShowOption > 0 ? finalOptions.slice(0, maxShowOption) : finalOptions);
|
|
326
|
+
setShowOption(true);
|
|
327
|
+
|
|
328
|
+
if (cacheKey) {
|
|
329
|
+
cavity.set({
|
|
330
|
+
key: cacheKey,
|
|
331
|
+
data: mutateOptions?.data,
|
|
332
|
+
expired: 5,
|
|
333
|
+
});
|
|
287
334
|
}
|
|
335
|
+
setLoadingOption(false);
|
|
288
336
|
};
|
|
289
337
|
|
|
290
338
|
const fetchIdbOptions = async () => {
|
|
@@ -309,6 +357,7 @@ export function SelectComponent({
|
|
|
309
357
|
})
|
|
310
358
|
|
|
311
359
|
setDataOptions(rows);
|
|
360
|
+
setFilteredOptions(typeof maxShowOption === "number" && maxShowOption > 0 ? rows.slice(0, maxShowOption) : rows);
|
|
312
361
|
setLoadingOption(false);
|
|
313
362
|
}
|
|
314
363
|
};
|
|
@@ -402,7 +451,8 @@ export function SelectComponent({
|
|
|
402
451
|
placeholder={!inputHandler.value || (Array.isArray(inputHandler.value) && !inputHandler.value.length) ? placeholder : ""}
|
|
403
452
|
disabled={disabled}
|
|
404
453
|
className={cn(
|
|
405
|
-
"input cursor-pointer",
|
|
454
|
+
"input cursor-pointer pr-10",
|
|
455
|
+
clearable && "pr-18",
|
|
406
456
|
leftIcon && "input-with-left-icon",
|
|
407
457
|
rightIcon && "input-with-right-icon",
|
|
408
458
|
isCreatingNew && "pl-[4.8rem]",
|
|
@@ -493,22 +543,23 @@ export function SelectComponent({
|
|
|
493
543
|
return (
|
|
494
544
|
<div key={key} className="input-values-item">
|
|
495
545
|
<span>{dataOptions?.find((option) => option.value == item)?.label}</span>
|
|
496
|
-
<
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
546
|
+
<div className="input-values-delete">
|
|
547
|
+
<Icon
|
|
548
|
+
icon="solid/times"
|
|
549
|
+
onClick={() => {
|
|
550
|
+
const values = Array().concat(inputHandler.value);
|
|
551
|
+
const index = values.findIndex((val: string | number) => val == item);
|
|
552
|
+
|
|
553
|
+
inputHandler.setValue(values.filter((_, val) => val != index));
|
|
554
|
+
|
|
555
|
+
if (!values.filter((_, val) => val != index)?.length) {
|
|
556
|
+
setInputShowValue("");
|
|
557
|
+
serverSearchable && setKeyword("");
|
|
558
|
+
onChange?.("");
|
|
559
|
+
}
|
|
560
|
+
}}
|
|
561
|
+
/>
|
|
562
|
+
</div>
|
|
512
563
|
</div>
|
|
513
564
|
);
|
|
514
565
|
})}
|
|
@@ -650,7 +701,7 @@ export function SelectComponent({
|
|
|
650
701
|
return (
|
|
651
702
|
<li
|
|
652
703
|
className={cn(
|
|
653
|
-
"input-suggest",
|
|
704
|
+
"input-suggest flex items-center",
|
|
654
705
|
(key == activeOption || selected) && "input-suggest-active",
|
|
655
706
|
pcn<CT>(className, "suggest-item"),
|
|
656
707
|
(key == activeOption || selected) && pcn<CT>(className, "suggest-item", "active")
|
|
@@ -669,8 +720,11 @@ export function SelectComponent({
|
|
|
669
720
|
setIsCreatingNew(false);
|
|
670
721
|
|
|
671
722
|
if (!multiple) {
|
|
672
|
-
|
|
673
|
-
|
|
723
|
+
const displayLabel = typeof option.label === "string" || typeof option.label === "number"
|
|
724
|
+
? option.label
|
|
725
|
+
: (option.name || option.code || option.value);
|
|
726
|
+
setInputShowValue(displayLabel);
|
|
727
|
+
serverSearchable && setKeyword(typeof displayLabel === "string" ? displayLabel : "");
|
|
674
728
|
inputHandler.setValue(option.value);
|
|
675
729
|
onChange?.(option.value, option);
|
|
676
730
|
} else {
|
|
@@ -700,7 +754,11 @@ export function SelectComponent({
|
|
|
700
754
|
className="select-suggest-check"
|
|
701
755
|
/>
|
|
702
756
|
)}
|
|
703
|
-
{
|
|
757
|
+
{customOption
|
|
758
|
+
? customOption(option)
|
|
759
|
+
: (option.customOption
|
|
760
|
+
? (typeof option.customOption === "function" ? option.customOption(option) : option.customOption)
|
|
761
|
+
: (option.customLabel || option.label))}
|
|
704
762
|
</li>
|
|
705
763
|
);
|
|
706
764
|
})}
|