@skalfa/skalfa-app-core 1.0.0
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/api.util.d.ts +49 -0
- package/dist/api.util.js +125 -0
- package/dist/auth.util.d.ts +10 -0
- package/dist/auth.util.js +37 -0
- package/dist/cavity.util.d.ts +20 -0
- package/dist/cavity.util.js +124 -0
- package/dist/cn.util.d.ts +3 -0
- package/dist/cn.util.js +45 -0
- package/dist/commands/barrels.d.ts +1 -0
- package/dist/commands/barrels.js +22 -0
- package/dist/commands/blueprint.d.ts +3 -0
- package/dist/commands/blueprint.js +306 -0
- package/dist/commands/light.d.ts +1 -0
- package/dist/commands/light.js +16 -0
- package/dist/commands/logger.d.ts +10 -0
- package/dist/commands/logger.js +36 -0
- package/dist/commands/use-pdf.d.ts +1 -0
- package/dist/commands/use-pdf.js +17 -0
- package/dist/conversion.util.d.ts +10 -0
- package/dist/conversion.util.js +53 -0
- package/dist/encryption.util.d.ts +6 -0
- package/dist/encryption.util.js +56 -0
- package/dist/form.util.d.ts +87 -0
- package/dist/form.util.js +294 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +79 -0
- package/dist/langs/index.d.ts +1 -0
- package/dist/langs/index.js +1 -0
- package/dist/langs/validation.langs.d.ts +17 -0
- package/dist/langs/validation.langs.js +17 -0
- package/dist/registry/index.d.ts +19 -0
- package/dist/registry/index.js +10 -0
- package/dist/resource.util.d.ts +36 -0
- package/dist/resource.util.js +81 -0
- package/dist/shortcut.util.d.ts +12 -0
- package/dist/shortcut.util.js +22 -0
- package/dist/table.util.d.ts +51 -0
- package/dist/table.util.js +140 -0
- package/dist/validation.util.d.ts +18 -0
- package/dist/validation.util.js +150 -0
- package/package.json +47 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { useRouter, useSearchParams } from "next/navigation";
|
|
4
|
+
import LZString from "lz-string";
|
|
5
|
+
import { conversion } from "./conversion.util";
|
|
6
|
+
import { useResource } from "./resource.util";
|
|
7
|
+
export const useTable = (fetchControl, id = "", title = "", urlParam) => {
|
|
8
|
+
const [state, setState] = useState({});
|
|
9
|
+
const router = useRouter();
|
|
10
|
+
const searchParams = useSearchParams();
|
|
11
|
+
// ======================
|
|
12
|
+
// ## Table state key
|
|
13
|
+
// ======================
|
|
14
|
+
const getTableKey = () => id || (title ? conversion.strSlug(title) : null) || fetchControl.path || fetchControl.store || "";
|
|
15
|
+
const tableKey = getTableKey();
|
|
16
|
+
// ======================
|
|
17
|
+
// ## Parse state url
|
|
18
|
+
// ======================
|
|
19
|
+
const getParamsFromUrl = () => {
|
|
20
|
+
if (typeof urlParam == "object" && urlParam?.compressed) {
|
|
21
|
+
const t = searchParams.get(tableKey ? `${tableKey}.t` : "t");
|
|
22
|
+
if (!t)
|
|
23
|
+
return {};
|
|
24
|
+
const decoded = LZString.decompressFromEncodedURIComponent(t);
|
|
25
|
+
return decoded ? JSON.parse(decoded) : {};
|
|
26
|
+
}
|
|
27
|
+
const params = {};
|
|
28
|
+
const prefix = tableKey ? `${tableKey}.` : "";
|
|
29
|
+
searchParams.forEach((value, key) => {
|
|
30
|
+
if (!key.startsWith(prefix))
|
|
31
|
+
return;
|
|
32
|
+
const shortKey = key.slice(prefix.length);
|
|
33
|
+
try {
|
|
34
|
+
params[shortKey] = JSON.parse(value);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
params[shortKey] = value;
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
return params;
|
|
41
|
+
};
|
|
42
|
+
// =======================
|
|
43
|
+
// ## Update url state
|
|
44
|
+
// =======================
|
|
45
|
+
const updateUrlParams = (params) => {
|
|
46
|
+
const url = new URL(window.location.href);
|
|
47
|
+
if (typeof urlParam == "object" && urlParam?.compressed) {
|
|
48
|
+
const encoded = LZString.compressToEncodedURIComponent(JSON.stringify(params));
|
|
49
|
+
url.searchParams.set(tableKey ? `${tableKey}.t` : "t", encoded);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
const prefix = tableKey ? `${tableKey}.` : "";
|
|
53
|
+
for (const key of Array.from(url.searchParams.keys())) {
|
|
54
|
+
if (key.startsWith(prefix))
|
|
55
|
+
url.searchParams.delete(key);
|
|
56
|
+
}
|
|
57
|
+
Object.entries(params || {}).forEach(([key, value]) => {
|
|
58
|
+
const paramKey = `${prefix}${key}`;
|
|
59
|
+
if (value === undefined || value === null || value === "") {
|
|
60
|
+
url.searchParams.delete(paramKey);
|
|
61
|
+
}
|
|
62
|
+
else if (typeof value === "object") {
|
|
63
|
+
url.searchParams.set(paramKey, JSON.stringify(value));
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
url.searchParams.set(paramKey, String(value));
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
router.replace(url.pathname + "?" + url.searchParams.toString(), { scroll: false });
|
|
71
|
+
};
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (state.params && urlParam)
|
|
74
|
+
updateUrlParams(state.params);
|
|
75
|
+
}, [state.params]);
|
|
76
|
+
// ===========================
|
|
77
|
+
// ## get url state
|
|
78
|
+
// ===========================
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
if (urlParam) {
|
|
81
|
+
const params = getParamsFromUrl();
|
|
82
|
+
setState((prev) => ({ ...prev, params }));
|
|
83
|
+
}
|
|
84
|
+
}, []);
|
|
85
|
+
// ==========================
|
|
86
|
+
// ## Fetch api
|
|
87
|
+
// ==========================
|
|
88
|
+
const { loading, data, reset } = useResource({
|
|
89
|
+
...fetchControl,
|
|
90
|
+
method: "GET",
|
|
91
|
+
params: {
|
|
92
|
+
...state.params,
|
|
93
|
+
...fetchControl.params
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
// ==========================
|
|
97
|
+
// ## Setter helper
|
|
98
|
+
// ==========================
|
|
99
|
+
const setParam = (key, value) => setState((prev) => ({ ...prev, params: { ...prev.params, [key]: value } }));
|
|
100
|
+
const setSelected = (selected) => setState((prev) => ({ ...prev, selected }));
|
|
101
|
+
const setChecks = (checks) => setState((prev) => ({ ...prev, checks }));
|
|
102
|
+
const setFocus = (focus) => setState((prev) => ({ ...prev, focus }));
|
|
103
|
+
// ==========================
|
|
104
|
+
// ## Table Control
|
|
105
|
+
// ==========================
|
|
106
|
+
const tableControl = {
|
|
107
|
+
loading: loading,
|
|
108
|
+
sortBy: state?.params?.sort,
|
|
109
|
+
onChangeSortBy: (e) => setParam('sort', e),
|
|
110
|
+
search: state?.params?.search,
|
|
111
|
+
onChangeSearch: (e) => setParam('search', e),
|
|
112
|
+
filter: state?.params?.filter,
|
|
113
|
+
onChangeFilter: (e) => setParam('filter', e),
|
|
114
|
+
onRefresh: () => reset(),
|
|
115
|
+
pagination: {
|
|
116
|
+
totalRow: data?.total_row,
|
|
117
|
+
page: state?.params?.page || 1,
|
|
118
|
+
paginate: state?.params?.paginate || 10,
|
|
119
|
+
onChange: (_, paginate, page) => {
|
|
120
|
+
setParam('paginate', paginate);
|
|
121
|
+
setParam('page', page);
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
return {
|
|
126
|
+
tableKey,
|
|
127
|
+
data,
|
|
128
|
+
reset,
|
|
129
|
+
loading,
|
|
130
|
+
params: state.params,
|
|
131
|
+
setParam,
|
|
132
|
+
focus: state.focus,
|
|
133
|
+
setFocus,
|
|
134
|
+
selected: state.selected,
|
|
135
|
+
setSelected: setSelected,
|
|
136
|
+
checks: state.checks,
|
|
137
|
+
setChecks: setChecks,
|
|
138
|
+
tableControl,
|
|
139
|
+
};
|
|
140
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type Rule = "required" | "string" | "numeric" | "boolean" | "email" | "url" | "date" | "confirmed" | `min:` | `min:${number}` | `max:` | `max:${number}` | `between:` | `between:${number},${number}` | `in:` | `in:${string}` | `not_in:` | `not_in:${string}` | `same:` | `same:${string}` | `different:` | `different:${string}` | `regex:` | `regex:${string}` | `unique:` | `unique:${string},${string}` | `exists:` | `exists:${string},${string}`;
|
|
2
|
+
export type ValidationRules = Rule[] | string;
|
|
3
|
+
export type ValidationHelperPropsType = {
|
|
4
|
+
value: string | string[] | number | number[] | Date | Date[] | File | File[] | null | object | boolean | (string | number)[];
|
|
5
|
+
rules?: ValidationRules;
|
|
6
|
+
};
|
|
7
|
+
export type ValidationHelperResults = {
|
|
8
|
+
valid: boolean;
|
|
9
|
+
message: string;
|
|
10
|
+
};
|
|
11
|
+
export declare const validation: {
|
|
12
|
+
normalizeRules: (rules?: Rule[] | string) => Rule[];
|
|
13
|
+
check: ({ value, rules }: ValidationHelperPropsType) => ValidationHelperResults;
|
|
14
|
+
hasRules: (rules?: Rule[] | string, ruleName?: string | string[]) => boolean;
|
|
15
|
+
getRules: (rules: Rule[] | string, ruleName: string) => string | undefined;
|
|
16
|
+
};
|
|
17
|
+
export declare const useValidation: (value?: any, rules?: Rule[] | string, includes?: string, sleep?: boolean) => [string, (message: string) => void];
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import validator from "validator";
|
|
4
|
+
import { validationLangs } from "./langs";
|
|
5
|
+
// ==========================>
|
|
6
|
+
// ## Validation core
|
|
7
|
+
// ==========================>
|
|
8
|
+
export const validation = {
|
|
9
|
+
// =========================>
|
|
10
|
+
// ## Normalize rules (string | array)
|
|
11
|
+
// =========================>
|
|
12
|
+
normalizeRules: (rules) => {
|
|
13
|
+
if (!rules)
|
|
14
|
+
return [];
|
|
15
|
+
if (Array.isArray(rules))
|
|
16
|
+
return rules;
|
|
17
|
+
return rules.split("|");
|
|
18
|
+
},
|
|
19
|
+
// =========================>
|
|
20
|
+
// ## Check value match of rules
|
|
21
|
+
// =========================>
|
|
22
|
+
check: ({ value, rules }) => {
|
|
23
|
+
const parsedRules = validation.normalizeRules(rules);
|
|
24
|
+
const strValue = String(value ?? "").trim();
|
|
25
|
+
for (const rule of parsedRules) {
|
|
26
|
+
const [name, param] = rule.split(":");
|
|
27
|
+
switch (name) {
|
|
28
|
+
// === BASIC ===
|
|
29
|
+
case "required":
|
|
30
|
+
if (!value || (Array.isArray(value) && value.length === 0)) {
|
|
31
|
+
return { valid: false, message: validationLangs.required };
|
|
32
|
+
}
|
|
33
|
+
break;
|
|
34
|
+
case "numeric":
|
|
35
|
+
if (!validator.isNumeric(strValue)) {
|
|
36
|
+
return { valid: false, message: validationLangs.numeric || "Harus berupa angka" };
|
|
37
|
+
}
|
|
38
|
+
break;
|
|
39
|
+
case "email":
|
|
40
|
+
if (!validator.isEmail(strValue)) {
|
|
41
|
+
return { valid: false, message: validationLangs.email };
|
|
42
|
+
}
|
|
43
|
+
break;
|
|
44
|
+
case "url":
|
|
45
|
+
if (!validator.isURL(strValue)) {
|
|
46
|
+
return { valid: false, message: validationLangs.url || "Harus berupa URL yang valid" };
|
|
47
|
+
}
|
|
48
|
+
break;
|
|
49
|
+
case "date":
|
|
50
|
+
if (!validator.isDate(strValue)) {
|
|
51
|
+
return { valid: false, message: "Tanggal tidak valid" };
|
|
52
|
+
}
|
|
53
|
+
break;
|
|
54
|
+
// === LENGTH ===
|
|
55
|
+
case "min": {
|
|
56
|
+
const min = parseInt(param || "0");
|
|
57
|
+
if (!validator.isLength(strValue, { min })) {
|
|
58
|
+
return { valid: false, message: validationLangs.min.replace(/@min/g, String(min)) };
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case "max": {
|
|
63
|
+
const max = parseInt(param || "0");
|
|
64
|
+
if (!validator.isLength(strValue, { max })) {
|
|
65
|
+
return { valid: false, message: validationLangs.max.replace(/@max/g, String(max)) };
|
|
66
|
+
}
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
case "between": {
|
|
70
|
+
const [minVal, maxVal] = (param || "0,0").split(",").map(Number);
|
|
71
|
+
if (!validator.isLength(strValue, { min: minVal, max: maxVal })) {
|
|
72
|
+
return {
|
|
73
|
+
valid: false,
|
|
74
|
+
message: validationLangs.min_max
|
|
75
|
+
.replace(/@min/g, String(minVal))
|
|
76
|
+
.replace(/@max/g, String(maxVal)),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
// === IN / NOT IN ===
|
|
82
|
+
case "in": {
|
|
83
|
+
const allowed = (param || "").split(",");
|
|
84
|
+
if (!allowed.includes(strValue)) {
|
|
85
|
+
return { valid: false, message: `${validationLangs.in} ${allowed.join(", ")}` };
|
|
86
|
+
}
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
case "not_in": {
|
|
90
|
+
const notAllowed = (param || "").split(",");
|
|
91
|
+
if (notAllowed.includes(strValue)) {
|
|
92
|
+
return { valid: false, message: `${validationLangs.not_in} ${notAllowed.join(", ")}` };
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
// === REGEX ===
|
|
97
|
+
case "regex":
|
|
98
|
+
try {
|
|
99
|
+
const pattern = new RegExp(param || "");
|
|
100
|
+
if (!pattern.test(strValue)) {
|
|
101
|
+
return { valid: false, message: validationLangs.regex || "Format tidak sesuai" };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return { valid: false, message: "Regex rule tidak valid" };
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return { valid: true, message: "" };
|
|
111
|
+
},
|
|
112
|
+
// =========================>
|
|
113
|
+
// ## Check has rules
|
|
114
|
+
// =========================>
|
|
115
|
+
hasRules: (rules, ruleName) => {
|
|
116
|
+
if (!rules || !ruleName)
|
|
117
|
+
return false;
|
|
118
|
+
const parsed = validation.normalizeRules(rules).map(r => r.split(":")[0]);
|
|
119
|
+
if (Array.isArray(ruleName))
|
|
120
|
+
return ruleName.every(r => parsed.includes(r));
|
|
121
|
+
return parsed.includes(ruleName);
|
|
122
|
+
},
|
|
123
|
+
// =========================>
|
|
124
|
+
// ## get rule param
|
|
125
|
+
// =========================>
|
|
126
|
+
getRules: (rules, ruleName) => {
|
|
127
|
+
const found = validation.normalizeRules(rules).find(r => r.startsWith(ruleName + ":"));
|
|
128
|
+
return found ? found.split(":")[1] : undefined;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
// =========================>
|
|
132
|
+
// ## Check validation Hook
|
|
133
|
+
// =========================>
|
|
134
|
+
export const useValidation = (value = "", rules = "", includes = "", sleep = false) => {
|
|
135
|
+
const [message, setMessage] = useState("");
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
if (rules && !sleep) {
|
|
138
|
+
const { valid, message } = validation.check({ value, rules });
|
|
139
|
+
setMessage(valid ? "" : message);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
setMessage("");
|
|
143
|
+
}
|
|
144
|
+
}, [value, rules, sleep]);
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
if (includes)
|
|
147
|
+
setMessage(includes);
|
|
148
|
+
}, [includes]);
|
|
149
|
+
return [message, setMessage];
|
|
150
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@skalfa/skalfa-app-core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Core utility functions for the Skalfa Next.js frontend framework.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc -p tsconfig.json"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"skalfa",
|
|
15
|
+
"frontend",
|
|
16
|
+
"core"
|
|
17
|
+
],
|
|
18
|
+
"author": "",
|
|
19
|
+
"license": "UNLICENSED",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"axios": "^1.12.0",
|
|
22
|
+
"clsx": "^2.1.1",
|
|
23
|
+
"crypto-js": "^4.2.0",
|
|
24
|
+
"js-cookie": "^3.0.5",
|
|
25
|
+
"lz-string": "^1.5.0",
|
|
26
|
+
"moment": "^2.30.1",
|
|
27
|
+
"tailwind-merge": "^3.0.2",
|
|
28
|
+
"validator": "^13.15.22"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"next": "^15.0.0 || ^16.0.0",
|
|
32
|
+
"react": "^19.0.0",
|
|
33
|
+
"react-dom": "^19.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/crypto-js": "^4.2.2",
|
|
37
|
+
"@types/js-cookie": "^3.0.6",
|
|
38
|
+
"@types/node": "^20.0.0",
|
|
39
|
+
"@types/react": "^19.0.0",
|
|
40
|
+
"@types/react-dom": "^19.0.0",
|
|
41
|
+
"@types/validator": "^13.15.4",
|
|
42
|
+
"next": "16.1.1",
|
|
43
|
+
"react": "^19.2.1",
|
|
44
|
+
"react-dom": "^19.2.1",
|
|
45
|
+
"typescript": "^5.0.0"
|
|
46
|
+
}
|
|
47
|
+
}
|