unictive-angular 1.0.40 → 1.0.43
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/bin/utils/createStructure.js +1 -0
- package/bin/utils/resource/src/app/API/auth/auth.service.ts +1 -7
- package/bin/utils/resource/src/app/core/config/list-store.ts +209 -0
- package/bin/utils/resource/src/app/core/guards/login.guard.ts +0 -5
- package/bin/utils/resource/src/app/shared/ui/input-text/input-text.component.ts +43 -7
- package/bin/utils/resource/tools/dx.js +123 -0
- package/bin/utils/updatePackageJson.js +12 -1
- package/package.json +1 -1
|
@@ -90,14 +90,8 @@ export class AuthService extends AbstractApiService<any> {
|
|
|
90
90
|
})
|
|
91
91
|
|
|
92
92
|
return Entity_User.load(async () => {
|
|
93
|
-
let isRole = null;
|
|
94
|
-
await this.helper.getStorage('role').then((res) => {
|
|
95
|
-
if (res) {
|
|
96
|
-
isRole = res;
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
93
|
|
|
100
|
-
const res = await this.get(
|
|
94
|
+
const res = await this.get('/refresh');
|
|
101
95
|
if (res) {
|
|
102
96
|
this.setLogin(true);
|
|
103
97
|
this.helper.setStorage('users', res?.data?.user);
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { signal, computed } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
export interface PaginationMeta {
|
|
4
|
+
current_page: number;
|
|
5
|
+
from: number;
|
|
6
|
+
last_page: number;
|
|
7
|
+
per_page: number;
|
|
8
|
+
to: number;
|
|
9
|
+
total: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface PaginatedResponse<T, M = PaginationMeta> {
|
|
13
|
+
data: T[];
|
|
14
|
+
meta: M;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface EntityState<T, M = PaginationMeta> {
|
|
18
|
+
value: T[];
|
|
19
|
+
loading: boolean;
|
|
20
|
+
error?: string;
|
|
21
|
+
meta?: M;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function ListStoreFactory<
|
|
25
|
+
T extends Record<any, any>,
|
|
26
|
+
M extends PaginationMeta = PaginationMeta,
|
|
27
|
+
K extends keyof T = 'id'
|
|
28
|
+
>(keyField?: K | any) {
|
|
29
|
+
|
|
30
|
+
const field = (keyField ?? ('id' as K));
|
|
31
|
+
|
|
32
|
+
const _state = signal<EntityState<T, M>>({
|
|
33
|
+
value: [],
|
|
34
|
+
loading: false,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// ========================
|
|
38
|
+
// Computed Selectors
|
|
39
|
+
// ========================
|
|
40
|
+
|
|
41
|
+
const all = computed(() => _state().value);
|
|
42
|
+
const count = computed(() => _state().value.length);
|
|
43
|
+
const meta = computed(() => _state().meta);
|
|
44
|
+
const loading = computed(() => _state().loading);
|
|
45
|
+
const error = computed(() => _state().error);
|
|
46
|
+
|
|
47
|
+
const hasNextPage = computed(() => {
|
|
48
|
+
const m = _state().meta;
|
|
49
|
+
if (!m) return false;
|
|
50
|
+
return m.current_page < m.last_page;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const currentPage = computed(() => _state().meta?.current_page ?? 1);
|
|
54
|
+
|
|
55
|
+
const mapByKey = computed(() => {
|
|
56
|
+
const map = new Map<T[K], T>();
|
|
57
|
+
for (const item of _state().value) {
|
|
58
|
+
map.set(item[field], item);
|
|
59
|
+
}
|
|
60
|
+
return map;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const getByKey = (key: T[K]) =>
|
|
64
|
+
computed(() => mapByKey().get(key));
|
|
65
|
+
|
|
66
|
+
// ========================
|
|
67
|
+
// Private Helpers
|
|
68
|
+
// ========================
|
|
69
|
+
|
|
70
|
+
function deduplicate(items: T[]): T[] {
|
|
71
|
+
const map = new Map<T[K], T>();
|
|
72
|
+
for (const item of items) {
|
|
73
|
+
map.set(item[field], item);
|
|
74
|
+
}
|
|
75
|
+
return Array.from(map.values());
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function setLoading(value: boolean) {
|
|
79
|
+
_state.update(s => ({ ...s, loading: value }));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function setError(message?: string) {
|
|
83
|
+
_state.update(s => ({ ...s, error: message }));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ========================
|
|
87
|
+
// Public API
|
|
88
|
+
// ========================
|
|
89
|
+
|
|
90
|
+
async function loadPage(
|
|
91
|
+
page: number,
|
|
92
|
+
fetchFn: (page: number) => Promise<PaginatedResponse<T, M>>
|
|
93
|
+
) {
|
|
94
|
+
setLoading(true);
|
|
95
|
+
setError(undefined);
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const response = await fetchFn(page);
|
|
99
|
+
|
|
100
|
+
console.log(response);
|
|
101
|
+
_state.update(curr => ({
|
|
102
|
+
value: page === 1
|
|
103
|
+
? response.data
|
|
104
|
+
: deduplicate([...curr.value, ...response.data]),
|
|
105
|
+
meta: response.meta,
|
|
106
|
+
loading: false,
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
return response;
|
|
110
|
+
} catch (err: any) {
|
|
111
|
+
setLoading(false);
|
|
112
|
+
setError(err?.message ?? 'Unknown error');
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// --- Async Loader ---
|
|
118
|
+
async function load(
|
|
119
|
+
page: number,
|
|
120
|
+
fetchFn: (page: number) => Promise<PaginatedResponse<T, M>>
|
|
121
|
+
) {
|
|
122
|
+
_state.update((curr) => ({
|
|
123
|
+
...curr,
|
|
124
|
+
loading: true,
|
|
125
|
+
error: undefined,
|
|
126
|
+
}));
|
|
127
|
+
try {
|
|
128
|
+
const response = await fetchFn(page);
|
|
129
|
+
// console.log('Entity Store Loaded Data:', data);
|
|
130
|
+
_state.set({ value: response.data, meta: response.meta, loading: false });
|
|
131
|
+
return response.data;
|
|
132
|
+
} catch (err: any) {
|
|
133
|
+
_state.update((curr) => ({
|
|
134
|
+
...curr,
|
|
135
|
+
loading: false,
|
|
136
|
+
error: err.message ?? 'Unknown error',
|
|
137
|
+
}));
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
async function loadNextPage(
|
|
145
|
+
fetchFn: (page: number) => Promise<PaginatedResponse<T, M>>
|
|
146
|
+
) {
|
|
147
|
+
if (!hasNextPage() || loading()) return;
|
|
148
|
+
|
|
149
|
+
const nextPage = currentPage() + 1;
|
|
150
|
+
return loadPage(nextPage, fetchFn);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function refresh(
|
|
154
|
+
fetchFn: (page: number) => Promise<PaginatedResponse<T, M>>
|
|
155
|
+
) {
|
|
156
|
+
return loadPage(1, fetchFn);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function reset() {
|
|
160
|
+
_state.set({
|
|
161
|
+
value: [],
|
|
162
|
+
loading: false,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function clear() {
|
|
167
|
+
reset();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function update(key: T[K], partial: Partial<T>) {
|
|
171
|
+
_state.update(curr => ({
|
|
172
|
+
...curr,
|
|
173
|
+
value: curr.value.map(i =>
|
|
174
|
+
i[field] === key ? { ...i, ...partial } : i
|
|
175
|
+
),
|
|
176
|
+
}));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function remove(key: T[K]) {
|
|
180
|
+
_state.update(curr => ({
|
|
181
|
+
...curr,
|
|
182
|
+
value: curr.value.filter(i => i[field] !== key),
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
state: _state,
|
|
188
|
+
|
|
189
|
+
// selectors
|
|
190
|
+
all,
|
|
191
|
+
count,
|
|
192
|
+
meta,
|
|
193
|
+
loading,
|
|
194
|
+
error,
|
|
195
|
+
hasNextPage,
|
|
196
|
+
currentPage,
|
|
197
|
+
getByKey,
|
|
198
|
+
|
|
199
|
+
// actions
|
|
200
|
+
load,
|
|
201
|
+
loadPage,
|
|
202
|
+
loadNextPage,
|
|
203
|
+
refresh,
|
|
204
|
+
update,
|
|
205
|
+
remove,
|
|
206
|
+
reset,
|
|
207
|
+
clear,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
@@ -15,12 +15,7 @@ export const loginGuard: CanActivateFn = (route, state) => {
|
|
|
15
15
|
return storage.get(environment.tokenName).pipe(
|
|
16
16
|
map((token: any) => {
|
|
17
17
|
if (token) {
|
|
18
|
-
const appId = route.queryParams['appId'];
|
|
19
|
-
const redirect = route.queryParams['redirect'];
|
|
20
18
|
|
|
21
|
-
if (appId && redirect) {
|
|
22
|
-
window.location.href = redirect + '?token=' + token.token;
|
|
23
|
-
}
|
|
24
19
|
|
|
25
20
|
router.navigateByUrl('/home');
|
|
26
21
|
// helper.showPopup(
|
|
@@ -1,12 +1,19 @@
|
|
|
1
|
+
import { CommonModule } from '@angular/common';
|
|
1
2
|
import { Component, forwardRef, input, Input, OnInit } from '@angular/core';
|
|
2
3
|
import { ControlValueAccessor, FormControl, FormsModule, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
|
|
3
4
|
import { AlertErrorInputComponent } from '@shared/widgets/alert-error-input/alert-error-input.component';
|
|
5
|
+
import { FloatLabelModule } from "primeng/floatlabel";
|
|
4
6
|
|
|
5
7
|
@Component({
|
|
6
8
|
selector: 'input-text',
|
|
7
9
|
styleUrls: ['./input-text.component.scss'],
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
imports: [
|
|
11
|
+
ReactiveFormsModule,
|
|
12
|
+
FormsModule,
|
|
13
|
+
AlertErrorInputComponent,
|
|
14
|
+
FloatLabelModule,
|
|
15
|
+
CommonModule
|
|
16
|
+
],
|
|
10
17
|
providers: [
|
|
11
18
|
{
|
|
12
19
|
provide: NG_VALUE_ACCESSOR,
|
|
@@ -15,17 +22,43 @@ import { AlertErrorInputComponent } from '@shared/widgets/alert-error-input/aler
|
|
|
15
22
|
},
|
|
16
23
|
],
|
|
17
24
|
template: `
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
<div class="rounded-lg bg-white px-4 py-2">
|
|
26
|
+
<label class="block text-[#657687] mb-0 text-sm" [for]="label()"
|
|
27
|
+
>{{label()}}</label>
|
|
28
|
+
@if(this.type == 'password'){
|
|
29
|
+
<div class="relative">
|
|
30
|
+
<input [autocomplete]="false" (ngModelChange)="onValueChange($event)" [(ngModel)]="value" [type]="showPassword ? 'text' : type"
|
|
31
|
+
class="w-full p-0! border-none! rounded-lg focus:outline-none! focus:ring-0"
|
|
32
|
+
[placeholder]="placeholder()" />
|
|
33
|
+
<i
|
|
34
|
+
class="pi absolute right-0 top-0 -translate-y-1/2 cursor-pointer text-gray-500 hover:text-gray-700"
|
|
35
|
+
[ngClass]="showPassword ? 'pi-eye' : 'pi-eye-slash'"
|
|
36
|
+
(click)="showPassword = !showPassword"
|
|
37
|
+
></i>
|
|
38
|
+
</div>
|
|
39
|
+
}
|
|
40
|
+
@else {
|
|
41
|
+
<input (ngModelChange)="onValueChange($event)" [(ngModel)]="value" [type]="type"
|
|
42
|
+
class="w-full p-0! border-none! rounded-lg focus:outline-none! focus:ring-0"
|
|
43
|
+
[placeholder]="placeholder()" />
|
|
44
|
+
}
|
|
45
|
+
</div>
|
|
21
46
|
<alert-error [control]="control" [label]="label()"></alert-error>
|
|
22
47
|
`
|
|
23
48
|
})
|
|
24
49
|
export class InputTextComponent implements ControlValueAccessor, OnInit {
|
|
25
50
|
|
|
51
|
+
showPassword = false;
|
|
26
52
|
@Input() control!: FormControl | any;
|
|
27
|
-
|
|
28
|
-
|
|
53
|
+
@Input() idLabel = "";
|
|
54
|
+
@Input() variant: "floating" | "normal" = "normal";
|
|
55
|
+
@Input() name = "";
|
|
56
|
+
@Input() typeLabel: "over" | "on" | "in" = "in";
|
|
57
|
+
@Input() type: "password" | "email" | "text" | "number" = "text";
|
|
58
|
+
|
|
59
|
+
placeholder = input("Enter your email"); //default
|
|
60
|
+
label = input("label"); //default
|
|
61
|
+
form: any = input();
|
|
29
62
|
|
|
30
63
|
value: any;
|
|
31
64
|
|
|
@@ -56,6 +89,9 @@ export class InputTextComponent implements ControlValueAccessor, OnInit {
|
|
|
56
89
|
onValueChange(value: any) {
|
|
57
90
|
this.onChange(value);
|
|
58
91
|
}
|
|
92
|
+
togglePassword() {
|
|
93
|
+
this.showPassword = !this.showPassword;
|
|
94
|
+
}
|
|
59
95
|
|
|
60
96
|
// Fungsi yang akan dipanggil ketika nilai diubah
|
|
61
97
|
onChange: (date: any) => void = () => { };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { execSync } = require("child_process");
|
|
4
|
+
|
|
5
|
+
function run(cmd) {
|
|
6
|
+
console.log(`\n$ ${cmd}`);
|
|
7
|
+
execSync(cmd, { stdio: "inherit" });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function branch() {
|
|
11
|
+
return execSync("git rev-parse --abbrev-ref HEAD").toString().trim();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const cmd = process.argv[2];
|
|
15
|
+
const arg = process.argv.slice(3).join(" ");
|
|
16
|
+
|
|
17
|
+
switch (cmd) {
|
|
18
|
+
|
|
19
|
+
case "feature":
|
|
20
|
+
|
|
21
|
+
if (!arg) {
|
|
22
|
+
console.log("❌ feature name required");
|
|
23
|
+
process.exit();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
run("git fetch origin");
|
|
27
|
+
run("git checkout development");
|
|
28
|
+
run("git pull origin development");
|
|
29
|
+
run(`git checkout -b feature/${arg}`);
|
|
30
|
+
|
|
31
|
+
console.log(`✅ feature/${arg} created`);
|
|
32
|
+
break;
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
case "sync":
|
|
36
|
+
|
|
37
|
+
const b = branch();
|
|
38
|
+
|
|
39
|
+
run("git fetch origin");
|
|
40
|
+
run("git checkout development");
|
|
41
|
+
run("git pull origin development");
|
|
42
|
+
run(`git checkout ${b}`);
|
|
43
|
+
run("git rebase development");
|
|
44
|
+
|
|
45
|
+
console.log("✅ branch synced");
|
|
46
|
+
break;
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
case "commit":
|
|
50
|
+
|
|
51
|
+
if (!arg) {
|
|
52
|
+
console.log("❌ commit message required");
|
|
53
|
+
process.exit();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
run("git add .");
|
|
57
|
+
run(`git commit -m "${arg}"`);
|
|
58
|
+
run(`git push origin ${branch()}`);
|
|
59
|
+
|
|
60
|
+
break;
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
case "pr":
|
|
64
|
+
|
|
65
|
+
const bp = branch();
|
|
66
|
+
|
|
67
|
+
run(`gh pr create --base development --head ${bp} --title "${bp}"`);
|
|
68
|
+
|
|
69
|
+
break;
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
case "finish":
|
|
73
|
+
|
|
74
|
+
const bf = branch();
|
|
75
|
+
|
|
76
|
+
run("git checkout development");
|
|
77
|
+
run("git pull origin development");
|
|
78
|
+
run(`git merge ${bf}`);
|
|
79
|
+
run("git push origin development");
|
|
80
|
+
|
|
81
|
+
run(`git branch -d ${bf}`);
|
|
82
|
+
run(`git push origin --delete ${bf}`);
|
|
83
|
+
|
|
84
|
+
console.log("🎉 feature merged");
|
|
85
|
+
break;
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
case "start":
|
|
89
|
+
|
|
90
|
+
console.log("🚀 starting dev environment");
|
|
91
|
+
|
|
92
|
+
run("git pull");
|
|
93
|
+
run("npm install");
|
|
94
|
+
run("ng serve");
|
|
95
|
+
|
|
96
|
+
break;
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
case "release":
|
|
100
|
+
|
|
101
|
+
console.log("📦 releasing project");
|
|
102
|
+
|
|
103
|
+
run("git checkout staging");
|
|
104
|
+
run("git merge development");
|
|
105
|
+
run("git push origin staging");
|
|
106
|
+
|
|
107
|
+
break;
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
default:
|
|
111
|
+
|
|
112
|
+
console.log(`
|
|
113
|
+
DX CLI
|
|
114
|
+
|
|
115
|
+
dx feature <name> create feature branch
|
|
116
|
+
dx sync sync with development
|
|
117
|
+
dx commit "msg" commit + push
|
|
118
|
+
dx pr create pull request
|
|
119
|
+
dx finish merge feature
|
|
120
|
+
dx start start dev environment
|
|
121
|
+
dx release release to staging
|
|
122
|
+
`);
|
|
123
|
+
}
|
|
@@ -8,7 +8,8 @@ const listPackagesToInstall = [
|
|
|
8
8
|
"npm i @ngx-pwa/local-storage --legacy-peer-deps",
|
|
9
9
|
"npm i primeng --legacy-peer-deps",
|
|
10
10
|
"npm i primeicons @primeuix/themes quill --legacy-peer-deps",
|
|
11
|
-
"npm i sweetalert2 --legacy-peer-deps"
|
|
11
|
+
"npm i sweetalert2 --legacy-peer-deps",
|
|
12
|
+
"npm i @angular/animations --legacy-peer-deps",
|
|
12
13
|
];
|
|
13
14
|
|
|
14
15
|
|
|
@@ -41,6 +42,13 @@ async function update_package_json() {
|
|
|
41
42
|
|
|
42
43
|
|
|
43
44
|
|
|
45
|
+
// ------------------- Bin -------------------
|
|
46
|
+
pkg.bin = pkg.bin || {};
|
|
47
|
+
pkg.bin["dx"] = "./tools/dx.js";
|
|
48
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
49
|
+
show_status('Menambahkan field "bin" ke package.json!', "success");
|
|
50
|
+
|
|
51
|
+
|
|
44
52
|
listPackagesToInstall.forEach(command => {
|
|
45
53
|
console.log(`Menjalankan: ${command}`);
|
|
46
54
|
try {
|
|
@@ -51,6 +59,9 @@ async function update_package_json() {
|
|
|
51
59
|
}
|
|
52
60
|
});
|
|
53
61
|
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
54
65
|
|
|
55
66
|
|
|
56
67
|
// ------------------- Angular file replacements -------------------
|
package/package.json
CHANGED