@skalfa/skalfa-component 1.0.8 → 1.0.10

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 (87) hide show
  1. package/dist/accordion/Accordion.component.js +2 -3
  2. package/dist/breadcrumb/Breadcrumb.component.js +2 -3
  3. package/dist/button/Button.component.d.ts +2 -1
  4. package/dist/button/Button.component.js +2 -2
  5. package/dist/carousel/Carousel.component.js +2 -3
  6. package/dist/chip/Chip.component.js +2 -3
  7. package/dist/contexts-mock.d.ts +2 -0
  8. package/dist/contexts-mock.js +10 -0
  9. package/dist/input/Checkbox.component.js +2 -3
  10. package/dist/input/Input.component.d.ts +3 -2
  11. package/dist/input/Input.component.js +3 -3
  12. package/dist/input/InputCheckbox.component.js +1 -1
  13. package/dist/input/InputCurrency.component.d.ts +3 -2
  14. package/dist/input/InputCurrency.component.js +3 -3
  15. package/dist/input/InputDate.component.js +4 -5
  16. package/dist/input/InputDatetime.component.d.ts +3 -2
  17. package/dist/input/InputDatetime.component.js +3 -3
  18. package/dist/input/InputDocument.component.js +7 -8
  19. package/dist/input/InputImage.component.js +3 -4
  20. package/dist/input/InputNumber.component.js +3 -4
  21. package/dist/input/InputPassword.component.js +3 -3
  22. package/dist/input/InputRadio.component.js +1 -1
  23. package/dist/input/InputTime.component.js +3 -3
  24. package/dist/input/InputValues.component.js +3 -4
  25. package/dist/input/Select.component.js +6 -7
  26. package/dist/modal/FloatingPage.component.d.ts +3 -1
  27. package/dist/modal/FloatingPage.component.js +7 -3
  28. package/dist/modal/Modal.component.js +1 -2
  29. package/dist/modal/ModalConfirm.component.d.ts +2 -2
  30. package/dist/modal/ModalConfirm.component.js +14 -12
  31. package/dist/modal/Toast.component.js +1 -2
  32. package/dist/nav/Bottombar.component.d.ts +2 -2
  33. package/dist/nav/Bottombar.component.js +7 -8
  34. package/dist/nav/Footer.component.d.ts +2 -2
  35. package/dist/nav/Footer.component.js +6 -7
  36. package/dist/nav/Headbar.component.js +2 -3
  37. package/dist/nav/Navbar.component.d.ts +2 -2
  38. package/dist/nav/Navbar.component.js +3 -4
  39. package/dist/nav/Sidebar.component.d.ts +3 -7
  40. package/dist/nav/Sidebar.component.js +14 -17
  41. package/dist/supervision/FormSupervision.component.d.ts +4 -29
  42. package/dist/supervision/FormSupervision.component.js +84 -136
  43. package/dist/supervision/TableSupervision.component.d.ts +17 -26
  44. package/dist/supervision/TableSupervision.component.js +207 -193
  45. package/dist/table/ControlBar.component.js +15 -16
  46. package/dist/table/FilterComponent.js +4 -6
  47. package/dist/table/Pagination.component.js +2 -3
  48. package/dist/table/Table.component.js +3 -4
  49. package/dist/wrap/Swipe.component.js +2 -2
  50. package/package.json +18 -10
  51. package/src/accordion/Accordion.component.tsx +3 -3
  52. package/src/breadcrumb/Breadcrumb.component.tsx +4 -4
  53. package/src/button/Button.component.tsx +3 -3
  54. package/src/carousel/Carousel.component.tsx +4 -4
  55. package/src/chip/Chip.component.tsx +3 -4
  56. package/src/contexts-mock.ts +6 -0
  57. package/src/input/Checkbox.component.tsx +3 -3
  58. package/src/input/Input.component.tsx +6 -6
  59. package/src/input/InputCheckbox.component.tsx +1 -1
  60. package/src/input/InputCurrency.component.tsx +6 -6
  61. package/src/input/InputDate.component.tsx +6 -7
  62. package/src/input/InputDatetime.component.tsx +6 -6
  63. package/src/input/InputDocument.component.tsx +9 -9
  64. package/src/input/InputImage.component.tsx +4 -5
  65. package/src/input/InputNumber.component.tsx +7 -8
  66. package/src/input/InputPassword.component.tsx +4 -4
  67. package/src/input/InputRadio.component.tsx +1 -1
  68. package/src/input/InputTime.component.tsx +4 -4
  69. package/src/input/InputValues.component.tsx +5 -6
  70. package/src/input/Select.component.tsx +10 -10
  71. package/src/modal/FloatingPage.component.tsx +33 -3
  72. package/src/modal/Modal.component.tsx +1 -2
  73. package/src/modal/ModalConfirm.component.tsx +34 -49
  74. package/src/modal/Toast.component.tsx +1 -2
  75. package/src/nav/Bottombar.component.tsx +9 -10
  76. package/src/nav/Footer.component.tsx +8 -9
  77. package/src/nav/Headbar.component.tsx +3 -3
  78. package/src/nav/Navbar.component.tsx +9 -10
  79. package/src/nav/Sidebar.component.tsx +55 -46
  80. package/src/supervision/FormSupervision.component.tsx +203 -224
  81. package/src/supervision/TableSupervision.component.tsx +420 -455
  82. package/src/table/ControlBar.component.tsx +16 -16
  83. package/src/table/FilterComponent.tsx +9 -11
  84. package/src/table/Pagination.component.tsx +6 -6
  85. package/src/table/Table.component.tsx +5 -5
  86. package/src/wrap/Swipe.component.tsx +3 -3
  87. package/src/types.d.ts +0 -7
@@ -1,23 +1,37 @@
1
1
  "use client"
2
2
 
3
- import React, { ReactNode, useEffect, useRef, useState } from "react";
4
- import { faSave, faQuestionCircle, faPlus, faTimes } from "@fortawesome/free-solid-svg-icons";
3
+ import React, { ReactNode, useEffect, useState } from "react";
5
4
  import { ApiType, cn, pcn, FormErrorType, FormRegisterType, FormValueType, useForm, ValidationRules, DBSchema } from "@utils";
6
- import { InputCheckboxComponent, InputCheckboxProps } from "../input/InputCheckbox.component";
7
- import { InputComponent, InputProps } from "../input/Input.component";
8
- import { InputCurrencyComponent, InputCurrencyProps } from "../input/InputCurrency.component";
9
- import { InputDateComponent, InputDateProps } from "../input/InputDate.component";
10
- import { InputNumberComponent, InputNumberProps } from "../input/InputNumber.component";
11
- import { InputOtpComponent, InputOtpProps } from "../input/InputOtp.component";
12
- import { InputPasswordComponent, InputPasswordProps } from "../input/InputPassword.component";
13
- import { InputRadioComponent, InputRadioProps } from "../input/InputRadio.component";
14
- import { SelectComponent, SelectProps } from "../input/Select.component";
15
- import { ButtonComponent } from "../button/Button.component";
16
- import { ModalConfirmComponent } from "../modal/ModalConfirm.component";
17
- import { ToastComponent } from "../modal/Toast.component";
18
- import { InputTimeProps, InputTimeComponent } from "../input/InputTime.component";
19
- import { InputImageProps, InputImageComponent } from "../input/InputImage.component";
20
- import { InputDateTimeProps, InputDatetimeComponent } from "../input/InputDatetime.component";
5
+ import {
6
+ InputCheckboxComponent,
7
+ InputComponent,
8
+ InputCurrencyComponent,
9
+ InputDateComponent,
10
+ InputNumberComponent,
11
+ InputOtpComponent,
12
+ InputPasswordComponent,
13
+ InputRadioComponent,
14
+ SelectComponent,
15
+ ButtonComponent,
16
+ ModalConfirmComponent,
17
+ InputProps,
18
+ InputCheckboxProps,
19
+ InputCurrencyProps,
20
+ InputDateProps,
21
+ InputNumberProps,
22
+ InputRadioProps,
23
+ SelectProps,
24
+ InputPasswordProps,
25
+ InputOtpProps,
26
+ InputTimeProps,
27
+ InputImageProps,
28
+ InputDateTimeProps,
29
+ InputDatetimeComponent,
30
+ InputTimeComponent,
31
+ InputImageComponent,
32
+ } from "../";
33
+ import { Icon } from "@skalfa/skalfa-icon";
34
+ import { useLang } from "@skalfa/skalfa-lang";
21
35
 
22
36
 
23
37
 
@@ -33,7 +47,6 @@ type formCustomConstructionProps = ({
33
47
  }: {
34
48
  formControl : (name: string) => {
35
49
  register: (regName: string, regValidations?: ValidationRules | undefined) => void;
36
- unregister: (regName: string) => void;
37
50
  onChange: (e: any) => void;
38
51
  value: any;
39
52
  invalid: any;
@@ -47,12 +60,11 @@ type formCustomConstructionProps = ({
47
60
  }) => ReactNode;
48
61
 
49
62
  type ClusterConstruction = {
50
- name : string;
51
- label : string;
52
- tip : string;
53
- fields : FormType[];
54
- wrap : boolean;
55
- min ?: number;
63
+ name : string;
64
+ label : string;
65
+ tip : string;
66
+ fields : FormType[];
67
+ wrap : boolean;
56
68
 
57
69
  /** Use custom class with: "label::", "tip::", "error::", "icon::", "suggest::", "suggest-item::". */
58
70
  className : string;
@@ -77,28 +89,12 @@ type ConstructionMap = {
77
89
 
78
90
  type TypeKeys = keyof ConstructionMap;
79
91
 
80
- export type WatchContext = {
81
- values : Record<string, any>
82
- self : string
83
- prev : WatchAction
84
- }
85
-
86
- export type WatchAction = {
87
- disabled ?: boolean
88
- hidden ?: boolean
89
- value ?: any
90
- required ?: boolean
91
- readonly ?: boolean
92
- reset ?: boolean
93
- }
94
-
95
92
  export interface FormType<T extends TypeKeys = keyof ConstructionMap> {
96
93
  col ?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | string;
97
94
  className ?: string;
98
95
  construction ?: ConstructionMap[T];
99
96
  type ?: T;
100
97
  onHide ?: (values: any) => boolean;
101
- watch ?: (ctx: WatchContext) => WatchAction | undefined;
102
98
  }
103
99
 
104
100
  export interface formSupervisionProps {
@@ -111,7 +107,9 @@ export interface formSupervisionProps {
111
107
  footerControl ?: ({ loading }: { loading: boolean }) => ReactNode;
112
108
  onSuccess ?: (data: any) => void;
113
109
  onError ?: (code: number) => void;
110
+ successMessage ?: string;
114
111
  className ?: string;
112
+ id ?: string;
115
113
  }
116
114
 
117
115
 
@@ -127,135 +125,95 @@ export function FormSupervisionComponent({
127
125
  footerControl,
128
126
  payload,
129
127
  className = "",
128
+ successMessage,
129
+ id,
130
130
  }: formSupervisionProps) {
131
+ const l = useLang();
132
+
131
133
  const [modal, setModal] = useState<boolean | "success" | "failed">(false);
132
134
  const [fresh, setFresh] = useState<boolean>(true);
133
- const [mapGroups, setMapGroups] = useState<Record<string, number[]>>({});
134
- const [watchState, setWatchState] = useState<Record<string, WatchAction>>({});
135
- const watchRef = useRef<Record<string, WatchAction>>({});
136
-
137
- const { formControl, setRegister, unregister, unregisterPrefix, values, setValues, errors, setErrors, setDefaultValues, submit, loading, confirm } = useForm({
138
- ...submitControl,
135
+ const [mapGroups, setMapGroups] = useState<Record<string, number[]>>({});
136
+
137
+ const [
138
+ {
139
+ formControl,
140
+ setRegister,
141
+ values,
142
+ setValues,
143
+ errors,
144
+ setErrors,
145
+ setDefaultValues,
146
+ submit,
147
+ loading,
148
+ confirm,
149
+ },
150
+ ] = useForm(
151
+ submitControl,
139
152
  payload,
140
153
  confirmation,
141
- onSuccess: (data: any) => {
154
+ (data: any) => {
142
155
  onSuccess?.(data);
143
156
  setModal("success");
157
+ setTimeout(() => setModal(false), 1000);
144
158
  resetFresh();
145
159
  },
146
- onFailed: (code: number) => {
160
+ (code: number) => {
147
161
  onError?.(code);
148
162
  if (code == 422) confirm.onClose();
149
163
  else setModal("failed");
150
- },
151
- });
152
-
153
- const resetFresh = () => {
154
- setFresh(false);
155
- setTimeout(() => setFresh(true), 300);
156
- };
157
-
158
- useEffect(() => {
159
- resetFresh();
160
- }, [fields]);
161
-
162
- useEffect(() => {
163
- if (defaultValue) setDefaultValues(defaultValue);
164
- else {
165
- setDefaultValues(null);
166
- resetFresh();
167
- }
168
- }, [defaultValue]);
169
-
170
- // ==============================>
171
- // ## Watch: collect watchers from fields
172
- // ==============================>
173
- const collectWatchers = (fieldList: FormType[], prefix?: string): { name: string, watch: NonNullable<FormType['watch']>, construction: any }[] => {
174
- const result: { name: string, watch: NonNullable<FormType['watch']>, construction: any }[] = [];
175
-
176
- for (const f of fieldList) {
177
- const inputType = f.type || "default";
178
- const name = prefix ? `${prefix}.${f.construction?.name}` : f.construction?.name || "";
179
-
180
- if (inputType === "cluster") {
181
- const cluster = f.construction as ClusterConstruction;
182
- const groupKey = prefix ? `${prefix}.${cluster.name}` : cluster.name;
183
- const group = mapGroups[groupKey] || [0];
184
-
185
- for (const gIndex of group) {
186
- result.push(...collectWatchers(cluster.fields, `${cluster.name}[${gIndex}]`));
187
- }
188
- } else if (f.watch) {
189
- result.push({ name, watch: f.watch, construction: f.construction });
190
- }
191
- }
192
-
193
- return result;
194
- };
195
-
196
-
197
- // ==============================>
198
- // ## Watch: execute watchers on value change
199
- // ==============================>
200
- useEffect(() => {
201
- const watchers = collectWatchers(fields);
202
- if (watchers.length === 0) {
203
- if (Object.keys(watchRef.current).length > 0) {
204
- watchRef.current = {};
205
- setWatchState({});
206
- }
207
- return;
208
164
  }
165
+ );
209
166
 
210
- const valMap = (values as any[]).reduce((acc, v) => { acc[v.name] = v.value; return acc; }, {} as any);
211
-
212
- const nextState : Record<string, WatchAction> = {};
213
- const valueUpdates : FormValueType[] = [];
214
-
215
- for (const { name, watch, construction } of watchers) {
216
- const prev = watchRef.current[name] || {};
217
- const action = watch({ values: valMap, self: name, prev });
218
-
219
- if (!action) continue;
220
-
221
- nextState[name] = action;
167
+ const GroupsFromDefaults = (defaults: Record<string, any>): Record<string, number[]> => {
168
+ const groups: Record<string, Set<number>> = {};
222
169
 
223
- if (action.hidden && !prev.hidden) unregister(name);
170
+ Object.keys(defaults).forEach((key) => {
171
+ const match = key.match(/^(.+?)\[(\d+)\]/);
172
+ if (!match) return;
224
173
 
225
- if (action.required !== prev.required) {
226
- const baseValidations = Array.isArray(construction?.validations) ? [...construction.validations] : [];
227
- const newValidations = action.required ? (baseValidations.includes("required") ? baseValidations : [...baseValidations, "required"]) : baseValidations.filter((v: string) => v !== "required");
174
+ const [, groupKey, indexStr] = match;
175
+ const index = Number(indexStr);
228
176
 
229
- setRegister({ name, validations: newValidations });
177
+ if (!groups[groupKey]) {
178
+ groups[groupKey] = new Set();
230
179
  }
231
180
 
232
- if (action.reset) {
233
- const cur = valMap[name];
181
+ groups[groupKey].add(index);
182
+ });
234
183
 
235
- if (cur != null && cur !== "") valueUpdates.push({ name, value: "" });
236
- } else if (action.value !== undefined && action.value !== valMap[name]) {
237
- valueUpdates.push({ name, value: action.value });
238
- }
239
- }
184
+ return Object.fromEntries(
185
+ Object.entries(groups).map(([key, indexSet]) => [
186
+ key,
187
+ Array.from(indexSet)
188
+ .sort((a, b) => a - b)
189
+ .map((_, i) => i),
190
+ ])
191
+ );
192
+ };
240
193
 
241
- if (JSON.stringify(watchRef.current) !== JSON.stringify(nextState)) {
242
- watchRef.current = nextState;
243
- setWatchState(nextState);
244
- }
194
+ const resetFresh = () => {
195
+ setFresh(false);
196
+ setTimeout(() => setFresh(true), 300);
197
+ };
245
198
 
246
- if (valueUpdates.length > 0) {
247
- const merged = [...values];
199
+ useEffect(() => {
200
+ resetFresh();
201
+ }, [fields]);
248
202
 
249
- for (const upd of valueUpdates) {
250
- const idx = merged.findIndex(v => v.name === upd.name);
251
- if (idx >= 0) merged[idx] = upd;
252
- else merged.push(upd);
253
- }
203
+ useEffect(() => {
204
+ if (defaultValue) {
205
+ setDefaultValues(defaultValue);
254
206
 
255
- setValues(merged);
256
- }
257
- }, [values, fields, mapGroups]);
207
+ const derivedGroups = GroupsFromDefaults(defaultValue);
208
+ setMapGroups(derivedGroups);
258
209
 
210
+ resetFresh();
211
+ } else {
212
+ setDefaultValues(null);
213
+ setMapGroups({});
214
+ resetFresh();
215
+ }
216
+ }, [defaultValue]);
259
217
 
260
218
  const generateColClass = (col: string | number) => String(col).split(" ").map((c) => (c.includes(":") ? `${c.replace(":", ":col-span-")}` : `col-span-${c}`)).join(" ");
261
219
 
@@ -282,61 +240,67 @@ export function FormSupervisionComponent({
282
240
 
283
241
  if (form?.onHide?.(values)) return null;
284
242
 
285
- const ws = watchState[name];
286
- if (ws?.hidden) return null;
287
-
288
243
  if (inputType === "cluster") {
289
- const { name: mapName, fields: innerForms, label, tip, wrap, className, min = 0 } = form.construction as ClusterConstruction;
244
+ const { name: mapName, fields: innerForms, label, tip, wrap, className } = form.construction as ClusterConstruction;
290
245
 
291
246
  const groupKey = prefix ? `${prefix}.${mapName}` : mapName;
292
- const group = mapGroups[groupKey] || Array.from({ length: Math.max(min, 1) }, (_, i) => i);
293
-
294
- const addGroup = () => setMapGroups((prev) => ({ ...prev, [groupKey]: [...group, group.length > 0 ? Math.max(...group) + 1 : 0] }));
295
-
296
- const removeGroup = (gIndex: number) => {
297
- setMapGroups((prev) => ({ ...prev, [groupKey]: group.filter((g) => g !== gIndex) }));
298
-
299
- unregisterPrefix(`${groupKey}[${gIndex}]`);
247
+ const group = mapGroups[groupKey] || [0];
248
+
249
+ const addGroup = () => setMapGroups((prev) => ({ ...prev, [groupKey]: [...group, group.length] }));
250
+
251
+ const removeGroup = (index: number) => {
252
+ const filteredGroup = group.filter((_, i) => i !== index);
253
+ const newGroup = filteredGroup.map((_, i) => i);
254
+
255
+ setMapGroups((prev) => ({ ...prev, [groupKey]: newGroup }));
256
+
257
+ let updatedValues = values.filter((v) => {
258
+ const n = String(v?.name || "");
259
+ if (!n) return true;
260
+ if (n.startsWith(`${groupKey}[${index}]`) || n.startsWith(`${groupKey}.${index}.`)) return false;
261
+ return true;
262
+ });
263
+
264
+ const regex = new RegExp(`${groupKey}\\[(\\d+)\\]`, 'g');
265
+ updatedValues = updatedValues.map((v) => {
266
+ let name = v.name;
267
+ name = name.replace(regex, (match: string, oldIdx: string) => {
268
+ const oldIndex = parseInt(oldIdx, 10);
269
+ const newIndex = newGroup.indexOf(oldIndex);
270
+ return newIndex >= 0 ? `${groupKey}[${newIndex}]` : match;
271
+ });
272
+ return { ...v, name };
273
+ });
274
+
275
+ setValues(updatedValues);
300
276
  };
301
277
 
302
278
  return (
303
279
  <div key={key} className={cn("flex flex-col gap-4", generateColClass(form.col || "12"))}>
304
280
  {group.map((gIndex) => (
305
- <div
306
- key={gIndex}
307
- className={cn(
308
- "form-supervision-cluster-item",
309
- wrap && "form-supervision-cluster-item-wrapped",
310
- className
311
- )}
312
- >
281
+ <div key={gIndex} className={cn("relative pr-8", wrap && "p-4 rounded border", className)}>
313
282
  {label && <p className="input-label">{label} {gIndex + 1}</p>}
314
- {tip && <small className="input-tip">{tip}</small>}
283
+ {tip && <small className={cn("input-tip")}>{tip}</small>}
315
284
  {(label || tip) && <div className="mb-2"></div>}
316
285
 
317
- <div className="form-supervision-cluster-grid">
286
+ <div className="w-full grid grid-cols-12 gap-4">
318
287
  {innerForms.map((inner, i) => renderInput(inner, i, `${mapName}[${gIndex}]`))}
319
288
  </div>
320
289
 
321
- {group.length > min && (
322
- <ButtonComponent
323
- icon={faTimes}
324
- paint="danger"
325
- variant="outline"
326
- size="xs"
327
- className={cn(
328
- "form-supervision-cluster-remove-btn",
329
- wrap && "form-supervision-cluster-remove-btn-wrapped"
330
- )}
331
- onClick={() => removeGroup(gIndex)}
332
- />
333
- )}
290
+ <ButtonComponent
291
+ icon={"solid/times"}
292
+ paint="danger"
293
+ variant="outline"
294
+ size="xs"
295
+ className={cn("absolute top-10 right-2 translate-x-[50%] -translate-y-[50%]", wrap && "translate-x-0 -translate-y-0 top-1 right-1")}
296
+ onClick={() => removeGroup(gIndex)}
297
+ />
334
298
  </div>
335
299
  ))}
336
300
 
337
301
  <div>
338
302
  <ButtonComponent
339
- icon={faPlus}
303
+ icon={"solid/plus"}
340
304
  label={`Tambah ${label || mapName}`}
341
305
  variant="outline"
342
306
  size="sm"
@@ -362,8 +326,7 @@ export function FormSupervisionComponent({
362
326
  <Component
363
327
  {...(form.construction as any)}
364
328
  {...formControl(name)}
365
- disabled={ws?.disabled}
366
- readOnly={ws?.readonly}
329
+ // autoFocus={key === 0}
367
330
  />
368
331
  </div>
369
332
  );
@@ -371,55 +334,71 @@ export function FormSupervisionComponent({
371
334
 
372
335
  return (
373
336
  <>
374
- {title && <h4 className={cn("form-supervision-title", pcn<CT>(className, "title"))}>{title}</h4>}
375
-
376
- <form className={cn("form-supervision-base", pcn<CT>(className, "base"))} onSubmit={submit}>
377
- {fresh && fields.map((f, i) => renderInput(f, i))}
337
+ {title && <h4 className={cn("text-lg font-semibold mb-4", pcn<CT>(className, "title"))}>{title}</h4>}
378
338
 
379
- <div className="col-span-12">
380
- {footerControl?.({ loading }) || (
381
- <div className="form-supervision-footer">
382
- <ButtonComponent
383
- type="submit"
384
- label="Simpan"
385
- icon={faSave}
386
- loading={loading}
387
- className={pcn<CT>(className, "submit")}
388
- />
389
- </div>
390
- )}
339
+ {modal == "success" ? (
340
+ <div className="flex flex-col items-center justify-center h-full py-6 transition-all duration-300 animate-intro-down">
341
+ <div className="w-12 h-12 rounded-full bg-blue-50 flex items-center justify-center">
342
+ <Icon icon={"solid/check"} className="text-primary text-2xl" />
343
+ </div>
344
+ <p className="text-primary text-lg font-semibold mt-4">{successMessage || "Berhasil disimpan!"}</p>
391
345
  </div>
392
- </form>
346
+ ) : (
347
+ <form id={id} className={cn("flex flex-col h-full pb-8")} onSubmit={submit}>
348
+ <div className={cn("grid grid-cols-12 gap-4 flex-1 content-start", pcn<CT>(className, "base"))}>
349
+ {fresh && fields.map((f, i) => renderInput(f, i))}
350
+
351
+ {!footerControl && (
352
+ <div className="hidden md:block col-span-12">
353
+ <div className="flex justify-end mt-4">
354
+ <ButtonComponent
355
+ type="submit"
356
+ label={l.base?.save ? l.base.save() : "Save"}
357
+ icon={"solid/save"}
358
+ loading={loading}
359
+ className={pcn<CT>(className, "submit")}
360
+ />
361
+ </div>
362
+ </div>
363
+ )}
364
+
365
+ {!footerControl && (
366
+ <div className={"md:hidden col-span-12 mt-4"}>
367
+ <ButtonComponent
368
+ label={l.base?.save ? l.base.save() : "Save"}
369
+ icon={"solid/save"}
370
+ type="submit"
371
+ loading={loading}
372
+ block
373
+ className={pcn<CT>(className, "submit")}
374
+ />
375
+ </div>
376
+ )}
377
+
378
+ {footerControl && (
379
+ <div className="col-span-12">{footerControl?.({ loading })}</div>
380
+ )}
381
+
382
+ {modal == "failed" && (
383
+ <div className="col-span-12 mt-4 w-full p-4 rounded-sm border border-danger bg-light-danger flex gap-4 items-center">
384
+ <div>
385
+ <div className="w-10 h-10 rounded-full bg-danger/20 flex items-center justify-center">
386
+ <Icon icon={"solid/exclamation-triangle"} className="text-danger text-lg" />
387
+ </div>
388
+ </div>
389
+ <p className="text-danger text-sm font-semibold">{"Terjadi Masalah, Coba ulangi lagi!"}</p>
390
+ </div>
391
+ )}
392
+ </div>
393
+ </form>
394
+ )}
393
395
 
394
396
  <ModalConfirmComponent
395
397
  show={confirm.show}
396
398
  onClose={() => confirm.onClose()}
397
- icon={faQuestionCircle}
398
- title="Yakin"
399
+ title={l.base?.confirmTitle ? l.base.confirmTitle() : "Yakin"}
399
400
  submitControl={{ onSubmit: () => confirm?.onConfirm(), paint: "primary" }}
400
- >
401
- <p className="form-supervision-confirm-text">Yakin semua data sudah benar?</p>
402
- </ModalConfirmComponent>
403
-
404
- <ToastComponent
405
- show={modal == "failed"}
406
- onClose={() => setModal(false)}
407
- title="Gagal"
408
- className="form-supervision-toast-error header::text-danger"
409
- >
410
- <p className="form-supervision-toast-text">
411
- Data gagal disimpan, cek data dan koneksi internet lalu coba kembali!
412
- </p>
413
- </ToastComponent>
414
-
415
- <ToastComponent
416
- show={modal == "success"}
417
- onClose={() => setModal(false)}
418
- title="Berhasil"
419
- className="form-supervision-toast-success header::text-success"
420
- >
421
- <p className="form-supervision-toast-text">Data berhasil disimpan!</p>
422
- </ToastComponent>
401
+ />
423
402
  </>
424
403
  );
425
404
  }