@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.
Files changed (41) hide show
  1. package/dist/api.util.d.ts +49 -0
  2. package/dist/api.util.js +125 -0
  3. package/dist/auth.util.d.ts +10 -0
  4. package/dist/auth.util.js +37 -0
  5. package/dist/cavity.util.d.ts +20 -0
  6. package/dist/cavity.util.js +124 -0
  7. package/dist/cn.util.d.ts +3 -0
  8. package/dist/cn.util.js +45 -0
  9. package/dist/commands/barrels.d.ts +1 -0
  10. package/dist/commands/barrels.js +22 -0
  11. package/dist/commands/blueprint.d.ts +3 -0
  12. package/dist/commands/blueprint.js +306 -0
  13. package/dist/commands/light.d.ts +1 -0
  14. package/dist/commands/light.js +16 -0
  15. package/dist/commands/logger.d.ts +10 -0
  16. package/dist/commands/logger.js +36 -0
  17. package/dist/commands/use-pdf.d.ts +1 -0
  18. package/dist/commands/use-pdf.js +17 -0
  19. package/dist/conversion.util.d.ts +10 -0
  20. package/dist/conversion.util.js +53 -0
  21. package/dist/encryption.util.d.ts +6 -0
  22. package/dist/encryption.util.js +56 -0
  23. package/dist/form.util.d.ts +87 -0
  24. package/dist/form.util.js +294 -0
  25. package/dist/index.d.ts +27 -0
  26. package/dist/index.js +79 -0
  27. package/dist/langs/index.d.ts +1 -0
  28. package/dist/langs/index.js +1 -0
  29. package/dist/langs/validation.langs.d.ts +17 -0
  30. package/dist/langs/validation.langs.js +17 -0
  31. package/dist/registry/index.d.ts +19 -0
  32. package/dist/registry/index.js +10 -0
  33. package/dist/resource.util.d.ts +36 -0
  34. package/dist/resource.util.js +81 -0
  35. package/dist/shortcut.util.d.ts +12 -0
  36. package/dist/shortcut.util.js +22 -0
  37. package/dist/table.util.d.ts +51 -0
  38. package/dist/table.util.js +140 -0
  39. package/dist/validation.util.d.ts +18 -0
  40. package/dist/validation.util.js +150 -0
  41. 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
+ }