@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,306 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { logger } from "./logger";
|
|
4
|
+
import { conversion } from "../conversion.util";
|
|
5
|
+
const renderJS = (value, indent = 0) => {
|
|
6
|
+
const pad = " ".repeat(indent);
|
|
7
|
+
if (Array.isArray(value)) {
|
|
8
|
+
if (!value.length)
|
|
9
|
+
return "[]";
|
|
10
|
+
if (value.every(v => typeof v === "string")) {
|
|
11
|
+
return `[${value.map(v => JSON.stringify(v)).join(", ")}]`;
|
|
12
|
+
}
|
|
13
|
+
return `[\n${value
|
|
14
|
+
.map(v => pad + " " + renderJS(v, indent + 2))
|
|
15
|
+
.join(",\n")}\n${pad}]`;
|
|
16
|
+
}
|
|
17
|
+
if (value && typeof value === "object") {
|
|
18
|
+
const entries = Object.entries(value);
|
|
19
|
+
if (!entries.length)
|
|
20
|
+
return "{}";
|
|
21
|
+
return `{\n${entries
|
|
22
|
+
.map(([k, v]) => `${pad} ${k}: ${renderJS(v, indent + 2)}`)
|
|
23
|
+
.join(",\n")}\n${pad}}`;
|
|
24
|
+
}
|
|
25
|
+
if (typeof value === "string")
|
|
26
|
+
return JSON.stringify(value);
|
|
27
|
+
return String(value);
|
|
28
|
+
};
|
|
29
|
+
function extractValidationArray(def = "") {
|
|
30
|
+
const rules = [];
|
|
31
|
+
if (def.includes("required"))
|
|
32
|
+
rules.push("required");
|
|
33
|
+
if (def.includes("email"))
|
|
34
|
+
rules.push("email");
|
|
35
|
+
if (def.includes("url"))
|
|
36
|
+
rules.push("url");
|
|
37
|
+
const min = def.match(/min,(\d+)/);
|
|
38
|
+
if (min)
|
|
39
|
+
rules.push(`min:${min[1]}`);
|
|
40
|
+
const max = def.match(/max,(\d+)/);
|
|
41
|
+
if (max)
|
|
42
|
+
rules.push(`max:${max[1]}`);
|
|
43
|
+
return rules;
|
|
44
|
+
}
|
|
45
|
+
function extractFormType(rules) {
|
|
46
|
+
const rule = rules.find(r => r.startsWith("form:"));
|
|
47
|
+
return rule ? rule.replace("form:", "") : undefined;
|
|
48
|
+
}
|
|
49
|
+
function inferFormType(pageDef = "", modelDef = "") {
|
|
50
|
+
const explicit = pageDef.split("|");
|
|
51
|
+
if (explicit && explicit[0] && explicit[0] != "text")
|
|
52
|
+
return explicit[0];
|
|
53
|
+
if (modelDef.includes("type:integer") || modelDef.includes("type:float")) {
|
|
54
|
+
return "number";
|
|
55
|
+
}
|
|
56
|
+
if (modelDef.includes("type:date"))
|
|
57
|
+
return "date";
|
|
58
|
+
if (modelDef.includes("type:datetime"))
|
|
59
|
+
return "datetime";
|
|
60
|
+
if (modelDef.includes("type:image"))
|
|
61
|
+
return "image";
|
|
62
|
+
return "default";
|
|
63
|
+
}
|
|
64
|
+
function parseFeatures(features) {
|
|
65
|
+
const controlBar = [];
|
|
66
|
+
const action = [];
|
|
67
|
+
if (!features) {
|
|
68
|
+
return { controlBar: ["CREATE"], action: ["EDIT", "DELETE"] };
|
|
69
|
+
}
|
|
70
|
+
const list = features.split(" ");
|
|
71
|
+
if (list.includes("create"))
|
|
72
|
+
controlBar.push("CREATE");
|
|
73
|
+
controlBar.push("SEARCH", "SORT", "SELECTABLE");
|
|
74
|
+
if (list.includes("import"))
|
|
75
|
+
controlBar.push("IMPORT");
|
|
76
|
+
if (list.includes("export"))
|
|
77
|
+
controlBar.push("EXPORT");
|
|
78
|
+
if (list.includes("print"))
|
|
79
|
+
controlBar.push("PRINT");
|
|
80
|
+
if (list.includes("update") || list.includes("edit"))
|
|
81
|
+
action.push("EDIT");
|
|
82
|
+
if (list.includes("delete"))
|
|
83
|
+
action.push("DELETE");
|
|
84
|
+
if (list.includes("detail"))
|
|
85
|
+
action.push("DETAIL");
|
|
86
|
+
return { controlBar, action };
|
|
87
|
+
}
|
|
88
|
+
function parseModelSchema(schema = {}) {
|
|
89
|
+
const columns = [];
|
|
90
|
+
const forms = [];
|
|
91
|
+
const details = [];
|
|
92
|
+
for (const [field, def] of Object.entries(schema)) {
|
|
93
|
+
const label = conversion.strPascal(field, " ");
|
|
94
|
+
if (def.includes("selectable")) {
|
|
95
|
+
columns.push({ selector: field, label });
|
|
96
|
+
details.push({ label, item: field });
|
|
97
|
+
}
|
|
98
|
+
if (def.includes("fillable")) {
|
|
99
|
+
const fieldType = inferFormType("", def);
|
|
100
|
+
forms.push({
|
|
101
|
+
...(fieldType != "default" ? { type: fieldType } : {}),
|
|
102
|
+
construction: {
|
|
103
|
+
name: field,
|
|
104
|
+
label,
|
|
105
|
+
placeholder: "Masukkan " + label.toLowerCase(),
|
|
106
|
+
validations: extractValidationArray(def)
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return { columns, forms, details };
|
|
112
|
+
}
|
|
113
|
+
function resolvePath(page, controllers, model) {
|
|
114
|
+
if (page.path)
|
|
115
|
+
return page.path;
|
|
116
|
+
if (Array.isArray(controllers)) {
|
|
117
|
+
const match = controllers;
|
|
118
|
+
if (match)
|
|
119
|
+
return "/" + match[1];
|
|
120
|
+
}
|
|
121
|
+
return "/" + model.split("/").pop();
|
|
122
|
+
}
|
|
123
|
+
function parsePageSchema(pageSchema, modelSchema = {}) {
|
|
124
|
+
const columns = [];
|
|
125
|
+
const forms = [];
|
|
126
|
+
const details = [];
|
|
127
|
+
for (const [field, def] of Object.entries(pageSchema)) {
|
|
128
|
+
const rules = def.replace(/\|/g, " ").split(" ").filter(Boolean);
|
|
129
|
+
const defaultLabel = conversion.strPascal(field, " ");
|
|
130
|
+
const colLabelRule = rules.find(r => r.includes("column:label,"));
|
|
131
|
+
const colLabel = conversion.strPascal(colLabelRule ? colLabelRule.split(",")[1] : defaultLabel, " ");
|
|
132
|
+
const formLabelRule = rules.find(r => r.includes("form:label,"));
|
|
133
|
+
const formLabel = conversion.strPascal(formLabelRule ? formLabelRule.split(",")[1] : (colLabelRule ? colLabel : defaultLabel), " ");
|
|
134
|
+
const detailLabel = colLabelRule ? colLabel : (formLabelRule ? formLabel : defaultLabel);
|
|
135
|
+
const hasColumn = rules.some(r => r.includes("column:"));
|
|
136
|
+
const hasForm = rules.some(r => r.includes("form:")) || rules.every(r => !r.includes("column:"));
|
|
137
|
+
const hasDetail = rules.includes("detail");
|
|
138
|
+
if (hasColumn) {
|
|
139
|
+
columns.push({
|
|
140
|
+
selector: field,
|
|
141
|
+
label: colLabel,
|
|
142
|
+
...(rules.includes("column:sortable") || rules.includes("sortable") ? { sortable: true } : {})
|
|
143
|
+
});
|
|
144
|
+
if (!hasDetail)
|
|
145
|
+
details.push({ label: detailLabel, item: field });
|
|
146
|
+
}
|
|
147
|
+
if (hasForm) {
|
|
148
|
+
const typeRules = rules.filter(r => !r.startsWith("form:label,"));
|
|
149
|
+
const typeRule = extractFormType(typeRules);
|
|
150
|
+
let fieldType = inferFormType(typeRule, modelSchema[field] || "");
|
|
151
|
+
let selectPath = "";
|
|
152
|
+
const selectRule = rules.find(r => r.startsWith("select,") || r.startsWith("form:select,"));
|
|
153
|
+
if (selectRule) {
|
|
154
|
+
fieldType = "select";
|
|
155
|
+
selectPath = selectRule.split(",")[1];
|
|
156
|
+
}
|
|
157
|
+
if (typeRule === "check")
|
|
158
|
+
fieldType = "boolean";
|
|
159
|
+
if (typeRule === "currency")
|
|
160
|
+
fieldType = "currency";
|
|
161
|
+
if (typeRule === "image")
|
|
162
|
+
fieldType = "image";
|
|
163
|
+
if (typeRule === "date")
|
|
164
|
+
fieldType = "date";
|
|
165
|
+
if (typeRule === "time")
|
|
166
|
+
fieldType = "time";
|
|
167
|
+
let col;
|
|
168
|
+
const colRule = rules.find(r => /col,(\d+)/.test(r));
|
|
169
|
+
if (colRule) {
|
|
170
|
+
const n = Number(colRule.split(',').pop());
|
|
171
|
+
if (n >= 1 && n <= 12)
|
|
172
|
+
col = n;
|
|
173
|
+
}
|
|
174
|
+
forms.push({
|
|
175
|
+
...(fieldType != "default" && fieldType != "text" ? { type: fieldType } : {}),
|
|
176
|
+
...(col ? { col } : {}),
|
|
177
|
+
construction: {
|
|
178
|
+
name: field,
|
|
179
|
+
label: formLabel,
|
|
180
|
+
placeholder: "Masukkan " + formLabel.toLowerCase(),
|
|
181
|
+
validations: extractValidationArray(modelSchema[field] || ""),
|
|
182
|
+
...(selectPath ? { serverOptionControl: { path: selectPath } } : {}),
|
|
183
|
+
...(rules.includes("wrap") ? { wrap: true } : {})
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
if (hasDetail)
|
|
188
|
+
details.push({ label: detailLabel, item: field });
|
|
189
|
+
}
|
|
190
|
+
const formMap = new Map();
|
|
191
|
+
forms.forEach(f => formMap.set(f.construction.name, f));
|
|
192
|
+
const nestedForms = [];
|
|
193
|
+
forms.forEach(f => {
|
|
194
|
+
const name = f.construction.name;
|
|
195
|
+
if (name.includes(".")) {
|
|
196
|
+
const parts = name.split(".");
|
|
197
|
+
const selfName = parts.pop();
|
|
198
|
+
const parentName = parts.join(".");
|
|
199
|
+
const parent = formMap.get(parentName);
|
|
200
|
+
if (parent) {
|
|
201
|
+
if (!parent.type)
|
|
202
|
+
parent.type = "cluster";
|
|
203
|
+
if (!parent.construction.fields)
|
|
204
|
+
parent.construction.fields = [];
|
|
205
|
+
f.construction.name = selfName;
|
|
206
|
+
parent.construction.fields.push(f);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
nestedForms.push(f);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
nestedForms.push(f);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
return { columns, forms: nestedForms, details };
|
|
217
|
+
}
|
|
218
|
+
function loadBlueprintFiles(dir = "blueprints") {
|
|
219
|
+
const basePath = path.join(process.cwd(), dir);
|
|
220
|
+
if (!fs.existsSync(basePath)) {
|
|
221
|
+
throw new Error("Blueprint folder not found");
|
|
222
|
+
}
|
|
223
|
+
return fs.readdirSync(basePath)
|
|
224
|
+
.filter(f => f.endsWith(".blueprint.json"))
|
|
225
|
+
.map(file => {
|
|
226
|
+
const fullPath = path.join(basePath, file);
|
|
227
|
+
const content = JSON.parse(fs.readFileSync(fullPath, "utf-8"));
|
|
228
|
+
if (!Array.isArray(content)) {
|
|
229
|
+
throw new Error(`${file} must export array of blueprints`);
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
file: file.replace(".blueprint.json", ""),
|
|
233
|
+
blueprints: content,
|
|
234
|
+
};
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
const blueprintMarker = `// ============================================
|
|
238
|
+
// ## file THIS FILE IS AUTO-GENERATED BY BLUEPRINT
|
|
239
|
+
// ?? Blueprint : {{ blueprint }}
|
|
240
|
+
// !! If this comment is removed, blueprint engine WILL NOT override this file.
|
|
241
|
+
// ============================================
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
`;
|
|
245
|
+
// ===============================
|
|
246
|
+
// ## Main generator
|
|
247
|
+
// ===============================
|
|
248
|
+
export async function blueprint(options) {
|
|
249
|
+
const stub = fs.readFileSync(path.join(process.cwd(), "/utils/commands/stubs/table-blueprint.stub"), "utf-8");
|
|
250
|
+
const loaded = loadBlueprintFiles();
|
|
251
|
+
for (const file of loaded) {
|
|
252
|
+
for (const bp of file.blueprints) {
|
|
253
|
+
const pagesToGenerate = { ...(bp.pages || {}) };
|
|
254
|
+
for (const [key, val] of Object.entries(bp)) {
|
|
255
|
+
if (typeof val === "object" && val["schema"]) {
|
|
256
|
+
pagesToGenerate[key] = val;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
for (const [key, page] of Object.entries(pagesToGenerate)) {
|
|
260
|
+
const route = key;
|
|
261
|
+
const name = conversion.strPascal(route.split("/").pop());
|
|
262
|
+
if (options?.only && !options.only.includes(name))
|
|
263
|
+
continue;
|
|
264
|
+
const outDir = path.join(process.cwd(), "app", route);
|
|
265
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
266
|
+
const filePath = path.join(outDir, "page.tsx");
|
|
267
|
+
if (fs.existsSync(filePath)) {
|
|
268
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
269
|
+
if (!content.includes("AUTO-GENERATED BY BLUEPRINT")) {
|
|
270
|
+
logger.info(`Skip overridden file: ${filePath}`);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const schema = bp.schema ?? {};
|
|
275
|
+
const parsed = page?.schema ? parsePageSchema(page.schema, schema) : parseModelSchema(schema);
|
|
276
|
+
const { controlBar, action } = parseFeatures(page?.features);
|
|
277
|
+
const fetchPath = resolvePath(page, bp.controllers, bp.model);
|
|
278
|
+
let properties = `
|
|
279
|
+
fetchControl={{
|
|
280
|
+
path: "${fetchPath}"
|
|
281
|
+
}}
|
|
282
|
+
columnControl={${renderJS(parsed.columns, 8)}}
|
|
283
|
+
formControl={{
|
|
284
|
+
fields: ${renderJS(parsed.forms, 10)}
|
|
285
|
+
}}
|
|
286
|
+
`;
|
|
287
|
+
if (parsed.details) {
|
|
288
|
+
properties += ` detailControl={${renderJS(parsed.details, 8)}}\n`;
|
|
289
|
+
}
|
|
290
|
+
if (controlBar.length) {
|
|
291
|
+
properties += ` controlBar={${JSON.stringify(controlBar)}}\n`;
|
|
292
|
+
}
|
|
293
|
+
if (action.length) {
|
|
294
|
+
properties += ` actionControl={${JSON.stringify(action)}}\n`;
|
|
295
|
+
}
|
|
296
|
+
const content = stub
|
|
297
|
+
.replace(/{{ marker }}/g, blueprintMarker.replace(/{{ blueprint }}/g, file.file + ".blueprint.json"))
|
|
298
|
+
.replace(/{{ name }}/g, name)
|
|
299
|
+
.replace(/{{ title }}/g, name)
|
|
300
|
+
.replace(/{{ properties }}/g, properties);
|
|
301
|
+
fs.writeFileSync(path.join(outDir, "page.tsx"), content);
|
|
302
|
+
logger.info(`Generated: ${filePath}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { usePdf } from "./use-pdf";
|
|
3
|
+
import { blueprint } from "./blueprint";
|
|
4
|
+
import { logger } from "./logger";
|
|
5
|
+
const program = new Command();
|
|
6
|
+
program.name("light").description("Next Light CLI").version("1.0.0");
|
|
7
|
+
program.command("use-pdf").description("Copy pdf.worker.min.mjs ke folder public/").action(usePdf);
|
|
8
|
+
program.command("blueprint")
|
|
9
|
+
.option("-o, --only <names...>", "Run only specific blueprints")
|
|
10
|
+
.description("Generate blueprint")
|
|
11
|
+
.action(async (opts) => {
|
|
12
|
+
await blueprint({ only: opts.only });
|
|
13
|
+
logger.info("Success run all blueprints!");
|
|
14
|
+
process.exit(0);
|
|
15
|
+
});
|
|
16
|
+
program.parse();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const logger: {
|
|
2
|
+
start: (...msg: unknown[]) => void;
|
|
3
|
+
info: (...msg: unknown[]) => void;
|
|
4
|
+
error: (...msg: unknown[]) => void;
|
|
5
|
+
warning: (...msg: unknown[]) => void;
|
|
6
|
+
cavity: (...msg: unknown[]) => void;
|
|
7
|
+
cavityError: (...msg: unknown[]) => void;
|
|
8
|
+
socket: (...msg: unknown[]) => void;
|
|
9
|
+
socketError: (...msg: unknown[]) => void;
|
|
10
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const colors = {
|
|
2
|
+
default: "\x1b[0m", // default
|
|
3
|
+
start: "\x1b[32m", // green
|
|
4
|
+
info: "\x1b[36m", // cyan
|
|
5
|
+
error: "\x1b[31m", // red
|
|
6
|
+
warning: "\x1b[33m", // yellow
|
|
7
|
+
cavity: "\x1b[34m", // blue
|
|
8
|
+
cavityError: "\x1b[31m", // red
|
|
9
|
+
socket: "\x1b[35m", // magenta
|
|
10
|
+
socketError: "\x1b[31m", // red
|
|
11
|
+
};
|
|
12
|
+
const prefixes = {
|
|
13
|
+
start: "START",
|
|
14
|
+
info: "INFO",
|
|
15
|
+
error: "ERROR",
|
|
16
|
+
warning: "WARNING",
|
|
17
|
+
cavity: "CAVITY",
|
|
18
|
+
socket: "SOCKET",
|
|
19
|
+
cavityError: "CAVITY ERROR",
|
|
20
|
+
socketError: "SOCKET ERROR",
|
|
21
|
+
};
|
|
22
|
+
function log(type, ...msg) {
|
|
23
|
+
const color = colors[type];
|
|
24
|
+
const prefix = prefixes[type];
|
|
25
|
+
console.log(`${color}[${prefix}]${colors.default}`, ...msg);
|
|
26
|
+
}
|
|
27
|
+
export const logger = {
|
|
28
|
+
start: (...msg) => log("start", ...msg),
|
|
29
|
+
info: (...msg) => log("info", ...msg),
|
|
30
|
+
error: (...msg) => log("error", ...msg),
|
|
31
|
+
warning: (...msg) => log("warning", ...msg),
|
|
32
|
+
cavity: (...msg) => log("cavity", ...msg),
|
|
33
|
+
cavityError: (...msg) => log("cavityError", ...msg),
|
|
34
|
+
socket: (...msg) => log("socket", ...msg),
|
|
35
|
+
socketError: (...msg) => log("socketError", ...msg),
|
|
36
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function usePdf(): void;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import { logger } from "./logger";
|
|
5
|
+
export function usePdf() {
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
const projectRoot = path.join(__dirname, "..", "..");
|
|
9
|
+
const source = path.join(projectRoot, "node_modules", "pdfjs-dist", "legacy", "build", "pdf.worker.min.mjs");
|
|
10
|
+
const target = path.join(projectRoot, "public", "pdf.worker.min.mjs");
|
|
11
|
+
if (!fs.existsSync(source)) {
|
|
12
|
+
logger.error(`Gagal: pdf.worker.min.mjs tidak ditemukan.`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
fs.copyFileSync(source, target);
|
|
16
|
+
logger.info("Berhasil memindahkan worker ke public/pdf.worker.min.mjs");
|
|
17
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const conversion: {
|
|
2
|
+
strSnake(value: string, delimiter?: string): string;
|
|
3
|
+
strSlug(value: string, delimiter?: string): string;
|
|
4
|
+
strCamel(value: string, delimiter?: string): string;
|
|
5
|
+
strPascal(value: string, delimiter?: string): string;
|
|
6
|
+
strPlural(value: string): string;
|
|
7
|
+
strSingular(value: string): string;
|
|
8
|
+
currency: (value: number, locale?: string, currency?: string) => string;
|
|
9
|
+
date: (date: string, format?: string) => string;
|
|
10
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import moment from "moment";
|
|
2
|
+
export const conversion = {
|
|
3
|
+
// =============================>
|
|
4
|
+
// ## Conversion: String formatter
|
|
5
|
+
// =============================>
|
|
6
|
+
strSnake(value, delimiter = "_") {
|
|
7
|
+
return toWords(value).join(delimiter);
|
|
8
|
+
},
|
|
9
|
+
strSlug(value, delimiter = "-") {
|
|
10
|
+
return toWords(value).join(delimiter);
|
|
11
|
+
},
|
|
12
|
+
strCamel(value, delimiter = "") {
|
|
13
|
+
return toWords(value).map((w, i) => i === 0 ? w : w[0].toUpperCase() + w.slice(1)).join(delimiter);
|
|
14
|
+
},
|
|
15
|
+
strPascal(value, delimiter = "") {
|
|
16
|
+
return toWords(value).map(w => w[0].toUpperCase() + w.slice(1)).join(delimiter);
|
|
17
|
+
},
|
|
18
|
+
strPlural(value) {
|
|
19
|
+
const match = value.match(/^(.*?)([A-Za-z]+)$/);
|
|
20
|
+
if (!match)
|
|
21
|
+
return value;
|
|
22
|
+
const [, prefix, word] = match;
|
|
23
|
+
if (word.endsWith("y") && !/[aeiou]y$/i.test(word)) {
|
|
24
|
+
return prefix + word.slice(0, -1) + "ies";
|
|
25
|
+
}
|
|
26
|
+
if (!word.endsWith("s"))
|
|
27
|
+
return prefix + word + "s";
|
|
28
|
+
return value;
|
|
29
|
+
},
|
|
30
|
+
strSingular(value) {
|
|
31
|
+
return value
|
|
32
|
+
.replace(/[-_]/g, " ")
|
|
33
|
+
.replace(/\b\w/g, (l) => l.toUpperCase())
|
|
34
|
+
.replace(/\s+/g, "");
|
|
35
|
+
},
|
|
36
|
+
// ==============================>
|
|
37
|
+
// ## currency formatter
|
|
38
|
+
// ==============================>
|
|
39
|
+
currency: (value, locale = "id-ID", currency = "IDR") => { const val = Math.trunc(value); return new Intl.NumberFormat(locale, { style: "currency", currency, minimumFractionDigits: 0, maximumFractionDigits: 2 }).format(val); },
|
|
40
|
+
// ==============================>
|
|
41
|
+
// ## date formatter
|
|
42
|
+
// ==============================>
|
|
43
|
+
date: (date, format = "DD MMM YYYY") => moment(date).format(format),
|
|
44
|
+
};
|
|
45
|
+
function toWords(value) {
|
|
46
|
+
return value
|
|
47
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
48
|
+
.replace(/[_\-\s]+/g, " ")
|
|
49
|
+
.trim()
|
|
50
|
+
.toLowerCase()
|
|
51
|
+
.split(" ")
|
|
52
|
+
.filter(Boolean);
|
|
53
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import CryptoJS from "crypto-js";
|
|
2
|
+
export const encryption = {
|
|
3
|
+
// ==============================>
|
|
4
|
+
// ## Encryption data
|
|
5
|
+
// ==============================>
|
|
6
|
+
set: (data, key = process.env.NEXT_PUBLIC_APP_KEY || "", algorithm = "AES") => {
|
|
7
|
+
const text = typeof data === "string" ? data : JSON.stringify(data);
|
|
8
|
+
let encrypted;
|
|
9
|
+
switch (algorithm) {
|
|
10
|
+
case "AES":
|
|
11
|
+
encrypted = CryptoJS.AES.encrypt(text, key).toString();
|
|
12
|
+
break;
|
|
13
|
+
case "TripleDES":
|
|
14
|
+
encrypted = CryptoJS.TripleDES.encrypt(text, key).toString();
|
|
15
|
+
break;
|
|
16
|
+
case "SHA256":
|
|
17
|
+
encrypted = CryptoJS.SHA256(text).toString(CryptoJS.enc.Hex);
|
|
18
|
+
break;
|
|
19
|
+
case "SHA512":
|
|
20
|
+
encrypted = CryptoJS.SHA512(text).toString(CryptoJS.enc.Hex);
|
|
21
|
+
break;
|
|
22
|
+
case "MD5":
|
|
23
|
+
encrypted = CryptoJS.MD5(text).toString(CryptoJS.enc.Hex);
|
|
24
|
+
break;
|
|
25
|
+
default: throw new Error(`Unsupported algorithm: ${algorithm}`);
|
|
26
|
+
}
|
|
27
|
+
if (["SHA256", "SHA512", "MD5"].includes(algorithm))
|
|
28
|
+
return encrypted;
|
|
29
|
+
const encData = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(encrypted));
|
|
30
|
+
return encData;
|
|
31
|
+
},
|
|
32
|
+
// ==============================>
|
|
33
|
+
// ## Decryption data
|
|
34
|
+
// ==============================>
|
|
35
|
+
get: (data, key = process.env.NEXT_PUBLIC_APP_KEY || "", algorithm = "AES") => {
|
|
36
|
+
if (["SHA256", "SHA512", "MD5"].includes(algorithm))
|
|
37
|
+
throw new Error(`${algorithm} is a one-way hash and cannot be decrypted.`);
|
|
38
|
+
const decData = CryptoJS.enc.Base64.parse(data).toString(CryptoJS.enc.Utf8);
|
|
39
|
+
let decrypted;
|
|
40
|
+
switch (algorithm) {
|
|
41
|
+
case "AES":
|
|
42
|
+
decrypted = CryptoJS.AES.decrypt(decData, key).toString(CryptoJS.enc.Utf8);
|
|
43
|
+
break;
|
|
44
|
+
case "TripleDES":
|
|
45
|
+
decrypted = CryptoJS.TripleDES.decrypt(decData, key).toString(CryptoJS.enc.Utf8);
|
|
46
|
+
break;
|
|
47
|
+
default: throw new Error(`Unsupported algorithm: ${algorithm}`);
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(decrypted);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return decrypted;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { ValidationRules } from "./validation.util";
|
|
2
|
+
import { ApiType } from "./api.util";
|
|
3
|
+
type DBSchema = any;
|
|
4
|
+
export interface FormRegisterType {
|
|
5
|
+
name: string;
|
|
6
|
+
status?: boolean;
|
|
7
|
+
validations?: ValidationRules;
|
|
8
|
+
}
|
|
9
|
+
export interface FormValueType {
|
|
10
|
+
name: string;
|
|
11
|
+
value?: any;
|
|
12
|
+
}
|
|
13
|
+
export interface FormErrorType {
|
|
14
|
+
name: string;
|
|
15
|
+
error?: string | null;
|
|
16
|
+
}
|
|
17
|
+
export interface FormStateType {
|
|
18
|
+
formRegisters: FormRegisterType[];
|
|
19
|
+
formValues: FormValueType[];
|
|
20
|
+
formErrors: FormErrorType[];
|
|
21
|
+
loading: boolean;
|
|
22
|
+
showConfirm: boolean;
|
|
23
|
+
}
|
|
24
|
+
type ActionPayloadType = {
|
|
25
|
+
SET_REGISTER: FormRegisterType;
|
|
26
|
+
UNREGISTER: string;
|
|
27
|
+
UNREGISTER_PREFIX: string;
|
|
28
|
+
SET_VALUES: FormValueType[];
|
|
29
|
+
SET_VALUE: FormValueType;
|
|
30
|
+
SET_ERRORS: FormErrorType[];
|
|
31
|
+
SET_LOADING: boolean;
|
|
32
|
+
SET_CONFIRM: boolean;
|
|
33
|
+
};
|
|
34
|
+
type TypeKeys = keyof ActionPayloadType;
|
|
35
|
+
export type ActionType<T extends TypeKeys = "SET_REGISTER" | "SET_VALUES" | "SET_VALUE" | "SET_ERRORS" | "SET_LOADING" | "SET_CONFIRM" | "RESET" | any> = {
|
|
36
|
+
type: T;
|
|
37
|
+
payload?: ActionPayloadType[T];
|
|
38
|
+
};
|
|
39
|
+
export declare const useForm: (submitControl: ((ApiType & {
|
|
40
|
+
idb?: never;
|
|
41
|
+
}) | {
|
|
42
|
+
idb: {
|
|
43
|
+
store: string;
|
|
44
|
+
schema?: DBSchema;
|
|
45
|
+
};
|
|
46
|
+
}) & {
|
|
47
|
+
payload?: ((values: any) => Promise<Record<string, any>> | Record<string, any>) | false;
|
|
48
|
+
confirmation?: boolean;
|
|
49
|
+
onSuccess?: (data: any) => void;
|
|
50
|
+
onFailed?: (code: number) => void;
|
|
51
|
+
}) => {
|
|
52
|
+
submit: (e: any) => Promise<void>;
|
|
53
|
+
formControl: (name: string) => {
|
|
54
|
+
register: (_: string, regValidations?: ValidationRules) => void;
|
|
55
|
+
unregister: () => void;
|
|
56
|
+
onChange: (e: any) => void;
|
|
57
|
+
value: any;
|
|
58
|
+
invalid: any;
|
|
59
|
+
};
|
|
60
|
+
setDefaultValues: (values: Record<string, any> | null) => void;
|
|
61
|
+
values: FormValueType[] | (FormValueType | {
|
|
62
|
+
name: any;
|
|
63
|
+
value: any;
|
|
64
|
+
})[];
|
|
65
|
+
setValues: (values: FormValueType[]) => void;
|
|
66
|
+
errors: any;
|
|
67
|
+
setErrors: (errors: FormErrorType[]) => void;
|
|
68
|
+
setRegister: (inputs: FormRegisterType) => void;
|
|
69
|
+
unregister: (name: string) => void;
|
|
70
|
+
unregisterPrefix: (prefix: string) => void;
|
|
71
|
+
loading: any;
|
|
72
|
+
confirm: {
|
|
73
|
+
onConfirm: () => Promise<void>;
|
|
74
|
+
show: any;
|
|
75
|
+
onClose: () => void;
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
export declare const useInputRandomId: () => string;
|
|
79
|
+
export declare const useInputHandler: (name?: string, value?: any, validations?: ValidationRules, register?: (name: string, validations?: ValidationRules) => void, unregister?: (name: string) => void, isFile?: boolean) => {
|
|
80
|
+
value: any;
|
|
81
|
+
setValue: import("react").Dispatch<any>;
|
|
82
|
+
idle: boolean;
|
|
83
|
+
setIdle: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
84
|
+
focus: boolean;
|
|
85
|
+
setFocus: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
86
|
+
};
|
|
87
|
+
export {};
|