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.
@@ -17,6 +17,7 @@ const folders_to_copy = [
17
17
  "src/app/app.ts",
18
18
  "src/app/app.html",
19
19
  "src/environments",
20
+ "tools",
20
21
 
21
22
  // "package.json",
22
23
  // "tailwind.config.js",
@@ -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(isRole ? `/refresh?role=${isRole}` : '/refresh');
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
- standalone: true,
9
- imports: [ReactiveFormsModule, FormsModule, AlertErrorInputComponent],
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
- <input (ngModelChange)="onValueChange($event)" [(ngModel)]="value" type="email"
19
- class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
20
- [placeholder]="placeholder()" />
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
- placeholder = input('Enter your email'); //default
28
- label = input('label'); //default
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unictive-angular",
3
- "version": "1.0.40",
3
+ "version": "1.0.43",
4
4
  "description": "CLI untuk membuat project Angular dengan struktur kustom (Modules, Core, API, Shared).",
5
5
  "scripts": {
6
6
  "build": "npm version patch && npm publish --access public"