-new"> 144
+			productRefGroup = 504EC3051FED79650016851F /* Products */;
145
+			projectDirPath = "";
146
+			projectRoot = "";
147
+			targets = (
148
+				504EC3031FED79650016851F /* App */,
149
+			);
150
+		};
151
+/* End PBXProject section */
152
+
153
+/* Begin PBXResourcesBuildPhase section */
154
+		504EC3021FED79650016851F /* Resources */ = {
155
+			isa = PBXResourcesBuildPhase;
156
+			buildActionMask = 2147483647;
157
+			files = (
158
+				504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */,
159
+				50B271D11FEDC1A000F3C39B /* public in Resources */,
160
+				504EC30F1FED79650016851F /* Assets.xcassets in Resources */,
161
+				50379B232058CBB4000EE86E /* capacitor.config.json in Resources */,
162
+				504EC30D1FED79650016851F /* Main.storyboard in Resources */,
163
+				2FAD9763203C412B000D30F8 /* config.xml in Resources */,
164
+			);
165
+			runOnlyForDeploymentPostprocessing = 0;
166
+		};
167
+/* End PBXResourcesBuildPhase section */
168
+
169
+/* Begin PBXShellScriptBuildPhase section */
170
+		6634F4EFEBD30273BCE97C65 /* [CP] Check Pods Manifest.lock */ = {
171
+			isa = PBXShellScriptBuildPhase;
172
+			buildActionMask = 2147483647;
173
+			files = (
174
+			);
175
+			inputPaths = (
176
+				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
177
+				"${PODS_ROOT}/Manifest.lock",
178
+			);
179
+			name = "[CP] Check Pods Manifest.lock";
180
+			outputPaths = (
181
+				"$(DERIVED_FILE_DIR)/Pods-App-checkManifestLockResult.txt",
182
+			);
183
+			runOnlyForDeploymentPostprocessing = 0;
184
+			shellPath = /bin/sh;
185
+			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
186
+			showEnvVarsInLog = 0;
187
+		};
188
+		9592DBEFFC6D2A0C8D5DEB22 /* [CP] Embed Pods Frameworks */ = {
189
+			isa = PBXShellScriptBuildPhase;
190
+			buildActionMask = 2147483647;
191
+			files = (
192
+			);
193
+			inputPaths = (
194
+			);
195
+			name = "[CP] Embed Pods Frameworks";
196
+			outputPaths = (
197
+			);
198
+			runOnlyForDeploymentPostprocessing = 0;
199
+			shellPath = /bin/sh;
200
+			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-App/Pods-App-frameworks.sh\"\n";
201
+			showEnvVarsInLog = 0;
202
+		};
203
+/* End PBXShellScriptBuildPhase section */
204
+
205
+/* Begin PBXSourcesBuildPhase section */
206
+		504EC3001FED79650016851F /* Sources */ = {
207
+			isa = PBXSourcesBuildPhase;
208
+			buildActionMask = 2147483647;
209
+			files = (
210
+				504EC3081FED79650016851F /* AppDelegate.swift in Sources */,
211
+			);
212
+			runOnlyForDeploymentPostprocessing = 0;
213
+		};
214
+/* End PBXSourcesBuildPhase section */
215
+
216
+/* Begin PBXVariantGroup section */
217
+		504EC30B1FED79650016851F /* Main.storyboard */ = {
218
+			isa = PBXVariantGroup;
219
+			children = (
220
+				504EC30C1FED79650016851F /* Base */,
221
+			);
222
+			name = Main.storyboard;
223
+			sourceTree = "<group>";
224
+		};
225
+		504EC3101FED79650016851F /* LaunchScreen.storyboard */ = {
226
+			isa = PBXVariantGroup;
227
+			children = (
228
+				504EC3111FED79650016851F /* Base */,
229
+			);
230
+			name = LaunchScreen.storyboard;
231
+			sourceTree = "<group>";
232
+		};
233
+/* End PBXVariantGroup section */
234
+
235
+/* Begin XCBuildConfiguration section */
236
+		504EC3141FED79650016851F /* Debug */ = {
237
+			isa = XCBuildConfiguration;
238
+			buildSettings = {
239
+				ALWAYS_SEARCH_USER_PATHS = NO;
240
+				CLANG_ANALYZER_NONNULL = YES;
241
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
242
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
243
+				CLANG_CXX_LIBRARY = "libc++";
244
+				CLANG_ENABLE_MODULES = YES;
245
+				CLANG_ENABLE_OBJC_ARC = YES;
246
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
247
+				CLANG_WARN_BOOL_CONVERSION = YES;
248
+				CLANG_WARN_COMMA = YES;
249
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
250
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
251
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
252
+				CLANG_WARN_EMPTY_BODY = YES;
253
+				CLANG_WARN_ENUM_CONVERSION = YES;
254
+				CLANG_WARN_INFINITE_RECURSION = YES;
255
+				CLANG_WARN_INT_CONVERSION = YES;
256
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
257
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
258
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
259
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
260
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
261
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
262
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
263
+				CLANG_WARN_UNREACHABLE_CODE = YES;
264
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
265
+				CODE_SIGN_IDENTITY = "iPhone Developer";
266
+				COPY_PHASE_STRIP = NO;
267
+				DEBUG_INFORMATION_FORMAT = dwarf;
268
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
269
+				ENABLE_TESTABILITY = YES;
270
+				GCC_C_LANGUAGE_STANDARD = gnu11;
271
+				GCC_DYNAMIC_NO_PIC = NO;
272
+				GCC_NO_COMMON_BLOCKS = YES;
273
+				GCC_OPTIMIZATION_LEVEL = 0;
274
+				GCC_PREPROCESSOR_DEFINITIONS = (
275
+					"DEBUG=1",
276
+					"$(inherited)",
277
+				);
278
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
279
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
280
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
281
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
282
+				GCC_WARN_UNUSED_FUNCTION = YES;
283
+				GCC_WARN_UNUSED_VARIABLE = YES;
284
+				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
285
+				MTL_ENABLE_DEBUG_INFO = YES;
286
+				ONLY_ACTIVE_ARCH = YES;
287
+				SDKROOT = iphoneos;
288
+				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
289
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
290
+			};
291
+			name = Debug;
292
+		};
293
+		504EC3151FED79650016851F /* Release */ = {
294
+			isa = XCBuildConfiguration;
295
+			buildSettings = {
296
+				ALWAYS_SEARCH_USER_PATHS = NO;
297
+				CLANG_ANALYZER_NONNULL = YES;
298
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
299
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
300
+				CLANG_CXX_LIBRARY = "libc++";
301
+				CLANG_ENABLE_MODULES = YES;
302
+				CLANG_ENABLE_OBJC_ARC = YES;
303
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
304
+				CLANG_WARN_BOOL_CONVERSION = YES;
305
+				CLANG_WARN_COMMA = YES;
306
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
307
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
308
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
309
+				CLANG_WARN_EMPTY_BODY = YES;
310
+				CLANG_WARN_ENUM_CONVERSION = YES;
311
+				CLANG_WARN_INFINITE_RECURSION = YES;
312
+				CLANG_WARN_INT_CONVERSION = YES;
313
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
314
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
315
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
316
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
317
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
318
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
319
+				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
320
+				CLANG_WARN_UNREACHABLE_CODE = YES;
321
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
322
+				CODE_SIGN_IDENTITY = "iPhone Developer";
323
+				COPY_PHASE_STRIP = NO;
324
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
325
+				ENABLE_NS_ASSERTIONS = NO;
326
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
327
+				GCC_C_LANGUAGE_STANDARD = gnu11;
328
+				GCC_NO_COMMON_BLOCKS = YES;
329
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
330
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
331
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
332
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
333
+				GCC_WARN_UNUSED_FUNCTION = YES;
334
+				GCC_WARN_UNUSED_VARIABLE = YES;
335
+				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
336
+				MTL_ENABLE_DEBUG_INFO = NO;
337
+				SDKROOT = iphoneos;
338
+				SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
339
+				VALIDATE_PRODUCT = YES;
340
+			};
341
+			name = Release;
342
+		};
343
+		504EC3171FED79650016851F /* Debug */ = {
344
+			isa = XCBuildConfiguration;
345
+			baseConfigurationReference = FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */;
346
+			buildSettings = {
347
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
348
+				CODE_SIGN_STYLE = Automatic;
349
+				CURRENT_PROJECT_VERSION = 1;
350
+				INFOPLIST_FILE = App/Info.plist;
351
+				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
352
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
353
+				MARKETING_VERSION = 1.0;
354
+				OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
355
+				PRODUCT_BUNDLE_IDENTIFIER = io.ionic.starter;
356
+				PRODUCT_NAME = "$(TARGET_NAME)";
357
+				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
358
+				SWIFT_VERSION = 5.0;
359
+				TARGETED_DEVICE_FAMILY = "1,2";
360
+			};
361
+			name = Debug;
362
+		};
363
+		504EC3181FED79650016851F /* Release */ = {
364
+			isa = XCBuildConfiguration;
365
+			baseConfigurationReference = AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */;
366
+			buildSettings = {
367
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
368
+				CODE_SIGN_STYLE = Automatic;
369
+				CURRENT_PROJECT_VERSION = 1;
370
+				INFOPLIST_FILE = App/Info.plist;
371
+				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
372
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
373
+				MARKETING_VERSION = 1.0;
374
+				PRODUCT_BUNDLE_IDENTIFIER = io.ionic.starter;
375
+				PRODUCT_NAME = "$(TARGET_NAME)";
376
+				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
377
+				SWIFT_VERSION = 5.0;
378
+				TARGETED_DEVICE_FAMILY = "1,2";
379
+			};
380
+			name = Release;
381
+		};
382
+/* End XCBuildConfiguration section */
383
+
384
+/* Begin XCConfigurationList section */
385
+		504EC2FF1FED79650016851F /* Build configuration list for PBXProject "App" */ = {
386
+			isa = XCConfigurationList;
387
+			buildConfigurations = (
388
+				504EC3141FED79650016851F /* Debug */,
389
+				504EC3151FED79650016851F /* Release */,
390
+			);
391
+			defaultConfigurationIsVisible = 0;
392
+			defaultConfigurationName = Release;
393
+		};
394
+		504EC3161FED79650016851F /* Build configuration list for PBXNativeTarget "App" */ = {
395
+			isa = XCConfigurationList;
396
+			buildConfigurations = (
397
+				504EC3171FED79650016851F /* Debug */,
398
+				504EC3181FED79650016851F /* Release */,
399
+			);
400
+			defaultConfigurationIsVisible = 0;
401
+			defaultConfigurationName = Release;
402
+		};
403
+/* End XCConfigurationList section */
404
+	};
405
+	rootObject = 504EC2FC1FED79650016851F /* Project object */;
406
+}

+ 7 - 0
ios/App/App.xcodeproj/project.xcworkspace/contents.xcworkspacedata

@@ -0,0 +1,7 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<Workspace
3
+   version = "1.0">
4
+   <FileRef
5
+      location = "self:App.xcodeproj">
6
+   </FileRef>
7
+</Workspace>

+ 10 - 0
ios/App/App.xcworkspace/contents.xcworkspacedata

@@ -0,0 +1,10 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<Workspace
3
+   version = "1.0">
4
+   <FileRef
5
+      location = "group:App.xcodeproj">
6
+   </FileRef>
7
+   <FileRef
8
+      location = "group:Pods/Pods.xcodeproj">
9
+   </FileRef>
10
+</Workspace>

+ 8 - 0
ios/App/App.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist

@@ -0,0 +1,8 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+<plist version="1.0">
4
+<dict>
5
+	<key>IDEDidComputeMac32BitWarning</key>
6
+	<true/>
7
+</dict>
8
+</plist>

+ 49 - 0
ios/App/App/AppDelegate.swift

@@ -0,0 +1,49 @@
1
+import UIKit
2
+import Capacitor
3
+
4
+@UIApplicationMain
5
+class AppDelegate: UIResponder, UIApplicationDelegate {
6
+
7
+    var window: UIWindow?
8
+
9
+    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
10
+        // Override point for customization after application launch.
11
+        return true
12
+    }
13
+
14
+    func applicationWillResignActive(_ application: UIApplication) {
15
+        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
16
+        // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
17
+    }
18
+
19
+    func applicationDidEnterBackground(_ application: UIApplication) {
20
+        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
21
+        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
22
+    }
23
+
24
+    func applicationWillEnterForeground(_ application: UIApplication) {
25
+        // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
26
+    }
27
+
28
+    func applicationDidBecomeActive(_ application: UIApplication) {
29
+        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
30
+    }
31
+
32
+    func applicationWillTerminate(_ application: UIApplication) {
33
+        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
34
+    }
35
+
36
+    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
37
+        // Called when the app was launched with a url. Feel free to add additional processing here,
38
+        // but if you want the App API to support tracking app url opens, make sure to keep this call
39
+        return ApplicationDelegateProxy.shared.application(app, open: url, options: options)
40
+    }
41
+
42
+    func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
43
+        // Called when the app was launched with an activity, including Universal Links.
44
+        // Feel free to add additional processing here, but if you want the App API to support
45
+        // tracking app url opens, make sure to keep this call
46
+        return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler)
47
+    }
48
+
49
+}

BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@1x.png


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x-1.png


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@2x.png


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-20x20@3x.png


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@1x.png


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x-1.png


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@2x.png


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-29x29@3x.png


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@1x.png


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x-1.png


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@2x.png


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-40x40@3x.png


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@2x.png


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-60x60@3x.png


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@1x.png


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-76x76@2x.png


BIN
ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5x83.5@2x.png


+ 116 - 0
ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json

@@ -0,0 +1,116 @@
1
+{
2
+  "images" : [
3
+    {
4
+      "size" : "20x20",
5
+      "idiom" : "iphone",
6
+      "filename" : "AppIcon-20x20@2x.png",
7
+      "scale" : "2x"
8
+    },
9
+    {
10
+      "size" : "20x20",
11
+      "idiom" : "iphone",
12
+      "filename" : "AppIcon-20x20@3x.png",
13
+      "scale" : "3x"
14
+    },
15
+    {
16
+      "size" : "29x29",
17
+      "idiom" : "iphone",
18
+      "filename" : "AppIcon-29x29@2x-1.png",
19
+      "scale" : "2x"
20
+    },
21
+    {
22
+      "size" : "29x29",
23
+      "idiom" : "iphone",
24
+      "filename" : "AppIcon-29x29@3x.png",
25
+      "scale" : "3x"
26
+    },
27
+    {
28
+      "size" : "40x40",
29
+      "idiom" : "iphone",
30
+      "filename" : "AppIcon-40x40@2x.png",
31
+      "scale" : "2x"
32
+    },
33
+    {
34
+      "size" : "40x40",
35
+      "idiom" : "iphone",
36
+      "filename" : "AppIcon-40x40@3x.png",
37
+      "scale" : "3x"
38
+    },
39
+    {
40
+      "size" : "60x60",
41
+      "idiom" : "iphone",
42
+      "filename" : "AppIcon-60x60@2x.png",
43
+      "scale" : "2x"
44
+    },
45
+    {
46
+      "size" : "60x60",
47
+      "idiom" : "iphone",
48
+      "filename" : "AppIcon-60x60@3x.png",
49
+      "scale" : "3x"
50
+    },
51
+    {
52
+      "size" : "20x20",
53
+      "idiom" : "ipad",
54
+      "filename" : "AppIcon-20x20@1x.png",
55
+      "scale" : "1x"
56
+    },
57
+    {
58
+      "size" : "20x20",
59
+      "idiom" : "ipad",
60
+      "filename" : "AppIcon-20x20@2x-1.png",
61
+      "scale" : "2x"
62
+    },
63
+    {
64
+      "size" : "29x29",
65
+      "idiom" : "ipad",
66
+      "filename" : "AppIcon-29x29@1x.png",
67
+      "scale" : "1x"
68
+    },
69
+    {
70
+      "size" : "29x29",
71
+      "idiom" : "ipad",
72
+      "filename" : "AppIcon-29x29@2x.png",
73
+      "scale" : "2x"
74
+    },
75
+    {
76
+      "size" : "40x40",
77
+      "idiom" : "ipad",
78
+      "filename" : "AppIcon-40x40@1x.png",
79
+      "scale" : "1x"
80
+    },
81
+    {
82
+      "size" : "40x40",
83
+      "idiom" : "ipad",
84
+      "filename" : "AppIcon-40x40@2x-1.png",
85
+      "scale" : "2x"
86
+    },
87
+    {
88
+      "size" : "76x76",
89
+      "idiom" : "ipad",
90
+      "filename" : "AppIcon-76x76@1x.png",
91
+      "scale" : "1x"
92
+    },
93
+    {
94
+      "size" : "76x76",
95
+      "idiom" : "ipad",
96
+      "filename" : "AppIcon-76x76@2x.png",
97
+      "scale" : "2x"
98
+    },
99
+    {
100
+      "size" : "83.5x83.5",
101
+      "idiom" : "ipad",
102
+      "filename" : "AppIcon-83.5x83.5@2x.png",
103
+      "scale" : "2x"
104
+    },
105
+    {
106
+      "size" : "1024x1024",
107
+      "idiom" : "ios-marketing",
108
+      "filename" : "AppIcon-512@2x.png",
109
+      "scale" : "1x"
110
+    }
111
+  ],
112
+  "info" : {
113
+    "version" : 1,
114
+    "author" : "xcode"
115
+  }
116
+}

+ 6 - 0
ios/App/App/Assets.xcassets/Contents.json

@@ -0,0 +1,6 @@
1
+{
2
+  "info" : {
3
+    "version" : 1,
4
+    "author" : "xcode"
5
+  }
6
+}

+ 23 - 0
ios/App/App/Assets.xcassets/Splash.imageset/Contents.json

@@ -0,0 +1,23 @@
1
+{
2
+  "images" : [
3
+    {
4
+      "idiom" : "universal",
5
+      "filename" : "splash-2732x2732-2.png",
6
+      "scale" : "1x"
7
+    },
8
+    {
9
+      "idiom" : "universal",
10
+      "filename" : "splash-2732x2732-1.png",
11
+      "scale" : "2x"
12
+    },
13
+    {
14
+      "idiom" : "universal",
15
+      "filename" : "splash-2732x2732.png",
16
+      "scale" : "3x"
17
+    }
18
+  ],
19
+  "info" : {
20
+    "version" : 1,
21
+    "author" : "xcode"
22
+  }
23
+}

BIN
ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png


BIN
ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png


BIN
ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png


+ 32 - 0
ios/App/App/Base.lproj/LaunchScreen.storyboard

@@ -0,0 +1,32 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17132" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
3
+    <device id="retina4_7" orientation="portrait" appearance="light"/>
4
+    <dependencies>
5
+        <deployment identifier="iOS"/>
6
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17105"/>
7
+        <capability name="System colors in document resources" minToolsVersion="11.0"/>
8
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
9
+    </dependencies>
10
+    <scenes>
11
+        <!--View Controller-->
12
+        <scene sceneID="EHf-IW-A2E">
13
+            <objects>
14
+                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
15
+                    <imageView key="view" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Splash" id="snD-IY-ifK">
16
+                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
17
+                        <autoresizingMask key="autoresizingMask"/>
18
+                        <color key="backgroundColor" systemColor="systemBackgroundColor"/>
19
+                    </imageView>
20
+                </viewController>
21
+                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
22
+            </objects>
23
+            <point key="canvasLocation" x="53" y="375"/>
24
+        </scene>
25
+    </scenes>
26
+    <resources>
27
+        <image name="Splash" width="1366" height="1366"/>
28
+        <systemColor name="systemBackgroundColor">
29
+            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
30
+        </systemColor>
31
+    </resources>
32
+</document>

+ 19 - 0
ios/App/App/Base.lproj/Main.storyboard

@@ -0,0 +1,19 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14111" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
3
+    <device id="retina4_7" orientation="portrait">
4
+        <adaptation id="fullscreen"/>
5
+    </device>
6
+    <dependencies>
7
+        <deployment identifier="iOS"/>
8
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
9
+    </dependencies>
10
+    <scenes>
11
+        <!--Bridge View Controller-->
12
+        <scene sceneID="tne-QT-ifu">
13
+            <objects>
14
+                <viewController id="BYZ-38-t0r" customClass="CAPBridgeViewController" customModule="Capacitor" sceneMemberID="viewController"/>
15
+                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
16
+            </objects>
17
+        </scene>
18
+    </scenes>
19
+</document>

+ 49 - 0
ios/App/App/Info.plist

@@ -0,0 +1,49 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+<plist version="1.0">
4
+<dict>
5
+	<key>CFBundleDevelopmentRegion</key>
6
+	<string>en</string>
7
+	<key>CFBundleDisplayName</key>
8
+        <string>ra100</string>
9
+	<key>CFBundleExecutable</key>
10
+	<string>$(EXECUTABLE_NAME)</string>
11
+	<key>CFBundleIdentifier</key>
12
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
13
+	<key>CFBundleInfoDictionaryVersion</key>
14
+	<string>6.0</string>
15
+	<key>CFBundleName</key>
16
+	<string>$(PRODUCT_NAME)</string>
17
+	<key>CFBundlePackageType</key>
18
+	<string>APPL</string>
19
+	<key>CFBundleShortVersionString</key>
20
+	<string>$(MARKETING_VERSION)</string>
21
+	<key>CFBundleVersion</key>
22
+	<string>$(CURRENT_PROJECT_VERSION)</string>
23
+	<key>LSRequiresIPhoneOS</key>
24
+	<true/>
25
+	<key>UILaunchStoryboardName</key>
26
+	<string>LaunchScreen</string>
27
+	<key>UIMainStoryboardFile</key>
28
+	<string>Main</string>
29
+	<key>UIRequiredDeviceCapabilities</key>
30
+	<array>
31
+		<string>armv7</string>
32
+	</array>
33
+	<key>UISupportedInterfaceOrientations</key>
34
+	<array>
35
+		<string>UIInterfaceOrientationPortrait</string>
36
+		<string>UIInterfaceOrientationLandscapeLeft</string>
37
+		<string>UIInterfaceOrientationLandscapeRight</string>
38
+	</array>
39
+	<key>UISupportedInterfaceOrientations~ipad</key>
40
+	<array>
41
+		<string>UIInterfaceOrientationPortrait</string>
42
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
43
+		<string>UIInterfaceOrientationLandscapeLeft</string>
44
+		<string>UIInterfaceOrientationLandscapeRight</string>
45
+	</array>
46
+	<key>UIViewControllerBasedStatusBarAppearance</key>
47
+	<true/>
48
+</dict>
49
+</plist>

+ 27 - 0
ios/App/Podfile

@@ -0,0 +1,27 @@
1
+require_relative '../../node_modules/@capacitor/ios/scripts/pods_helpers'
2
+
3
+platform :ios, '13.0'
4
+use_frameworks!
5
+
6
+# workaround to avoid Xcode caching of Pods that requires
7
+# Product -> Clean Build Folder after new Cordova plugins installed
8
+# Requires CocoaPods 1.6 or newer
9
+install! 'cocoapods', :disable_input_output_paths => true
10
+
11
+def capacitor_pods
12
+  pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
13
+  pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
14
+  pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
15
+  pod 'CapacitorHaptics', :path => '../../node_modules/@capacitor/haptics'
16
+  pod 'CapacitorKeyboard', :path => '../../node_modules/@capacitor/keyboard'
17
+  pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar'
18
+end
19
+
20
+target 'App' do
21
+  capacitor_pods
22
+  # Add your Pods here
23
+end
24
+
25
+post_install do |installer|
26
+  assertDeploymentTarget(installer)
27
+end

File diff suppressed because it is too large
+ 43 - 16987
package-lock.json


+ 1 - 0
package.json

@@ -22,6 +22,7 @@
22 22
     "@capacitor/app": "4.1.0",
23 23
     "@capacitor/core": "4.4.0",
24 24
     "@capacitor/haptics": "4.0.1",
25
+    "@capacitor/ios": "4.4.0",
25 26
     "@capacitor/keyboard": "4.0.1",
26 27
     "@capacitor/status-bar": "4.0.1",
27 28
     "@fortawesome/angular-fontawesome": "^0.11.1",

+ 1 - 1
src/app/home/home.page.html

@@ -72,4 +72,4 @@
72 72
     <img src="../../../assets/img-static/banner-320x100.jpeg" alt="">
73 73
   </ion-grid>
74 74
 
75
-</ion-content>
75
+</ion-content>

BIN
www/IBMPlexSansThai-Regular.ttf


BIN
www/IBMPlexSansThai-SemiBold.ttf


BIN
www/K2D-Light.ttf


BIN
www/Montserrat-Medium.ttf


BIN
www/Prompt-Medium.ttf


BIN
www/Prompt-Regular.ttf


File diff suppressed because it is too large
+ 3244 - 0
www/assets/api/news-api.json


BIN
www/assets/fonts/IBMPlexSansThai-Regular.ttf


BIN
www/assets/fonts/IBMPlexSansThai-SemiBold.ttf


BIN
www/assets/fonts/K2D-Light.ttf


BIN
www/assets/fonts/Montserrat-Medium.ttf


BIN
www/assets/fonts/Montserrat-SemiBold.ttf


BIN
www/assets/fonts/Prompt-Light.ttf


BIN
www/assets/fonts/Prompt-Medium.ttf


BIN
www/assets/fonts/Prompt-Regular.ttf


BIN
www/assets/icon/favicon.png


BIN
www/assets/icon/logo-fm99.png


BIN
www/assets/img-static/SEI129887404-c4a9.jpg


BIN
www/assets/img-static/background_982645-1-min.png


BIN
www/assets/img-static/banner-250x250.jpeg


BIN
www/assets/img-static/banner-320x100.jpeg


BIN
www/assets/img-static/banner-320x50.jpeg


BIN
www/assets/img-static/bg-content-min.jpg


BIN
www/assets/img-static/fb-Ad-1.jpeg


BIN
www/assets/img-static/fb-Ad-2.jpeg


BIN
www/assets/img-static/fb-Ad-3.jpeg


BIN
www/assets/img-static/mqdefault-1.jpg


BIN
www/assets/img-static/mqdefault-2.jpg


BIN
www/assets/img-static/mqdefault-3.jpg


File diff suppressed because it is too large
+ 1 - 0
www/assets/shapes.svg


BIN
www/background_982645-1-min.png


BIN
www/bg-content-min.jpg


File diff suppressed because it is too large
+ 1329 - 0
www/common.js


File diff suppressed because it is too large
+ 1 - 0
www/common.js.map


File diff suppressed because it is too large
+ 1823 - 0
www/default-node_modules_ionic_core_dist_esm_parse-71f28cd7_js-node_modules_ionic_core_dist_esm_t-0c999b.js


File diff suppressed because it is too large
+ 1 - 0
www/default-node_modules_ionic_core_dist_esm_parse-71f28cd7_js-node_modules_ionic_core_dist_esm_t-0c999b.js.map


+ 26 - 0
www/index.html

@@ -0,0 +1,26 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+
4
+<head>
5
+  <meta charset="utf-8"/>
6
+  <title>Ionic App</title>
7
+
8
+  <base href="/"/>
9
+
10
+  <meta name="color-scheme" content="light dark"/>
11
+  <meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
12
+  <meta name="format-detection" content="telephone=no"/>
13
+  <meta name="msapplication-tap-highlight" content="no"/>
14
+
15
+  <link rel="icon" type="image/png" href="assets/icon/favicon.png"/>
16
+
17
+  <!-- add to homescreen for ios -->
18
+  <meta name="apple-mobile-web-app-capable" content="yes"/>
19
+  <meta name="apple-mobile-web-app-status-bar-style" content="black"/>
20
+<link rel="stylesheet" href="styles.css"></head>
21
+
22
+<body>
23
+  <app-root></app-root>
24
+<script src="runtime.js" type="module"></script><script src="polyfills.js" type="module"></script><script src="vendor.js" type="module"></script><script src="main.js" type="module"></script></body>
25
+
26
+</html>

File diff suppressed because it is too large
+ 501 - 0
www/main.js


File diff suppressed because it is too large
+ 1 - 0
www/main.js.map


+ 239 - 0
www/node_modules_ionic_core_dist_esm_index-0bc00b33_js.js

@@ -0,0 +1,239 @@
1
+"use strict";
2
+(self["webpackChunkapp"] = self["webpackChunkapp"] || []).push([["node_modules_ionic_core_dist_esm_index-0bc00b33_js"],{
3
+
4
+/***/ 1612:
5
+/*!*************************************************************!*\
6
+  !*** ./node_modules/@ionic/core/dist/esm/index-0bc00b33.js ***!
7
+  \*************************************************************/
8
+/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
9
+
10
+__webpack_require__.r(__webpack_exports__);
11
+/* harmony export */ __webpack_require__.d(__webpack_exports__, {
12
+/* harmony export */   "startTapClick": () => (/* binding */ startTapClick)
13
+/* harmony export */ });
14
+/* harmony import */ var _helpers_3b390e48_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./helpers-3b390e48.js */ 9234);
15
+/*!
16
+ * (C) Ionic http://ionicframework.com - MIT License
17
+ */
18
+
19
+
20
+const startTapClick = config => {
21
+  let lastTouch = -MOUSE_WAIT * 10;
22
+  let lastActivated = 0;
23
+  let activatableEle;
24
+  let activeRipple;
25
+  let activeDefer;
26
+  const useRippleEffect = config.getBoolean('animated', true) && config.getBoolean('rippleEffect', true);
27
+  const clearDefers = new WeakMap(); // Touch Events
28
+
29
+  const onTouchStart = ev => {
30
+    lastTouch = (0,_helpers_3b390e48_js__WEBPACK_IMPORTED_MODULE_0__.u)(ev);
31
+    pointerDown(ev);
32
+  };
33
+
34
+  const onTouchEnd = ev => {
35
+    lastTouch = (0,_helpers_3b390e48_js__WEBPACK_IMPORTED_MODULE_0__.u)(ev);
36
+    pointerUp(ev);
37
+  };
38
+
39
+  const onMouseDown = ev => {
40
+    // Ignore right clicks
41
+    if (ev.button === 2) {
42
+      return;
43
+    }
44
+
45
+    const t = (0,_helpers_3b390e48_js__WEBPACK_IMPORTED_MODULE_0__.u)(ev) - MOUSE_WAIT;
46
+
47
+    if (lastTouch < t) {
48
+      pointerDown(ev);
49
+    }
50
+  };
51
+
52
+  const onMouseUp = ev => {
53
+    const t = (0,_helpers_3b390e48_js__WEBPACK_IMPORTED_MODULE_0__.u)(ev) - MOUSE_WAIT;
54
+
55
+    if (lastTouch < t) {
56
+      pointerUp(ev);
57
+    }
58
+  };
59
+
60
+  const cancelActive = () => {
61
+    clearTimeout(activeDefer);
62
+    activeDefer = undefined;
63
+
64
+    if (activatableEle) {
65
+      removeActivated(false);
66
+      activatableEle = undefined;
67
+    }
68
+  };
69
+
70
+  const pointerDown = ev => {
71
+    if (activatableEle) {
72
+      return;
73
+    }
74
+
75
+    setActivatedElement(getActivatableTarget(ev), ev);
76
+  };
77
+
78
+  const pointerUp = ev => {
79
+    setActivatedElement(undefined, ev);
80
+  };
81
+
82
+  const setActivatedElement = (el, ev) => {
83
+    // do nothing
84
+    if (el && el === activatableEle) {
85
+      return;
86
+    }
87
+
88
+    clearTimeout(activeDefer);
89
+    activeDefer = undefined;
90
+    const {
91
+      x,
92
+      y
93
+    } = (0,_helpers_3b390e48_js__WEBPACK_IMPORTED_MODULE_0__.p)(ev); // deactivate selected
94
+
95
+    if (activatableEle) {
96
+      if (clearDefers.has(activatableEle)) {
97
+        throw new Error('internal error');
98
+      }
99
+
100
+      if (!activatableEle.classList.contains(ACTIVATED)) {
101
+        addActivated(activatableEle, x, y);
102
+      }
103
+
104
+      removeActivated(true);
105
+    } // activate
106
+
107
+
108
+    if (el) {
109
+      const deferId = clearDefers.get(el);
110
+
111
+      if (deferId) {
112
+        clearTimeout(deferId);
113
+        clearDefers.delete(el);
114
+      }
115
+
116
+      const delay = isInstant(el) ? 0 : ADD_ACTIVATED_DEFERS;
117
+      el.classList.remove(ACTIVATED);
118
+      activeDefer = setTimeout(() => {
119
+        addActivated(el, x, y);
120
+        activeDefer = undefined;
121
+      }, delay);
122
+    }
123
+
124
+    activatableEle = el;
125
+  };
126
+
127
+  const addActivated = (el, x, y) => {
128
+    lastActivated = Date.now();
129
+    el.classList.add(ACTIVATED);
130
+    if (!useRippleEffect) return;
131
+    const rippleEffect = getRippleEffect(el);
132
+
133
+    if (rippleEffect !== null) {
134
+      removeRipple();
135
+      activeRipple = rippleEffect.addRipple(x, y);
136
+    }
137
+  };
138
+
139
+  const removeRipple = () => {
140
+    if (activeRipple !== undefined) {
141
+      activeRipple.then(remove => remove());
142
+      activeRipple = undefined;
143
+    }
144
+  };
145
+
146
+  const removeActivated = smooth => {
147
+    removeRipple();
148
+    const active = activatableEle;
149
+
150
+    if (!active) {
151
+      return;
152
+    }
153
+
154
+    const time = CLEAR_STATE_DEFERS - Date.now() + lastActivated;
155
+
156
+    if (smooth && time > 0 && !isInstant(active)) {
157
+      const deferId = setTimeout(() => {
158
+        active.classList.remove(ACTIVATED);
159
+        clearDefers.delete(active);
160
+      }, CLEAR_STATE_DEFERS);
161
+      clearDefers.set(active, deferId);
162
+    } else {
163
+      active.classList.remove(ACTIVATED);
164
+    }
165
+  };
166
+
167
+  const doc = document;
168
+  doc.addEventListener('ionGestureCaptured', cancelActive);
169
+  doc.addEventListener('touchstart', onTouchStart, true);
170
+  doc.addEventListener('touchcancel', onTouchEnd, true);
171
+  doc.addEventListener('touchend', onTouchEnd, true);
172
+  /**
173
+   * Tap click effects such as the ripple effect should
174
+   * not happen when scrolling. For example, if a user scrolls
175
+   * the page but also happens to do a touchstart on a button
176
+   * as part of the scroll, the ripple effect should not
177
+   * be dispatched. The ripple effect should only happen
178
+   * if the button is activated and the page is not scrolling.
179
+   *
180
+   * pointercancel is dispatched on a gesture when scrolling
181
+   * starts, so this lets us avoid having to listen for
182
+   * ion-content's scroll events.
183
+   */
184
+
185
+  doc.addEventListener('pointercancel', cancelActive, true);
186
+  doc.addEventListener('mousedown', onMouseDown, true);
187
+  doc.addEventListener('mouseup', onMouseUp, true);
188
+};
189
+
190
+const getActivatableTarget = ev => {
191
+  if (ev.composedPath !== undefined) {
192
+    /**
193
+     * composedPath returns EventTarget[]. However,
194
+     * objects other than Element can be targets too.
195
+     * For example, AudioContext can be a target. In this
196
+     * case, we know that the event is a UIEvent so we
197
+     * can assume that the path will contain either Element
198
+     * or ShadowRoot.
199
+     */
200
+    const path = ev.composedPath();
201
+
202
+    for (let i = 0; i < path.length - 2; i++) {
203
+      const el = path[i];
204
+
205
+      if (!(el instanceof ShadowRoot) && el.classList.contains('ion-activatable')) {
206
+        return el;
207
+      }
208
+    }
209
+  } else {
210
+    return ev.target.closest('.ion-activatable');
211
+  }
212
+};
213
+
214
+const isInstant = el => {
215
+  return el.classList.contains('ion-activatable-instant');
216
+};
217
+
218
+const getRippleEffect = el => {
219
+  if (el.shadowRoot) {
220
+    const ripple = el.shadowRoot.querySelector('ion-ripple-effect');
221
+
222
+    if (ripple) {
223
+      return ripple;
224
+    }
225
+  }
226
+
227
+  return el.querySelector('ion-ripple-effect');
228
+};
229
+
230
+const ACTIVATED = 'ion-activated';
231
+const ADD_ACTIVATED_DEFERS = 200;
232
+const CLEAR_STATE_DEFERS = 200;
233
+const MOUSE_WAIT = 2500;
234
+
235
+
236
+/***/ })
237
+
238
+}]);
239
+//# sourceMappingURL=node_modules_ionic_core_dist_esm_index-0bc00b33_js.js.map

File diff suppressed because it is too large
+ 1 - 0
www/node_modules_ionic_core_dist_esm_index-0bc00b33_js.js.map


+ 526 - 0
www/node_modules_ionic_core_dist_esm_input-shims-6ed8f5a5_js.js

@@ -0,0 +1,526 @@
1
+"use strict";
2
+(self["webpackChunkapp"] = self["webpackChunkapp"] || []).push([["node_modules_ionic_core_dist_esm_input-shims-6ed8f5a5_js"],{
3
+
4
+/***/ 4508:
5
+/*!*******************************************************************!*\
6
+  !*** ./node_modules/@ionic/core/dist/esm/input-shims-6ed8f5a5.js ***!
7
+  \*******************************************************************/
8
+/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
9
+
10
+__webpack_require__.r(__webpack_exports__);
11
+/* harmony export */ __webpack_require__.d(__webpack_exports__, {
12
+/* harmony export */   "startInputShims": () => (/* binding */ startInputShims)
13
+/* harmony export */ });
14
+/* harmony import */ var _Users_simplicoltd_projects_fm99_rev_node_modules_babel_runtime_helpers_esm_asyncToGenerator_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./node_modules/@babel/runtime/helpers/esm/asyncToGenerator.js */ 1670);
15
+/* harmony import */ var _index_5d0c8232_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./index-5d0c8232.js */ 3081);
16
+/* harmony import */ var _helpers_3b390e48_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./helpers-3b390e48.js */ 9234);
17
+/* harmony import */ var _index_c4b11676_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./index-c4b11676.js */ 9273);
18
+
19
+
20
+/*!
21
+ * (C) Ionic http://ionicframework.com - MIT License
22
+ */
23
+
24
+
25
+
26
+const cloneMap = new WeakMap();
27
+
28
+const relocateInput = (componentEl, inputEl, shouldRelocate, inputRelativeY = 0) => {
29
+  if (cloneMap.has(componentEl) === shouldRelocate) {
30
+    return;
31
+  }
32
+
33
+  if (shouldRelocate) {
34
+    addClone(componentEl, inputEl, inputRelativeY);
35
+  } else {
36
+    removeClone(componentEl, inputEl);
37
+  }
38
+};
39
+
40
+const isFocused = input => {
41
+  return input === input.getRootNode().activeElement;
42
+};
43
+
44
+const addClone = (componentEl, inputEl, inputRelativeY) => {
45
+  // this allows for the actual input to receive the focus from
46
+  // the user's touch event, but before it receives focus, it
47
+  // moves the actual input to a location that will not screw
48
+  // up the app's layout, and does not allow the native browser
49
+  // to attempt to scroll the input into place (messing up headers/footers)
50
+  // the cloned input fills the area of where native input should be
51
+  // while the native input fakes out the browser by relocating itself
52
+  // before it receives the actual focus event
53
+  // We hide the focused input (with the visible caret) invisible by making it scale(0),
54
+  const parentEl = inputEl.parentNode; // DOM WRITES
55
+
56
+  const clonedEl = inputEl.cloneNode(false);
57
+  clonedEl.classList.add('cloned-input');
58
+  clonedEl.tabIndex = -1;
59
+  parentEl.appendChild(clonedEl);
60
+  cloneMap.set(componentEl, clonedEl);
61
+  const doc = componentEl.ownerDocument;
62
+  const tx = doc.dir === 'rtl' ? 9999 : -9999;
63
+  componentEl.style.pointerEvents = 'none';
64
+  inputEl.style.transform = `translate3d(${tx}px,${inputRelativeY}px,0) scale(0)`;
65
+};
66
+
67
+const removeClone = (componentEl, inputEl) => {
68
+  const clone = cloneMap.get(componentEl);
69
+
70
+  if (clone) {
71
+    cloneMap.delete(componentEl);
72
+    clone.remove();
73
+  }
74
+
75
+  componentEl.style.pointerEvents = '';
76
+  inputEl.style.transform = '';
77
+};
78
+
79
+const enableHideCaretOnScroll = (componentEl, inputEl, scrollEl) => {
80
+  if (!scrollEl || !inputEl) {
81
+    return () => {
82
+      return;
83
+    };
84
+  }
85
+
86
+  const scrollHideCaret = shouldHideCaret => {
87
+    if (isFocused(inputEl)) {
88
+      relocateInput(componentEl, inputEl, shouldHideCaret);
89
+    }
90
+  };
91
+
92
+  const onBlur = () => relocateInput(componentEl, inputEl, false);
93
+
94
+  const hideCaret = () => scrollHideCaret(true);
95
+
96
+  const showCaret = () => scrollHideCaret(false);
97
+
98
+  (0,_helpers_3b390e48_js__WEBPACK_IMPORTED_MODULE_2__.a)(scrollEl, 'ionScrollStart', hideCaret);
99
+  (0,_helpers_3b390e48_js__WEBPACK_IMPORTED_MODULE_2__.a)(scrollEl, 'ionScrollEnd', showCaret);
100
+  inputEl.addEventListener('blur', onBlur);
101
+  return () => {
102
+    (0,_helpers_3b390e48_js__WEBPACK_IMPORTED_MODULE_2__.b)(scrollEl, 'ionScrollStart', hideCaret);
103
+    (0,_helpers_3b390e48_js__WEBPACK_IMPORTED_MODULE_2__.b)(scrollEl, 'ionScrollEnd', showCaret);
104
+    inputEl.addEventListener('ionBlur', onBlur);
105
+  };
106
+};
107
+
108
+const SKIP_SELECTOR = 'input, textarea, [no-blur], [contenteditable]';
109
+
110
+const enableInputBlurring = () => {
111
+  let focused = true;
112
+  let didScroll = false;
113
+  const doc = document;
114
+
115
+  const onScroll = () => {
116
+    didScroll = true;
117
+  };
118
+
119
+  const onFocusin = () => {
120
+    focused = true;
121
+  };
122
+
123
+  const onTouchend = ev => {
124
+    // if app did scroll return early
125
+    if (didScroll) {
126
+      didScroll = false;
127
+      return;
128
+    }
129
+
130
+    const active = doc.activeElement;
131
+
132
+    if (!active) {
133
+      return;
134
+    } // only blur if the active element is a text-input or a textarea
135
+
136
+
137
+    if (active.matches(SKIP_SELECTOR)) {
138
+      return;
139
+    } // if the selected target is the active element, do not blur
140
+
141
+
142
+    const tapped = ev.target;
143
+
144
+    if (tapped === active) {
145
+      return;
146
+    }
147
+
148
+    if (tapped.matches(SKIP_SELECTOR) || tapped.closest(SKIP_SELECTOR)) {
149
+      return;
150
+    }
151
+
152
+    focused = false; // TODO: find a better way, why 50ms?
153
+
154
+    setTimeout(() => {
155
+      if (!focused) {
156
+        active.blur();
157
+      }
158
+    }, 50);
159
+  };
160
+
161
+  (0,_helpers_3b390e48_js__WEBPACK_IMPORTED_MODULE_2__.a)(doc, 'ionScrollStart', onScroll);
162
+  doc.addEventListener('focusin', onFocusin, true);
163
+  doc.addEventListener('touchend', onTouchend, false);
164
+  return () => {
165
+    (0,_helpers_3b390e48_js__WEBPACK_IMPORTED_MODULE_2__.b)(doc, 'ionScrollStart', onScroll, true);
166
+    doc.removeEventListener('focusin', onFocusin, true);
167
+    doc.removeEventListener('touchend', onTouchend, false);
168
+  };
169
+};
170
+
171
+const SCROLL_ASSIST_SPEED = 0.3;
172
+
173
+const getScrollData = (componentEl, contentEl, keyboardHeight) => {
174
+  var _a;
175
+
176
+  const itemEl = (_a = componentEl.closest('ion-item,[ion-item]')) !== null && _a !== void 0 ? _a : componentEl;
177
+  return calcScrollData(itemEl.getBoundingClientRect(), contentEl.getBoundingClientRect(), keyboardHeight, componentEl.ownerDocument.defaultView.innerHeight);
178
+};
179
+
180
+const calcScrollData = (inputRect, contentRect, keyboardHeight, platformHeight) => {
181
+  // compute input's Y values relative to the body
182
+  const inputTop = inputRect.top;
183
+  const inputBottom = inputRect.bottom; // compute visible area
184
+
185
+  const visibleAreaTop = contentRect.top;
186
+  const visibleAreaBottom = Math.min(contentRect.bottom, platformHeight - keyboardHeight); // compute safe area
187
+
188
+  const safeAreaTop = visibleAreaTop + 15;
189
+  const safeAreaBottom = visibleAreaBottom * 0.75; // figure out if each edge of the input is within the safe area
190
+
191
+  const distanceToBottom = safeAreaBottom - inputBottom;
192
+  const distanceToTop = safeAreaTop - inputTop; // desiredScrollAmount is the negated distance to the safe area according to our calculations.
193
+
194
+  const desiredScrollAmount = Math.round(distanceToBottom < 0 ? -distanceToBottom : distanceToTop > 0 ? -distanceToTop : 0); // our calculations make some assumptions that aren't always true, like the keyboard being closed when an input
195
+  // gets focus, so make sure we don't scroll the input above the visible area
196
+
197
+  const scrollAmount = Math.min(desiredScrollAmount, inputTop - visibleAreaTop);
198
+  const distance = Math.abs(scrollAmount);
199
+  const duration = distance / SCROLL_ASSIST_SPEED;
200
+  const scrollDuration = Math.min(400, Math.max(150, duration));
201
+  return {
202
+    scrollAmount,
203
+    scrollDuration,
204
+    scrollPadding: keyboardHeight,
205
+    inputSafeY: -(inputTop - safeAreaTop) + 4
206
+  };
207
+};
208
+
209
+const enableScrollAssist = (componentEl, inputEl, contentEl, footerEl, keyboardHeight) => {
210
+  let coord;
211
+
212
+  const touchStart = ev => {
213
+    coord = (0,_helpers_3b390e48_js__WEBPACK_IMPORTED_MODULE_2__.p)(ev);
214
+  };
215
+
216
+  const touchEnd = ev => {
217
+    // input cover touchend/mouseup
218
+    if (!coord) {
219
+      return;
220
+    } // get where the touchend/mouseup ended
221
+
222
+
223
+    const endCoord = (0,_helpers_3b390e48_js__WEBPACK_IMPORTED_MODULE_2__.p)(ev); // focus this input if the pointer hasn't moved XX pixels
224
+    // and the input doesn't already have focus
225
+
226
+    if (!hasPointerMoved(6, coord, endCoord) && !isFocused(inputEl)) {
227
+      // begin the input focus process
228
+      jsSetFocus(componentEl, inputEl, contentEl, footerEl, keyboardHeight);
229
+    }
230
+  };
231
+
232
+  componentEl.addEventListener('touchstart', touchStart, {
233
+    capture: true,
234
+    passive: true
235
+  });
236
+  componentEl.addEventListener('touchend', touchEnd, true);
237
+  return () => {
238
+    componentEl.removeEventListener('touchstart', touchStart, true);
239
+    componentEl.removeEventListener('touchend', touchEnd, true);
240
+  };
241
+};
242
+
243
+const jsSetFocus = /*#__PURE__*/function () {
244
+  var _ref = (0,_Users_simplicoltd_projects_fm99_rev_node_modules_babel_runtime_helpers_esm_asyncToGenerator_js__WEBPACK_IMPORTED_MODULE_0__["default"])(function* (componentEl, inputEl, contentEl, footerEl, keyboardHeight) {
245
+    if (!contentEl && !footerEl) {
246
+      return;
247
+    }
248
+
249
+    const scrollData = getScrollData(componentEl, contentEl || footerEl, keyboardHeight);
250
+
251
+    if (contentEl && Math.abs(scrollData.scrollAmount) < 4) {
252
+      // the text input is in a safe position that doesn't
253
+      // require it to be scrolled into view, just set focus now
254
+      inputEl.focus();
255
+      return;
256
+    } // temporarily move the focus to the focus holder so the browser
257
+    // doesn't freak out while it's trying to get the input in place
258
+    // at this point the native text input still does not have focus
259
+
260
+
261
+    relocateInput(componentEl, inputEl, true, scrollData.inputSafeY);
262
+    inputEl.focus();
263
+    /**
264
+     * Relocating/Focusing input causes the
265
+     * click event to be cancelled, so
266
+     * manually fire one here.
267
+     */
268
+
269
+    (0,_helpers_3b390e48_js__WEBPACK_IMPORTED_MODULE_2__.r)(() => componentEl.click());
270
+
271
+    if (typeof window !== 'undefined') {
272
+      let scrollContentTimeout;
273
+
274
+      const scrollContent = /*#__PURE__*/function () {
275
+        var _ref2 = (0,_Users_simplicoltd_projects_fm99_rev_node_modules_babel_runtime_helpers_esm_asyncToGenerator_js__WEBPACK_IMPORTED_MODULE_0__["default"])(function* () {
276
+          // clean up listeners and timeouts
277
+          if (scrollContentTimeout !== undefined) {
278
+            clearTimeout(scrollContentTimeout);
279
+          }
280
+
281
+          window.removeEventListener('ionKeyboardDidShow', doubleKeyboardEventListener);
282
+          window.removeEventListener('ionKeyboardDidShow', scrollContent); // scroll the input into place
283
+
284
+          if (contentEl) {
285
+            yield (0,_index_5d0c8232_js__WEBPACK_IMPORTED_MODULE_1__.c)(contentEl, 0, scrollData.scrollAmount, scrollData.scrollDuration);
286
+          } // the scroll view is in the correct position now
287
+          // give the native text input focus
288
+
289
+
290
+          relocateInput(componentEl, inputEl, false, scrollData.inputSafeY); // ensure this is the focused input
291
+
292
+          inputEl.focus();
293
+        });
294
+
295
+        return function scrollContent() {
296
+          return _ref2.apply(this, arguments);
297
+        };
298
+      }();
299
+
300
+      const doubleKeyboardEventListener = () => {
301
+        window.removeEventListener('ionKeyboardDidShow', doubleKeyboardEventListener);
302
+        window.addEventListener('ionKeyboardDidShow', scrollContent);
303
+      };
304
+
305
+      if (contentEl) {
306
+        const scrollEl = yield (0,_index_5d0c8232_js__WEBPACK_IMPORTED_MODULE_1__.g)(contentEl);
307
+        /**
308
+         * scrollData will only consider the amount we need
309
+         * to scroll in order to properly bring the input
310
+         * into view. It will not consider the amount
311
+         * we can scroll in the content element.
312
+         * As a result, scrollData may request a greater
313
+         * scroll position than is currently available
314
+         * in the DOM. If this is the case, we need to
315
+         * wait for the webview to resize/the keyboard
316
+         * to show in order for additional scroll
317
+         * bandwidth to become available.
318
+         */
319
+
320
+        const totalScrollAmount = scrollEl.scrollHeight - scrollEl.clientHeight;
321
+
322
+        if (scrollData.scrollAmount > totalScrollAmount - scrollEl.scrollTop) {
323
+          /**
324
+           * On iOS devices, the system will show a "Passwords" bar above the keyboard
325
+           * after the initial keyboard is shown. This prevents the webview from resizing
326
+           * until the "Passwords" bar is shown, so we need to wait for that to happen first.
327
+           */
328
+          if (inputEl.type === 'password') {
329
+            // Add 50px to account for the "Passwords" bar
330
+            scrollData.scrollAmount += 50;
331
+            window.addEventListener('ionKeyboardDidShow', doubleKeyboardEventListener);
332
+          } else {
333
+            window.addEventListener('ionKeyboardDidShow', scrollContent);
334
+          }
335
+          /**
336
+           * This should only fire in 2 instances:
337
+           * 1. The app is very slow.
338
+           * 2. The app is running in a browser on an old OS
339
+           * that does not support Ionic Keyboard Events
340
+           */
341
+
342
+
343
+          scrollContentTimeout = setTimeout(scrollContent, 1000);
344
+          return;
345
+        }
346
+      }
347
+
348
+      scrollContent();
349
+    }
350
+  });
351
+
352
+  return function jsSetFocus(_x, _x2, _x3, _x4, _x5) {
353
+    return _ref.apply(this, arguments);
354
+  };
355
+}();
356
+
357
+const hasPointerMoved = (threshold, startCoord, endCoord) => {
358
+  if (startCoord && endCoord) {
359
+    const deltaX = startCoord.x - endCoord.x;
360
+    const deltaY = startCoord.y - endCoord.y;
361
+    const distance = deltaX * deltaX + deltaY * deltaY;
362
+    return distance > threshold * threshold;
363
+  }
364
+
365
+  return false;
366
+};
367
+
368
+const PADDING_TIMER_KEY = '$ionPaddingTimer';
369
+
370
+const enableScrollPadding = keyboardHeight => {
371
+  const doc = document;
372
+
373
+  const onFocusin = ev => {
374
+    setScrollPadding(ev.target, keyboardHeight);
375
+  };
376
+
377
+  const onFocusout = ev => {
378
+    setScrollPadding(ev.target, 0);
379
+  };
380
+
381
+  doc.addEventListener('focusin', onFocusin);
382
+  doc.addEventListener('focusout', onFocusout);
383
+  return () => {
384
+    doc.removeEventListener('focusin', onFocusin);
385
+    doc.removeEventListener('focusout', onFocusout);
386
+  };
387
+};
388
+
389
+const setScrollPadding = (input, keyboardHeight) => {
390
+  var _a, _b;
391
+
392
+  if (input.tagName !== 'INPUT') {
393
+    return;
394
+  }
395
+
396
+  if (input.parentElement && input.parentElement.tagName === 'ION-INPUT') {
397
+    return;
398
+  }
399
+
400
+  if (((_b = (_a = input.parentElement) === null || _a === void 0 ? void 0 : _a.parentElement) === null || _b === void 0 ? void 0 : _b.tagName) === 'ION-SEARCHBAR') {
401
+    return;
402
+  }
403
+
404
+  const el = (0,_index_5d0c8232_js__WEBPACK_IMPORTED_MODULE_1__.f)(input);
405
+
406
+  if (el === null) {
407
+    return;
408
+  }
409
+
410
+  const timer = el[PADDING_TIMER_KEY];
411
+
412
+  if (timer) {
413
+    clearTimeout(timer);
414
+  }
415
+
416
+  if (keyboardHeight > 0) {
417
+    el.style.setProperty('--keyboard-offset', `${keyboardHeight}px`);
418
+  } else {
419
+    el[PADDING_TIMER_KEY] = setTimeout(() => {
420
+      el.style.setProperty('--keyboard-offset', '0px');
421
+    }, 120);
422
+  }
423
+};
424
+
425
+const INPUT_BLURRING = true;
426
+const SCROLL_PADDING = true;
427
+
428
+const startInputShims = config => {
429
+  const doc = document;
430
+  const keyboardHeight = config.getNumber('keyboardHeight', 290);
431
+  const scrollAssist = config.getBoolean('scrollAssist', true);
432
+  const hideCaret = config.getBoolean('hideCaretOnScroll', true);
433
+  const inputBlurring = config.getBoolean('inputBlurring', true);
434
+  const scrollPadding = config.getBoolean('scrollPadding', true);
435
+  const inputs = Array.from(doc.querySelectorAll('ion-input, ion-textarea'));
436
+  const hideCaretMap = new WeakMap();
437
+  const scrollAssistMap = new WeakMap();
438
+
439
+  const registerInput = /*#__PURE__*/function () {
440
+    var _ref3 = (0,_Users_simplicoltd_projects_fm99_rev_node_modules_babel_runtime_helpers_esm_asyncToGenerator_js__WEBPACK_IMPORTED_MODULE_0__["default"])(function* (componentEl) {
441
+      yield new Promise(resolve => (0,_helpers_3b390e48_js__WEBPACK_IMPORTED_MODULE_2__.c)(componentEl, resolve));
442
+      const inputRoot = componentEl.shadowRoot || componentEl;
443
+      const inputEl = inputRoot.querySelector('input') || inputRoot.querySelector('textarea');
444
+      const scrollEl = (0,_index_5d0c8232_js__WEBPACK_IMPORTED_MODULE_1__.f)(componentEl);
445
+      const footerEl = !scrollEl ? componentEl.closest('ion-footer') : null;
446
+
447
+      if (!inputEl) {
448
+        return;
449
+      }
450
+
451
+      if (!!scrollEl && hideCaret && !hideCaretMap.has(componentEl)) {
452
+        const rmFn = enableHideCaretOnScroll(componentEl, inputEl, scrollEl);
453
+        hideCaretMap.set(componentEl, rmFn);
454
+      }
455
+      /**
456
+       * date/datetime-locale inputs on mobile devices show date picker
457
+       * overlays instead of keyboards. As a result, scroll assist is
458
+       * not needed. This also works around a bug in iOS <16 where
459
+       * scroll assist causes the browser to lock up. See FW-1997.
460
+       */
461
+
462
+
463
+      const isDateInput = inputEl.type === 'date' || inputEl.type === 'datetime-local';
464
+
465
+      if (!isDateInput && (!!scrollEl || !!footerEl) && scrollAssist && !scrollAssistMap.has(componentEl)) {
466
+        const rmFn = enableScrollAssist(componentEl, inputEl, scrollEl, footerEl, keyboardHeight);
467
+        scrollAssistMap.set(componentEl, rmFn);
468
+      }
469
+    });
470
+
471
+    return function registerInput(_x6) {
472
+      return _ref3.apply(this, arguments);
473
+    };
474
+  }();
475
+
476
+  const unregisterInput = componentEl => {
477
+    if (hideCaret) {
478
+      const fn = hideCaretMap.get(componentEl);
479
+
480
+      if (fn) {
481
+        fn();
482
+      }
483
+
484
+      hideCaretMap.delete(componentEl);
485
+    }
486
+
487
+    if (scrollAssist) {
488
+      const fn = scrollAssistMap.get(componentEl);
489
+
490
+      if (fn) {
491
+        fn();
492
+      }
493
+
494
+      scrollAssistMap.delete(componentEl);
495
+    }
496
+  };
497
+
498
+  if (inputBlurring && INPUT_BLURRING) {
499
+    enableInputBlurring();
500
+  }
501
+
502
+  if (scrollPadding && SCROLL_PADDING) {
503
+    enableScrollPadding(keyboardHeight);
504
+  } // Input might be already loaded in the DOM before ion-device-hacks did.
505
+  // At this point we need to look for all of the inputs not registered yet
506
+  // and register them.
507
+
508
+
509
+  for (const input of inputs) {
510
+    registerInput(input);
511
+  }
512
+
513
+  doc.addEventListener('ionInputDidLoad', ev => {
514
+    registerInput(ev.detail);
515
+  });
516
+  doc.addEventListener('ionInputDidUnload', ev => {
517
+    unregisterInput(ev.detail);
518
+  });
519
+};
520
+
521
+
522
+
523
+/***/ })
524
+
525
+}]);
526
+//# sourceMappingURL=node_modules_ionic_core_dist_esm_input-shims-6ed8f5a5_js.js.map

File diff suppressed because it is too large
+ 1 - 0
www/node_modules_ionic_core_dist_esm_input-shims-6ed8f5a5_js.js.map


File diff suppressed because it is too large
+ 799 - 0
www/node_modules_ionic_core_dist_esm_ion-accordion_2_entry_js.js


File diff suppressed because it is too large
+ 1 - 0
www/node_modules_ionic_core_dist_esm_ion-accordion_2_entry_js.js.map


File diff suppressed because it is too large
+ 450 - 0
www/node_modules_ionic_core_dist_esm_ion-action-sheet_entry_js.js


File diff suppressed because it is too large
+ 1 - 0
www/node_modules_ionic_core_dist_esm_ion-action-sheet_entry_js.js.map


File diff suppressed because it is too large
+ 780 - 0
www/node_modules_ionic_core_dist_esm_ion-alert_entry_js.js


File diff suppressed because it is too large
+ 1 - 0
www/node_modules_ionic_core_dist_esm_ion-alert_entry_js.js.map


File diff suppressed because it is too large
+ 1726 - 0
www/node_modules_ionic_core_dist_esm_ion-app_8_entry_js.js


File diff suppressed because it is too large
+ 1 - 0
www/node_modules_ionic_core_dist_esm_ion-app_8_entry_js.js.map


File diff suppressed because it is too large
+ 160 - 0
www/node_modules_ionic_core_dist_esm_ion-avatar_3_entry_js.js


File diff suppressed because it is too large
+ 1 - 0
www/node_modules_ionic_core_dist_esm_ion-avatar_3_entry_js.js.map


File diff suppressed because it is too large
+ 257 - 0
www/node_modules_ionic_core_dist_esm_ion-back-button_entry_js.js


File diff suppressed because it is too large
+ 1 - 0
www/node_modules_ionic_core_dist_esm_ion-back-button_entry_js.js.map


+ 97 - 0
www/node_modules_ionic_core_dist_esm_ion-backdrop_entry_js.js

@@ -0,0 +1,97 @@
1
+"use strict";
2
+(self["webpackChunkapp"] = self["webpackChunkapp"] || []).push([["node_modules_ionic_core_dist_esm_ion-backdrop_entry_js"],{
3
+
4
+/***/ 3059:
5
+/*!*****************************************************************!*\
6
+  !*** ./node_modules/@ionic/core/dist/esm/ion-backdrop.entry.js ***!
7
+  \*****************************************************************/
8
+/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
9
+
10
+__webpack_require__.r(__webpack_exports__);
11
+/* harmony export */ __webpack_require__.d(__webpack_exports__, {
12
+/* harmony export */   "ion_backdrop": () => (/* binding */ Backdrop)
13
+/* harmony export */ });
14
+/* harmony import */ var _index_8e692445_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./index-8e692445.js */ 1559);
15
+/* harmony import */ var _ionic_global_c95cf239_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./ionic-global-c95cf239.js */ 8607);
16
+/* harmony import */ var _gesture_controller_17060b7c_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./gesture-controller-17060b7c.js */ 6379);
17
+/*!
18
+ * (C) Ionic http://ionicframework.com - MIT License
19
+ */
20
+
21
+
22
+
23
+const backdropIosCss = ":host{left:0;right:0;top:0;bottom:0;display:block;position:absolute;-webkit-transform:translateZ(0);transform:translateZ(0);contain:strict;cursor:pointer;opacity:0.01;-ms-touch-action:none;touch-action:none;z-index:2}:host(.backdrop-hide){background:transparent}:host(.backdrop-no-tappable){cursor:auto}:host{background-color:var(--ion-backdrop-color, #000)}";
24
+const backdropMdCss = ":host{left:0;right:0;top:0;bottom:0;display:block;position:absolute;-webkit-transform:translateZ(0);transform:translateZ(0);contain:strict;cursor:pointer;opacity:0.01;-ms-touch-action:none;touch-action:none;z-index:2}:host(.backdrop-hide){background:transparent}:host(.backdrop-no-tappable){cursor:auto}:host{background-color:var(--ion-backdrop-color, #000)}";
25
+const Backdrop = class {
26
+  constructor(hostRef) {
27
+    (0,_index_8e692445_js__WEBPACK_IMPORTED_MODULE_0__.r)(this, hostRef);
28
+    this.ionBackdropTap = (0,_index_8e692445_js__WEBPACK_IMPORTED_MODULE_0__.e)(this, "ionBackdropTap", 7);
29
+    this.blocker = _gesture_controller_17060b7c_js__WEBPACK_IMPORTED_MODULE_2__.G.createBlocker({
30
+      disableScroll: true
31
+    });
32
+    /**
33
+     * If `true`, the backdrop will be visible.
34
+     */
35
+
36
+    this.visible = true;
37
+    /**
38
+     * If `true`, the backdrop will can be clicked and will emit the `ionBackdropTap` event.
39
+     */
40
+
41
+    this.tappable = true;
42
+    /**
43
+     * If `true`, the backdrop will stop propagation on tap.
44
+     */
45
+
46
+    this.stopPropagation = true;
47
+  }
48
+
49
+  connectedCallback() {
50
+    if (this.stopPropagation) {
51
+      this.blocker.block();
52
+    }
53
+  }
54
+
55
+  disconnectedCallback() {
56
+    this.blocker.unblock();
57
+  }
58
+
59
+  onMouseDown(ev) {
60
+    this.emitTap(ev);
61
+  }
62
+
63
+  emitTap(ev) {
64
+    if (this.stopPropagation) {
65
+      ev.preventDefault();
66
+      ev.stopPropagation();
67
+    }
68
+
69
+    if (this.tappable) {
70
+      this.ionBackdropTap.emit();
71
+    }
72
+  }
73
+
74
+  render() {
75
+    const mode = (0,_ionic_global_c95cf239_js__WEBPACK_IMPORTED_MODULE_1__.b)(this);
76
+    return (0,_index_8e692445_js__WEBPACK_IMPORTED_MODULE_0__.h)(_index_8e692445_js__WEBPACK_IMPORTED_MODULE_0__.H, {
77
+      tabindex: "-1",
78
+      "aria-hidden": "true",
79
+      class: {
80
+        [mode]: true,
81
+        'backdrop-hide': !this.visible,
82
+        'backdrop-no-tappable': !this.tappable
83
+      }
84
+    });
85
+  }
86
+
87
+};
88
+Backdrop.style = {
89
+  ios: backdropIosCss,
90
+  md: backdropMdCss
91
+};
92
+
93
+
94
+/***/ })
95
+
96
+}]);
97
+//# sourceMappingURL=node_modules_ionic_core_dist_esm_ion-backdrop_entry_js.js.map

File diff suppressed because it is too large
+ 1 - 0
www/node_modules_ionic_core_dist_esm_ion-backdrop_entry_js.js.map


File diff suppressed because it is too large
+ 414 - 0
www/node_modules_ionic_core_dist_esm_ion-breadcrumb_2_entry_js.js


File diff suppressed because it is too large
+ 1 - 0
www/node_modules_ionic_core_dist_esm_ion-breadcrumb_2_entry_js.js.map


File diff suppressed because it is too large
+ 770 - 0
www/node_modules_ionic_core_dist_esm_ion-button_2_entry_js.js


File diff suppressed because it is too large
+ 1 - 0
www/node_modules_ionic_core_dist_esm_ion-button_2_entry_js.js.map


File diff suppressed because it is too large
+ 299 - 0
www/node_modules_ionic_core_dist_esm_ion-card_5_entry_js.js


+ 0 - 0
www/node_modules_ionic_core_dist_esm_ion-card_5_entry_js.js.map


Some files were not shown because too many files changed in this diff

user form · af6cd6e897 - Gogs: Simplico Git Service
tum vor 1 Jahr
Ursprung
Commit
af6cd6e897

+ 4 - 3
app/core/utils.py

@@ -35,8 +35,7 @@ class ConfigurableCRUDView:
35 35
 
36 36
     config_edit_fields = "__all__"  # "all" or a list of field names to display
37 37
     ordering = None
38
-
39
-
38
+    form_class = None
40 39
 
41 40
     def get_fields(self):
42 41
         """
@@ -139,6 +138,7 @@ class ConfigurableCRUDView:
139 138
             model = self.model
140 139
             template_name = self.form_template_name
141 140
             fields = self.config_edit_fields
141
+            form_class = self.form_class
142 142
 
143 143
             def form_valid(inner_self, form):
144 144
                 response = super().form_valid(form)
@@ -165,7 +165,8 @@ class ConfigurableCRUDView:
165 165
             model = self.model
166 166
             template_name = self.form_template_name
167 167
             fields = self.config_edit_fields
168
-
168
+            form_class = self.form_class
169
+            
169 170
             def form_valid(inner_self, form):
170 171
                 response = super().form_valid(form)
171 172
                 messages.success(inner_self.request, f"{self.model._meta.verbose_name} updated successfully!")

+ 37 - 1
app/legacy/filters.py

@@ -1,5 +1,6 @@
1 1
 import django_filters
2
-from .models import Data, DataMs, TbFgPressinfoLotlist, LotSummary
2
+from .models import Data, DataMs, TbFgPressinfoLotlist, LotSummary, DataRl, DataWb, \
3
+        LotSummaryRl, LotSummaryWb
3 4
 
4 5
 class DataFilter(django_filters.FilterSet):
5 6
     lot_no = django_filters.CharFilter(field_name='lot_no', lookup_expr='icontains')
@@ -34,3 +35,38 @@ class LotSummaryFilter(django_filters.FilterSet):
34 35
     class Meta:
35 36
         model = LotSummary
36 37
         fields = ['lot_no', 'code']  # Add fields you want to filter
38
+
39
+
40
+class DataRlFilter(django_filters.FilterSet):
41
+    lot_no = django_filters.CharFilter(field_name='lot_no', lookup_expr='icontains')
42
+    code = django_filters.CharFilter(field_name='code', lookup_expr='icontains')
43
+
44
+    class Meta:
45
+        model = DataRl
46
+        fields = ['lot_no', 'code']  # Add fields you want to filter
47
+
48
+class DataWbFilter(django_filters.FilterSet):
49
+    lot_no = django_filters.CharFilter(field_name='lot_no', lookup_expr='icontains')
50
+    code = django_filters.CharFilter(field_name='code', lookup_expr='icontains')
51
+
52
+    class Meta:
53
+        model = DataWb
54
+        fields = ['lot_no', 'code']  # Add fields you want to filter
55
+
56
+class LotSummaryRlFilter(django_filters.FilterSet):
57
+    lot_no = django_filters.CharFilter(field_name='lot_no', lookup_expr='icontains')
58
+    code = django_filters.CharFilter(field_name='code', lookup_expr='icontains')
59
+
60
+    class Meta:
61
+        model = LotSummaryRl
62
+        fields = ['lot_no', 'code']  # Add fields you want to filter
63
+
64
+
65
+class LotSummaryWbFilter(django_filters.FilterSet):
66
+    lot_no = django_filters.CharFilter(field_name='lot_no', lookup_expr='icontains')
67
+    code = django_filters.CharFilter(field_name='code', lookup_expr='icontains')
68
+
69
+    class Meta:
70
+        model = LotSummaryWb
71
+        fields = ['lot_no', 'code']  # Add fields you want to filter
72
+

+ 1 - 1
app/legacy/templates/legacy/datacrud_list.html

@@ -47,7 +47,7 @@
47 47
                     {% elif field.name == 'file' and obj.file %}
48 48
                     <a href="{{ obj.file.url }}" target="_blank">View</a>
49 49
                     {% else %}
50
-                        {{ obj|attr:field.name }}
50
+                        {{ obj|attr:field.name | safe_floatformat:2 }}
51 51
                     {% endif %}
52 52
                 </td>
53 53
                 {% endfor %}

+ 10 - 0
app/legacy/templatetags/legacy_filters.py

@@ -1,5 +1,6 @@
1 1
 from django import template
2 2
 from django.template import Context
3
+from django.template.defaultfilters import floatformat
3 4
 
4 5
 
5 6
 register = template.Library()
@@ -27,3 +28,12 @@ def render_breadcrumbs(context, breadcrumbs):
27 28
     Context({
28 29
         'breadcrumbs': breadcrumbs
29 30
     }))
31
+
32
+@register.filter
33
+def safe_floatformat(value, decimal_places):
34
+    try:
35
+        # Attempt to convert the value to a float and format it
36
+        return floatformat(float(value), decimal_places)
37
+    except (ValueError, TypeError):
38
+        # If the value is not a number, return it as is
39
+        return value

+ 26 - 2
app/legacy/urls.py

@@ -1,6 +1,7 @@
1 1
 from django.urls import path
2 2
 from .views import DataListView, DataDetailView, DataCreateView, DataUpdateView, DataDeleteView,\
3
-DataMsCRUDView, TbFgPressInfoLotListCRUDView, LotSummaryCRUDView, VMasterViewCRUDView, MgMasterViewCRUDView, BelMasterViewCRUDView, EMasterViewCRUDView
3
+DataMsCRUDView, TbFgPressInfoLotListCRUDView, LotSummaryCRUDView, VMasterViewCRUDView, MgMasterViewCRUDView,\
4
+BelMasterViewCRUDView, EMasterViewCRUDView, DataRLCRUDView, DataWbCRUDView, LotSummaryRlCRUDView, LotSummaryWbCRUDView
4 5
 
5 6
 app_name = 'legacy'  # Namespace for this app
6 7
 
@@ -11,7 +12,10 @@ vm_crud = VMasterViewCRUDView()
11 12
 mg_crud = MgMasterViewCRUDView()
12 13
 bel_crud = BelMasterViewCRUDView()
13 14
 em_crud = EMasterViewCRUDView()
14
-
15
+datarl_crud = DataRLCRUDView()
16
+datawb_crud = DataWbCRUDView()
17
+lsrl_crud = LotSummaryRlCRUDView()
18
+lswb_crud = LotSummaryWbCRUDView()
15 19
 
16 20
 urlpatterns = [
17 21
     path('data/', DataListView.as_view(), name='data-list'),            # data/
@@ -24,6 +28,16 @@ urlpatterns = [
24 28
     path('datams/create/', datams_crud.get_create_view().as_view(), name='datams-create'),
25 29
     path('datams/<int:pk>/update/', datams_crud.get_update_view().as_view(), name='datams-update'),
26 30
     path('datams/<int:pk>/delete/', datams_crud.get_delete_view().as_view(), name='datams-delete'),
31
+
32
+    path('datarl/', datarl_crud.get_list_view().as_view(), name='datarl-list'),
33
+    path('datarl/create/', datarl_crud.get_create_view().as_view(), name='datarl-create'),
34
+    path('datarl/<int:pk>/update/', datarl_crud.get_update_view().as_view(), name='datarl-update'),
35
+    path('datarl/<int:pk>/delete/', datarl_crud.get_delete_view().as_view(), name='datarl-delete'),
36
+
37
+    path('datawb/', datawb_crud.get_list_view().as_view(), name='datawb-list'),
38
+    path('datawb/create/', datawb_crud.get_create_view().as_view(), name='datawb-create'),
39
+    path('datawb/<int:pk>/update/', datawb_crud.get_update_view().as_view(), name='datawb-update'),
40
+    path('datawb/<int:pk>/delete/', datawb_crud.get_delete_view().as_view(), name='datawb-delete'),
27 41
     
28 42
     path('fg/', fg_crud.get_list_view().as_view(), name='fg-list'),
29 43
     path('fg/create/', fg_crud.get_create_view().as_view(), name='fg-create'),
@@ -34,6 +48,7 @@ urlpatterns = [
34 48
     path('ls/create/', ls_crud.get_create_view().as_view(), name='ls-create'),
35 49
     path('ls/<int:pk>/update/', ls_crud.get_update_view().as_view(), name='ls-update'),
36 50
     path('ls/<int:pk>/delete/', ls_crud.get_delete_view().as_view(), name='ls-delete'),
51
+
37 52
     path('vm/', vm_crud.get_list_view().as_view(), name='vm-list'),
38 53
     path('vm/create/', vm_crud.get_create_view().as_view(), name='vm-create'),
39 54
     path('vm/<str:pk>/update/', vm_crud.get_update_view().as_view(), name='vm-update'),
@@ -55,4 +70,13 @@ urlpatterns = [
55 70
     path('em/<str:pk>/delete/', em_crud.get_delete_view().as_view(), name='em-delete'),
56 71
 
57 72
 
73
+    path('lsrl/', lsrl_crud.get_list_view().as_view(), name='lsrl-list'),
74
+    path('lsrl/create/', lsrl_crud.get_create_view().as_view(), name='lsrl-create'),
75
+    path('lsrl/<int:pk>/update/', lsrl_crud.get_update_view().as_view(), name='lsrl-update'),
76
+    path('lsrl/<int:pk>/delete/', lsrl_crud.get_delete_view().as_view(), name='lsrl-delete'),
77
+
78
+    path('lswb/', lswb_crud.get_list_view().as_view(), name='lswb-list'),
79
+    path('lswb/create/', lswb_crud.get_create_view().as_view(), name='lswb-create'),
80
+    path('lswb/<int:pk>/update/', lswb_crud.get_update_view().as_view(), name='lswb-update'),
81
+    path('lswb/<int:pk>/delete/', lswb_crud.get_delete_view().as_view(), name='lswb-delete'),
58 82
 ]

+ 75 - 3
app/legacy/views.py

@@ -11,8 +11,9 @@ from django.views.generic import (
11 11
     DeleteView,
12 12
 )
13 13
 from django.core.paginator import Paginator
14
-from .models import Data, DataMs, TbFgPressinfoLotlist, LotSummary
15
-from .filters import DataFilter, DataMsFilter, TbFgPressFilter, LotSummaryFilter
14
+from .models import Data, DataMs, TbFgPressinfoLotlist, LotSummary, DataRl, DataWb, LotSummaryRl, LotSummaryWb
15
+from .filters import DataFilter, DataMsFilter, TbFgPressFilter, LotSummaryFilter, \
16
+        DataRlFilter, DataWbFilter, LotSummaryRlFilter, LotSummaryWbFilter
16 17
 from django.urls import reverse
17 18
 from django.contrib import messages
18 19
 from pprint import pprint
@@ -158,7 +159,7 @@ class LotSummaryCRUDView(ConfigurableCRUDView):
158 159
     detail_template_name = 'legacy/datacrud_detail.html'
159 160
     form_template_name = 'legacy/datacrud_form.html'
160 161
     confirm_delete_template_name = 'legacy/datacrud_confirm_delete.html'
161
-    filterset_class = DataMsFilter
162
+    filterset_class = LotSummaryFilter
162 163
 
163 164
     page_title = "Lot Summary"
164 165
 
@@ -301,3 +302,74 @@ class EMasterViewCRUDView(ConfigurableCRUDView):
301 302
     # Default ordering
302 303
     # ordering = ["-id", "PRO2"]
303 304
     
305
+class DataRLCRUDView(ConfigurableCRUDView):
306
+    model = DataRl
307
+    list_template_name = 'legacy/datacrud_list.html'
308
+    detail_template_name = 'legacy/datacrud_detail.html'
309
+    form_template_name = 'legacy/datacrud_form.html'
310
+    confirm_delete_template_name = 'legacy/datacrud_confirm_delete.html'
311
+    filterset_class = DataRlFilter
312
+
313
+    page_title = "Data RL"
314
+
315
+    # URL name mappings
316
+    list_url_name = 'legacy:datarl-list'
317
+    create_url_name = 'legacy:datarl-create'
318
+    update_url_name = 'legacy:datarl-update'
319
+    delete_url_name = 'legacy:datarl-delete'
320
+    # excludes = ["splitdata"]
321
+    config_field_orders = ["id", "lot_no", "code"]  # Display these fields first
322
+
323
+class DataWbCRUDView(ConfigurableCRUDView):
324
+    model = DataWb
325
+    list_template_name = 'legacy/datacrud_list.html'
326
+    detail_template_name = 'legacy/datacrud_detail.html'
327
+    form_template_name = 'legacy/datacrud_form.html'
328
+    confirm_delete_template_name = 'legacy/datacrud_confirm_delete.html'
329
+    filterset_class = DataWbFilter
330
+
331
+    page_title = "Data WB"
332
+
333
+    # URL name mappings
334
+    list_url_name = 'legacy:datawb-list'
335
+    create_url_name = 'legacy:datawb-create'
336
+    update_url_name = 'legacy:datawb-update'
337
+    delete_url_name = 'legacy:datawb-delete'
338
+    # excludes = ["splitdata"]
339
+    config_field_orders = ["id", "lot_no", "code"]  # Display these fields first
340
+
341
+class LotSummaryRlCRUDView(ConfigurableCRUDView):
342
+    model = LotSummaryRl
343
+    list_template_name = 'legacy/datacrud_list.html'
344
+    detail_template_name = 'legacy/datacrud_detail.html'
345
+    form_template_name = 'legacy/datacrud_form.html'
346
+    confirm_delete_template_name = 'legacy/datacrud_confirm_delete.html'
347
+    filterset_class = LotSummaryRlFilter
348
+
349
+    page_title = "Lot Summary RL"
350
+
351
+    # URL name mappings
352
+    list_url_name = 'legacy:lsrl-list'
353
+    create_url_name = 'legacy:lsrl-create'
354
+    update_url_name = 'legacy:lsrl-update'
355
+    delete_url_name = 'legacy:lsrl-delete'
356
+    # excludes = ["splitdata"]
357
+    config_field_orders = ["id", "lot_no", "code"]  # Display these fields first
358
+
359
+class LotSummaryWbCRUDView(ConfigurableCRUDView):
360
+    model = LotSummaryWb
361
+    list_template_name = 'legacy/datacrud_list.html'
362
+    detail_template_name = 'legacy/datacrud_detail.html'
363
+    form_template_name = 'legacy/datacrud_form.html'
364
+    confirm_delete_template_name = 'legacy/datacrud_confirm_delete.html'
365
+    filterset_class = LotSummaryWbFilter
366
+
367
+    page_title = "Lot Summary WB"
368
+
369
+    # URL name mappings
370
+    list_url_name = 'legacy:lswb-list'
371
+    create_url_name = 'legacy:lswb-create'
372
+    update_url_name = 'legacy:lswb-update'
373
+    delete_url_name = 'legacy:lswb-delete'
374
+    # excludes = ["splitdata"]
375
+    config_field_orders = ["id", "lot_no", "code"]  # Display these fields first

BIN
app/report/coi_templates.xlsx


+ 20 - 0
app/report/templates/report/coi.html

@@ -32,6 +32,26 @@
32 32
         </label>
33 33
         {% endfor %}
34 34
         </div>
35
+        <div class="grid grid-cols-2 gap-4">
36
+          <div class="my-4">
37
+            <label for="qa1" class="block mb-2 text-sm font-medium text-gray-900">Select QA.1</label>
38
+            <select id="qa1" name="qa1" class="block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500">
39
+              <option value="" disabled selected>Choose a user</option>
40
+              {% for user in users %}
41
+              <option value="{{ user.id }}">{{ user.profile }}</option>
42
+              {% endfor %}
43
+            </select>
44
+          </div>
45
+          <div class="my-4">
46
+            <label for="qa2" class="block mb-2 text-sm font-medium text-gray-900">Select QA.2</label>
47
+            <select id="qa2" name="qa2" class="block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500">
48
+              <option value="" disabled selected>Choose a user</option>
49
+              {% for user in users %}
50
+              <option value="{{ user.id }}">{{ user.profile }}</option>
51
+              {% endfor %}
52
+            </select>
53
+          </div>
54
+        </div>
35 55
         <div class="flex justify-end my-3 space-x-4">
36 56
           <div>
37 57
             <button  type='button' class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600" 

+ 5 - 1
app/report/views.py

@@ -15,6 +15,8 @@ from django.views.decorators.csrf import csrf_exempt
15 15
 from django.http import JsonResponse, HttpResponseBadRequest
16 16
 import json
17 17
 from django.contrib.auth.decorators import login_required
18
+from django.contrib.auth.models import User
19
+
18 20
 
19 21
 def index(request):
20 22
     reports = Report.objects.all()
@@ -127,6 +129,8 @@ SHEET_NAMES = {
127 129
 }
128 130
 def coi_view(request):
129 131
     pprint(f"xxxx method = xxx {request.method}")
132
+    users = User.objects.all()
133
+
130 134
     if request.method == "POST":
131 135
         pprint(request.POST)
132 136
         exports = request.POST.getlist("exports")  # Retrieve the list of selected values
@@ -187,7 +191,7 @@ def coi_view(request):
187 191
 
188 192
         messages.success(request, "Request Sent")
189 193
         return redirect(request.path_info)
190
-    return render(request, 'report/coi.html', {'SHEET_NAMES': SHEET_NAMES})
194
+    return render(request, 'report/coi.html', {'SHEET_NAMES': SHEET_NAMES, 'users': users})
191 195
 
192 196
 
193 197
 @csrf_exempt  # Disable CSRF for API requests (ensure this is secure in production)

BIN
app/report/~$coi_templates.xlsx


+ 12 - 0
app/sysadmin/filters.py

@@ -0,0 +1,12 @@
1
+
2
+import django_filters
3
+from django.contrib.auth.models import User
4
+
5
+class UserFilter(django_filters.FilterSet):
6
+    username = django_filters.CharFilter(field_name='username', lookup_expr='icontains')
7
+    first_name = django_filters.CharFilter(field_name='first_name', lookup_expr='icontains')
8
+    last_name = django_filters.CharFilter(field_name='last_name', lookup_expr='icontains')
9
+
10
+    class Meta:
11
+        model = User
12
+        fields = ['username', 'first_name', 'last_name']  # Add fields you want to filter

+ 55 - 4
app/sysadmin/forms.py

@@ -2,6 +2,11 @@
2 2
 from django import forms
3 3
 from django.contrib.auth.forms import AuthenticationForm
4 4
 
5
+from django.contrib.auth.forms import UserCreationForm
6
+from django.contrib.auth.models import User
7
+
8
+from .models import UserProfile
9
+
5 10
 class CustomLoginForm(AuthenticationForm):
6 11
     username = forms.CharField(widget=forms.TextInput(attrs={
7 12
         'placeholder': 'Username'
@@ -11,10 +16,6 @@ class CustomLoginForm(AuthenticationForm):
11 16
     }))
12 17
 
13 18
 # forms.py
14
-from django.contrib.auth.forms import UserCreationForm
15
-from django.contrib.auth.models import User
16
-
17
-from .models import UserProfile
18 19
 
19 20
 class CustomUserCreationForm(UserCreationForm):
20 21
     class Meta:
@@ -41,3 +42,53 @@ class UserProfileForm(forms.ModelForm):
41 42
     class Meta:
42 43
         model = UserProfile
43 44
         fields = ['profile_picture', 'position', 'signed_picture']  # Include the fields you want to manage
45
+
46
+class UserCustomForm(forms.ModelForm):
47
+    # Profile fields
48
+    profile_picture = forms.ImageField(required=False, label="Profile Picture")
49
+    signed_picture = forms.ImageField(required=False, label="Signed Picture")
50
+    position = forms.ChoiceField(
51
+        choices=UserProfile.POSITION_CHOICES, 
52
+        required=False, 
53
+        label="Position"
54
+    )
55
+
56
+    class Meta:
57
+        model = User
58
+        fields = [
59
+            "first_name",
60
+            "last_name",
61
+            "is_staff",
62
+            "is_superuser",
63
+            "is_active",
64
+        ]
65
+
66
+    def __init__(self, *args, **kwargs):
67
+        # Allow passing `instance` to access both User and UserProfile objects
68
+        user_instance = kwargs.pop('instance', None)
69
+        profile_instance = getattr(user_instance, 'profile', None)
70
+        super().__init__(instance=user_instance, *args, **kwargs)
71
+
72
+        # Populate initial data for profile fields if `profile` exists
73
+        if profile_instance:
74
+            self.fields['profile_picture'].initial = profile_instance.profile_picture
75
+            self.fields['signed_picture'].initial = profile_instance.signed_picture
76
+            self.fields['position'].initial = profile_instance.position
77
+
78
+    def save(self, commit=True):
79
+        # Save the User instance first
80
+        user = super().save(commit=commit)
81
+        profile_data = {
82
+            'profile_picture': self.cleaned_data.get('profile_picture'),
83
+            'signed_picture': self.cleaned_data.get('signed_picture'),
84
+            'position': self.cleaned_data.get('position'),
85
+        }
86
+
87
+        # Ensure profile exists for the user
88
+        profile, created = UserProfile.objects.get_or_create(user=user)
89
+        for key, value in profile_data.items():
90
+            setattr(profile, key, value)
91
+        if commit:
92
+            profile.save()
93
+
94
+        return user

+ 4 - 2
app/sysadmin/models.py

@@ -7,7 +7,9 @@ from django.contrib.auth.models import User
7 7
 class UserProfile(models.Model):
8 8
     POSITION_CHOICES = [
9 9
         ('QA_STAFF', 'QA Staff'),
10
-        ('QA_MANAGER', 'QA Manager'),
10
+        ('QA_MANAGER', 'QA. MG.'),
11
+        ('QA_AST_MANAGER', 'QA. Asst. MG.'),
12
+        ('QA_ENGINEER', 'QA. Engineer'),
11 13
     ]
12 14
     user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
13 15
     bio = models.TextField(blank=True, null=True)
@@ -17,4 +19,4 @@ class UserProfile(models.Model):
17 19
     position = models.CharField(max_length=20, choices=POSITION_CHOICES, blank=True, null=True)  # New position field
18 20
 
19 21
     def __str__(self):
20
-        return self.user.username
22
+        return f"{self.user.username} / {self.user.first_name} {self.user.last_name} #{self.position}"

+ 71 - 0
app/sysadmin/templates/sysadmin/user_form.html

@@ -0,0 +1,71 @@
1
+{% extends "base.html" %}
2
+
3
+{% load legacy_filters %}
4
+{% load tailwind_filters %}
5
+{% load crispy_forms_filters %}
6
+
7
+{% block title %}
8
+    {% if view.title %}
9
+        {{ view.title }}
10
+    {% else %}
11
+        {{ view|class_name }}
12
+    {% endif %}
13
+{% endblock %}
14
+
15
+{% block content %}
16
+<div class="container mx-auto px-4 py-6">
17
+    <h1 class="text-2xl font-bold mb-6">
18
+        {% if view.title %}
19
+            {{ view.title }}
20
+        {% elif view|class_name == "CreateViewClass" %}
21
+            Create {{ model_verbose_name }}
22
+        {% else %}
23
+            Update {{ model_verbose_name }}
24
+        {% endif %}
25
+    </h1>
26
+
27
+    <!-- Render the Form -->
28
+    <form method="post" enctype="multipart/form-data">
29
+        {% csrf_token %}
30
+        <div class="flex flex-wrap items-center space-x-4">
31
+            <!-- Show profile_picture -->
32
+            <div class=" mb-4">
33
+                {% if form.instance.profile and form.instance.profile.profile_picture %}
34
+                    <div class="mb-2">
35
+                        <img src="{{ form.instance.profile.profile_picture.url }}" alt="Profile Picture" class="max-w-xs border rounded" width=100>
36
+                    </div>
37
+                {% endif %}
38
+                {{ form.profile_picture|as_crispy_field }}
39
+            </div>
40
+
41
+            <!-- Show signed_picture -->
42
+            <div class=" mb-4">
43
+                {% if form.instance.profile and form.instance.profile.signed_picture %}
44
+                    <div class="mb-2">
45
+                        <img src="{{ form.instance.profile.signed_picture.url }}" alt="Signed Picture" class="max-w-xs border rounded" width=100>
46
+                    </div>
47
+                {% endif %}
48
+                {{ form.signed_picture|as_crispy_field }}
49
+            </div>
50
+
51
+            <!-- Render other form fields -->
52
+            {% for field in form %}
53
+                {% if field.name not in "profile_picture signed_picture" %}
54
+                    <div class=" mb-4">
55
+                        {{ field|as_crispy_field }}
56
+                    </div>
57
+                {% endif %}
58
+            {% endfor %}
59
+        </div>
60
+
61
+        <div class="mt-4">
62
+            <button type="submit" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
63
+                Save
64
+            </button>
65
+            <a href="{% url list_url_name %}" class="bg-gray-300 text-gray-800 px-4 py-2 rounded hover:bg-gray-400">
66
+                Cancel
67
+            </a>
68
+        </div>
69
+    </form>
70
+</div>
71
+{% endblock %}

+ 6 - 0
app/sysadmin/urls.py

@@ -3,10 +3,16 @@ from . import views
3 3
 
4 4
 app_name = "sysadmin"  # Use this namespace for reverse URL lookups
5 5
 
6
+users_crud = views.UserCRUDView()
7
+
6 8
 urlpatterns = [
7 9
     path('login/', views.login_view, name='login'),
8 10
     path('register/', views.register_view, name='register'),
9 11
     path('logout/', views.logout_view, name='logout'),
10 12
     path('profile/', views.profile_view, name='profile'),  # Add profile view URL
11 13
 
14
+    path('users/', users_crud.get_list_view().as_view(), name='users-list'),
15
+    path('users/create/', users_crud.get_create_view().as_view(), name='users-create'),
16
+    path('users/<int:pk>/update/', users_crud.get_update_view().as_view(), name='users-update'),
17
+    path('users/<int:pk>/delete/', users_crud.get_delete_view().as_view(), name='users-delete'),
12 18
 ]

+ 26 - 1
app/sysadmin/views.py

@@ -1,10 +1,13 @@
1 1
 # views.py
2 2
 from django.contrib.auth import authenticate, login, logout
3 3
 from django.shortcuts import render, redirect
4
-from .forms import CustomLoginForm, CustomUserCreationForm, UserProfileForm
4
+from .forms import CustomLoginForm, CustomUserCreationForm, UserProfileForm, UserCustomForm
5 5
 from .models import UserProfile
6
+from .filters import UserFilter
6 7
 from django.contrib.auth.decorators import login_required
7 8
 from django.contrib import messages
9
+from core.utils import ConfigurableCRUDView
10
+from django.contrib.auth.models import User
8 11
 
9 12
 def login_view(request):
10 13
     if request.method == "POST":
@@ -61,3 +64,25 @@ def profile_view(request):
61 64
         form = UserProfileForm(instance=profile)
62 65
 
63 66
     return render(request, 'sysadmin/profile.html', {'form': form})
67
+
68
+
69
+class UserCRUDView(ConfigurableCRUDView):
70
+    model = User
71
+    list_template_name = 'legacy/datacrud_list.html'
72
+    detail_template_name = 'legacy/datacrud_detail.html'
73
+    form_template_name = 'sysadmin/user_form.html'
74
+    confirm_delete_template_name = 'legacy/datacrud_confirm_delete.html'
75
+    filterset_class = UserFilter
76
+
77
+    page_title = "Users"
78
+
79
+    # URL name mappings
80
+    list_url_name = 'sysadmin:users-list'
81
+    create_url_name = 'sysadmin:users-create'
82
+    update_url_name = 'sysadmin:users-update'
83
+    delete_url_name = 'sysadmin:users-delete'
84
+    # excludes = ["splitdata"]
85
+    config_fields = ("id", "username", "is_active", "is_staff", "is_superuser", "last_login")
86
+    config_field_orders = ["id",]  # Display these fields first
87
+    form_class = UserCustomForm
88
+    config_edit_fields = None

+ 33 - 4
app/templates/base.html

@@ -82,6 +82,15 @@
82 82
                             <a href="{% url "legacy:data-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">Data</a>
83 83
                           </li>
84 84
                           <li>
85
+                            <a href="{% url "legacy:datams-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">Data MS</a>
86
+                          </li>
87
+                          <li>
88
+                            <a href="{% url "legacy:datarl-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">Data RL</a>
89
+                          </li>
90
+                          <li>
91
+                            <a href="{% url "legacy:datawb-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">Data WB</a>
92
+                          </li>
93
+                          <li>
85 94
                             <a href="{% url "legacy:datams-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">Measurement</a>
86 95
                           </li>
87 96
                           <li>
@@ -91,6 +100,12 @@
91 100
                             <a href="{% url "legacy:ls-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">Lot Summary</a>
92 101
                           </li>
93 102
                           <li>
103
+                            <a href="{% url "legacy:lsrl-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">Lot Summary RL</a>
104
+                          </li>
105
+                          <li>
106
+                            <a href="{% url "legacy:lswb-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">Lot Summary WB</a>
107
+                          </li>
108
+                          <li>
94 109
                             <a href="{% url "legacy:vm-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">VMaster</a>
95 110
                           </li>
96 111
                           <li>
@@ -102,12 +117,26 @@
102 117
                           <li>
103 118
                             <a href="{% url "legacy:em-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">EMaster</a>
104 119
                           </li>
105
-                          <li>
106
-                             <a href="#" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">Invoice</a>
107
-                          </li>
108 120
                     </ul>
109 121
                  </li>
110
-                <li><a href="/settings/" class="flex items-center p-2 text-gray-900 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-700 dark:text-white"><span class="ml-3">Settings</span></a></li>
122
+                 <li>
123
+
124
+                   <button type="button" class="flex items-center w-full p-2 text-base text-gray-900 transition duration-75 rounded-lg group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700" aria-controls="setting-sub" data-collapse-toggle="setting-sub">
125
+                     <svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
126
+                       <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 6c0 1.657-3.134 3-7 3S5 7.657 5 6m14 0c0-1.657-3.134-3-7-3S5 4.343 5 6m14 0v6M5 6v6m0 0c0 1.657 3.134 3 7 3s7-1.343 7-3M5 12v6c0 1.657 3.134 3 7 3s7-1.343 7-3v-6"/>
127
+                     </svg>
128
+
129
+                     <span class="flex-1 ms-3 text-left rtl:text-right whitespace-nowrap">Settings</span>
130
+                     <svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
131
+                       <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
132
+                     </svg>
133
+                   </button>
134
+                   <ul id="setting-sub" class="hidden py-2 space-y-2">
135
+                     <li>
136
+                       <a href="{% url "sysadmin:users-list" %}" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">Users</a>
137
+                     </li>
138
+                   </ul>
139
+                 </li>
111 140
             </ul>
112 141
         </div>
113 142
     </aside>