Compare commits
250 Commits
policy-cri
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
491965dc7f | ||
|
|
85fd7c0b47 | ||
|
|
220e7c4c9e | ||
|
|
08ac38a64b | ||
|
|
816d8b1bf1 | ||
|
|
a838aaeae0 | ||
|
|
aaff31e8d4 | ||
|
|
52878d6810 | ||
|
|
bd7d538481 | ||
|
|
030d1487a6 | ||
|
|
f0a0eb3c1d | ||
|
|
de7491cc6e | ||
|
|
6f251973a5 | ||
|
|
f9a3cbd91b | ||
|
|
020e890dae | ||
|
|
5b55ab17a7 | ||
|
|
f7342a14b4 | ||
|
|
d22e8bfcac | ||
|
|
d29caf66c5 | ||
|
|
005f526798 | ||
|
|
bf9a2b1277 | ||
|
|
a7310238c0 | ||
|
|
07cc47d8bb | ||
|
|
b40f65514d | ||
|
|
2dee35c37c | ||
|
|
6f85000d9d | ||
|
|
fa00f90f8a | ||
|
|
b24215272f | ||
|
|
a32fb0d0c3 | ||
|
|
8d6b62f0fa | ||
|
|
9c910f020c | ||
|
|
4960c472be | ||
|
|
6c98829a21 | ||
|
|
1b6188c584 | ||
|
|
5a776c39d0 | ||
|
|
d2b3b792d9 | ||
|
|
cf23f4f936 | ||
|
|
058875d571 | ||
|
|
ea0456d97c | ||
|
|
9f6cfedf42 | ||
|
|
80db60c39c | ||
|
|
9fac4152ab | ||
|
|
5bbda6f8fd | ||
|
|
186a07d126 | ||
|
|
66d62ca8f8 | ||
|
|
bc8968f9c5 | ||
|
|
0ec324bf03 | ||
|
|
8dea38b3a6 | ||
|
|
35229fa755 | ||
|
|
3be15a71ef | ||
|
|
12d11ed0d8 | ||
|
|
e15666e3b8 | ||
|
|
63e51d8c5f | ||
|
|
ae31d1fea5 | ||
|
|
57f7f29054 | ||
|
|
179e1db5d7 | ||
|
|
c0ecff7697 | ||
|
|
7e03cae3c2 | ||
|
|
efa0294820 | ||
|
|
d2aef160c5 | ||
|
|
29e30d3452 | ||
|
|
91f1e86afa | ||
|
|
eee3182898 | ||
|
|
77e0ba054c | ||
|
|
0d0b2a4799 | ||
|
|
c4478cce85 | ||
|
|
534c8213d9 | ||
|
|
6c12c4ca35 | ||
|
|
fae41a2f7e | ||
|
|
15623eb065 | ||
|
|
045b256551 | ||
|
|
284ff8779f | ||
|
|
4926b48e4f | ||
|
|
1c77a03067 | ||
|
|
f95b5d9c37 | ||
|
|
53fd28d65d | ||
|
|
428dff9ca8 | ||
|
|
f029c8b9b6 | ||
|
|
e33af3cd8a | ||
|
|
29eedb3162 | ||
|
|
7f897a7296 | ||
|
|
6169e8b1a2 | ||
|
|
c0218ad1c9 | ||
|
|
691ba181c6 | ||
|
|
1fcd57f676 | ||
|
|
2003fc68bc | ||
|
|
3e0a3c87d0 | ||
|
|
305ac4f329 | ||
|
|
e1d1266853 | ||
|
|
6bb37994de | ||
|
|
287b7b9ad2 | ||
|
|
ce9bcb113b | ||
|
|
25239c6e62 | ||
|
|
08f80a8afa | ||
|
|
c2bbbb2214 | ||
|
|
e2b9781cd6 | ||
|
|
3e973707a6 | ||
|
|
03a5fbfaf1 | ||
|
|
400a7a2ec0 | ||
|
|
438b5ec4dc | ||
|
|
563f808dfc | ||
|
|
4121213bff | ||
|
|
b81f3803e0 | ||
|
|
16e58dca07 | ||
|
|
128b0ea9e6 | ||
|
|
65528b1df3 | ||
|
|
b66391fbb5 | ||
|
|
66b62c7ad4 | ||
|
|
675d82eab5 | ||
|
|
2a77ebc7d1 | ||
|
|
036d0704fd | ||
|
|
2e85273b06 | ||
|
|
0537c45545 | ||
|
|
ae6750bef3 | ||
|
|
5a3c2a7fb9 | ||
|
|
e8eaad1a1f | ||
|
|
26ece5fb8a | ||
|
|
f1c153162b | ||
|
|
7527838cf5 | ||
|
|
c61183c472 | ||
|
|
7f448d3e2f | ||
|
|
e0cd77fbd5 | ||
|
|
ffa40af980 | ||
|
|
424d93cc9f | ||
|
|
4887c69950 | ||
|
|
a8712dcb68 | ||
|
|
20a24c7d46 | ||
|
|
80f4f62fb1 | ||
|
|
de960412a0 | ||
|
|
5238e245f4 | ||
|
|
5ccb201b7a | ||
|
|
ec0c74a0a2 | ||
| b157fc40a7 | |||
| bf1cd002e1 | |||
|
|
e00b27f994 | ||
|
|
99b8ae5b21 | ||
| 5cd189a8cc | |||
| b1d19aec84 | |||
|
|
20b74c5f5c | ||
|
|
b3eb4051bf | ||
|
|
f48680c2a5 | ||
|
|
fe6ccab809 | ||
|
|
a41402f929 | ||
|
|
0368c05c35 | ||
|
|
f663fd776f | ||
|
|
ef79391915 | ||
|
|
86da658dcd | ||
|
|
194af031d0 | ||
|
|
1a568f6878 | ||
|
|
335fbacede | ||
|
|
cf15771518 | ||
|
|
b246c79789 | ||
|
|
dca5c4ba6b | ||
|
|
7c9968c159 | ||
|
|
8c828b1dac | ||
|
|
454c07b04a | ||
|
|
3104812979 | ||
|
|
3e3ef56694 | ||
|
|
5d7faf309a | ||
|
|
1005ff962d | ||
|
|
905759ef90 | ||
|
|
6aa5767c9f | ||
|
|
d476f467ad | ||
|
|
b88973d43c | ||
|
|
ff74934520 | ||
|
|
5ddd028fa8 | ||
| 41010df10f | |||
| ac303e666f | |||
| 3b24228080 | |||
|
|
954fc5873e | ||
|
|
6c3571f4e8 | ||
|
|
057da00078 | ||
| e981a27b0c | |||
|
|
dbe248195d | ||
|
|
41ed141e13 | ||
|
|
33185ab3b7 | ||
| bc00123e56 | |||
| 52fa34bd0d | |||
| 154f18a59b | |||
| dc4a1530a6 | |||
|
|
a1837db9fe | ||
|
|
9cab4b02a3 | ||
|
|
93f69128b5 | ||
| 2891e93d8d | |||
|
|
b630ac35b7 | ||
|
|
b625692dad | ||
| 6c7c41008a | |||
| 3bf6caabe5 | |||
|
|
8de950586a | ||
|
|
9d1873c749 | ||
| 8f4e2c8c99 | |||
|
|
42f3f465d5 | ||
| 3dab5db7c3 | |||
| b34f9dc60a | |||
|
|
c99ac679a6 | ||
|
|
b1ec5e4fed | ||
|
|
7c9ec2310b | ||
| ca934a3a88 | |||
| 157b329de1 | |||
| 3474a1dbca | |||
| 7e9ea0134b | |||
|
|
aa284d9563 | ||
|
|
00441fe907 | ||
|
|
7b515573c7 | ||
|
|
5c3cb6c87a | ||
| e4acf06234 | |||
| 3fb412edba | |||
| e387bd783c | |||
|
|
2fb77ea7e8 | ||
|
|
a9761f0684 | ||
| a4e32596c2 | |||
| 9746318f2c | |||
| a5142b5ee4 | |||
|
|
48b2d3ca25 | ||
|
|
dbc0bf0c22 | ||
| 77b63c271d | |||
| 8cc56017ce | |||
|
|
3be2f695c6 | ||
|
|
d99d9433ec | ||
|
|
c560963e50 | ||
| 4559eb7262 | |||
| 9caf98de7c | |||
|
|
d85209139f | ||
|
|
cafa8cbc95 | ||
|
|
b55b908f42 | ||
|
|
53f2c4d5b5 | ||
|
|
db7df6b7de | ||
|
|
75592f9879 | ||
|
|
4435eeadd3 | ||
|
|
12310625ef | ||
|
|
2b8172d5f4 | ||
|
|
94d2a730fb | ||
|
|
1999e63c0f | ||
|
|
a76464bbbc | ||
| a509583cb6 | |||
|
|
36975e716f | ||
|
|
5c628282c6 | ||
|
|
15992c4008 | ||
|
|
5c31a33ff2 | ||
|
|
5e4a093456 | ||
|
|
36d3b365be | ||
|
|
d59aaf90bc | ||
|
|
ceb6aece50 | ||
|
|
e0ed41ad35 | ||
|
|
a91166727a | ||
|
|
886bef2baa | ||
|
|
1f28816021 | ||
|
|
83d71279eb | ||
|
|
4f9c353e02 | ||
|
|
204a71179f |
10
.run/ts-tat.run.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="ts-tat" type="FlutterRunConfigurationType" factoryName="Flutter">
|
||||||
|
<option name="additionalArgs" value="--web-browser-flag "--disable-web-security"" />
|
||||||
|
<option name="envs">
|
||||||
|
<entry key="google-chrome --disable-web-security --user-data-dir" value="~/chromeTemp" />
|
||||||
|
</option>
|
||||||
|
<option name="filePath" value="$PROJECT_DIR$/lib/main.dart" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
@ -1,8 +1,11 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:label="frontend"
|
android:label="frontend"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
@ -25,11 +28,22 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<provider
|
||||||
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
android:authorities="${applicationId}.fileprovider"
|
||||||
|
android:exported="false"
|
||||||
|
android:grantUriPermissions="true" >
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/file_paths" />
|
||||||
|
</provider>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="flutterEmbedding"
|
android:name="flutterEmbedding"
|
||||||
android:value="2" />
|
android:value="2" />
|
||||||
|
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
<!-- Required to query activities that can process text, see:
|
<!-- Required to query activities that can process text, see:
|
||||||
https://developer.android.com/training/package-visibility and
|
https://developer.android.com/training/package-visibility and
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 355 B |
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 265 B |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 442 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 622 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 772 B |
@ -5,6 +5,7 @@
|
|||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
the Flutter engine draws its first frame -->
|
the Flutter engine draws its first frame -->
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
This theme determines the color of the Android Window while your
|
This theme determines the color of the Android Window while your
|
||||||
@ -15,4 +16,7 @@
|
|||||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
<item name="android:windowBackground">?android:colorBackground</item>
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
</style>
|
</style>
|
||||||
|
<paths>
|
||||||
|
<cache-path name="cache" path="." />
|
||||||
|
</paths>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
6
android/app/src/main/res/xml/file_paths.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<cache-path name="cache" path="." />
|
||||||
|
<external-path name="external" path="." />
|
||||||
|
<!-- You can add more paths as needed -->
|
||||||
|
</paths>
|
||||||
@ -4,4 +4,5 @@
|
|||||||
to allow setting breakpoints, to provide hot reload, etc.
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
-->
|
-->
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
BIN
assets/images/IconsImg/Domestic.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
assets/images/IconsImg/Domestic_new.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
assets/images/IconsImg/Domestic_old.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
assets/images/IconsImg/Frame.png
Normal file
|
After Width: | Height: | Size: 391 B |
BIN
assets/images/IconsImg/International.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
assets/images/IconsImg/International_new.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/images/IconsImg/International_old.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
assets/images/IconsImg/ViceVersaArrow.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
assets/images/IconsImg/alternate.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
assets/images/IconsImg/delete.png
Normal file
|
After Width: | Height: | Size: 242 B |
BIN
assets/images/IconsImg/download.png
Normal file
|
After Width: | Height: | Size: 741 B |
BIN
assets/images/IconsImg/edit.png
Normal file
|
After Width: | Height: | Size: 488 B |
BIN
assets/images/IconsImg/favicon_ts.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
assets/images/IconsImg/path.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
assets/images/IconsImg/planPdf_icon.png
Normal file
|
After Width: | Height: | Size: 900 B |
BIN
assets/images/IconsImg/upload.png
Normal file
|
After Width: | Height: | Size: 729 B |
BIN
assets/images/login/TravelSpendsLogo1.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
assets/images/login/VectorG.png
Normal file
|
After Width: | Height: | Size: 476 B |
BIN
assets/images/login/login_img1.png
Normal file
|
After Width: | Height: | Size: 863 KiB |
BIN
assets/images/login/login_terminal.png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
assets/images/login/login_travel.png
Normal file
|
After Width: | Height: | Size: 179 KiB |
BIN
assets/images/login/logoNew.jpg
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
assets/images/login/microsoft.png
Normal file
|
After Width: | Height: | Size: 200 B |
BIN
assets/images/login/sign.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
assets/images/login/travelSpend_Logo.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
assets/images/screenshot_1.png
Normal file
|
After Width: | Height: | Size: 381 KiB |
BIN
assets/images/screenshot_2.png
Normal file
|
After Width: | Height: | Size: 154 KiB |
BIN
assets/images/screenshot_3.png
Normal file
|
After Width: | Height: | Size: 204 KiB |
BIN
assets/images/screenshot_4.png
Normal file
|
After Width: | Height: | Size: 292 KiB |
3
devtools_options.yaml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
description: This file stores settings for Dart & Flutter DevTools.
|
||||||
|
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||||
|
extensions:
|
||||||
@ -427,7 +427,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
@ -484,7 +484,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
|||||||
@ -1,122 +1 @@
|
|||||||
{
|
{"images":[{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"Icon-App-20x20@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"Icon-App-29x29@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"Icon-App-40x40@3x.png","scale":"3x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"Icon-App-60x60@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@1x.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"Icon-App-20x20@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@1x.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"Icon-App-29x29@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@1x.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"Icon-App-40x40@2x.png","scale":"2x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@1x.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"Icon-App-76x76@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"Icon-App-83.5x83.5@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"Icon-App-1024x1024@1x.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"size" : "20x20",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-20x20@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "20x20",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-20x20@3x.png",
|
|
||||||
"scale" : "3x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "29x29",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-29x29@1x.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "29x29",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-29x29@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "29x29",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-29x29@3x.png",
|
|
||||||
"scale" : "3x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "40x40",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-40x40@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "40x40",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-40x40@3x.png",
|
|
||||||
"scale" : "3x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "60x60",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-60x60@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "60x60",
|
|
||||||
"idiom" : "iphone",
|
|
||||||
"filename" : "Icon-App-60x60@3x.png",
|
|
||||||
"scale" : "3x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "20x20",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-20x20@1x.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "20x20",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-20x20@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "29x29",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-29x29@1x.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "29x29",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-29x29@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "40x40",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-40x40@1x.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "40x40",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-40x40@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "76x76",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-76x76@1x.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "76x76",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-76x76@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "83.5x83.5",
|
|
||||||
"idiom" : "ipad",
|
|
||||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"size" : "1024x1024",
|
|
||||||
"idiom" : "ios-marketing",
|
|
||||||
"filename" : "Icon-App-1024x1024@1x.png",
|
|
||||||
"scale" : "1x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 185 B |
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 241 B |
|
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 287 B |
|
Before Width: | Height: | Size: 282 B After Width: | Height: | Size: 207 B |
|
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 287 B |
|
Before Width: | Height: | Size: 704 B After Width: | Height: | Size: 421 B |
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 241 B |
|
Before Width: | Height: | Size: 586 B After Width: | Height: | Size: 389 B |
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 527 B |
|
After Width: | Height: | Size: 281 B |
|
After Width: | Height: | Size: 475 B |
|
After Width: | Height: | Size: 283 B |
|
After Width: | Height: | Size: 509 B |
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 527 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 743 B |
|
After Width: | Height: | Size: 355 B |
|
After Width: | Height: | Size: 622 B |
|
Before Width: | Height: | Size: 762 B After Width: | Height: | Size: 388 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 649 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 721 B |
2079
lib/Screens/allTrips/list_all_plans.dart
Normal file
218
lib/Screens/allTrips/plan_info_mdl.dart
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:frontend/services/apiService.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
import '../../config/apiUrl.dart';
|
||||||
|
import '../../utils/auth_utils.dart';
|
||||||
|
|
||||||
|
class TripInformation extends StatefulWidget {
|
||||||
|
final String planId;
|
||||||
|
final Color layoutColorForUser;
|
||||||
|
|
||||||
|
const TripInformation({
|
||||||
|
Key? key,
|
||||||
|
required this.planId,
|
||||||
|
required this.layoutColorForUser,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_TripInformationState createState() => _TripInformationState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TripInformationState extends State<TripInformation> {
|
||||||
|
final ApiService apiService = ApiService();
|
||||||
|
late Future<Map<String, dynamic>> _tripInfoFuture;
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> fetchComments() async {
|
||||||
|
final String apiUrldata =
|
||||||
|
'$apiUrl/api/plans/planInfo?plan_id=${widget.planId}';
|
||||||
|
|
||||||
|
final token = await getToken();
|
||||||
|
|
||||||
|
if (token == null) {
|
||||||
|
throw Exception('Token not found. Please log in.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final response = await http.get(
|
||||||
|
Uri.parse(apiUrldata),
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
'app-signature': 'ts-traveltool-2025-signature-123456',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final jsonData = json.decode(response.body);
|
||||||
|
final Map<String, dynamic> dataMap =
|
||||||
|
jsonData['data'] as Map<String, dynamic>;
|
||||||
|
return dataMap;
|
||||||
|
} else if (response.statusCode == 403) {
|
||||||
|
print("403-FORB");
|
||||||
|
await apiService.logout(context);
|
||||||
|
return {};
|
||||||
|
// throw Exception('Failed to load users');
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to load comments');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_tripInfoFuture = fetchComments();
|
||||||
|
|
||||||
|
print('_tripInfoFuture : $_tripInfoFuture');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
title: Text(
|
||||||
|
'Trip Information',
|
||||||
|
style: GoogleFonts.poppins(color: Colors.black),
|
||||||
|
),
|
||||||
|
content: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 500, // ✅ You can adjust this width
|
||||||
|
maxHeight:
|
||||||
|
400, // ✅ Optional: limit height to make it scrollable vertically
|
||||||
|
),
|
||||||
|
child: FutureBuilder<Map<String, dynamic>>(
|
||||||
|
future: _tripInfoFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.hasError) {
|
||||||
|
return Text(
|
||||||
|
'Error: ${snapshot.error}',
|
||||||
|
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!snapshot.hasData) {
|
||||||
|
return Text(
|
||||||
|
'No data found.',
|
||||||
|
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final data = snapshot.data!;
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
keyValueColumn("Traveller", data["traveller"] ?? ""),
|
||||||
|
keyValueColumn("Group Name", data["group_name"] ?? ""),
|
||||||
|
keyValueColumn(
|
||||||
|
"Allowed Plan Type",
|
||||||
|
data["allowed_plan_type"] ?? "",
|
||||||
|
),
|
||||||
|
keyValueColumn(
|
||||||
|
"Policy Action Flow",
|
||||||
|
data["policy_action_flow"] ?? "",
|
||||||
|
),
|
||||||
|
keyValueColumn("Policy Type", data["policy_type"] ?? ""),
|
||||||
|
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
"Approval Criteria:",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 13,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
...List.generate((data["approval_criteria"] as List).length, (
|
||||||
|
index,
|
||||||
|
) {
|
||||||
|
final item = data["approval_criteria"][index];
|
||||||
|
return Card(
|
||||||
|
elevation: 1,
|
||||||
|
color: Colors.white,
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 4),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
keyValueRow("Action", item["action"] ?? '-'),
|
||||||
|
keyValueRow("Approver", item["approver"] ?? '-'),
|
||||||
|
keyValueRow(
|
||||||
|
"Is Action Done",
|
||||||
|
item["is_action_done"] ?? '-',
|
||||||
|
),
|
||||||
|
keyValueRow("Action On", item["action_on"] ?? '-'),
|
||||||
|
keyValueRow(
|
||||||
|
"Email Status",
|
||||||
|
item["email_status"] ?? '-',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget keyValueColumn(String key, String value) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 2),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"$key ",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 13,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
Text(value, style: GoogleFonts.poppins(fontSize: 13)),
|
||||||
|
// Expanded(
|
||||||
|
// child: Text(value, style: GoogleFonts.poppins(fontSize: 13)),
|
||||||
|
// ),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget keyValueRow(String key, String value) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 4),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"$key ",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
Expanded(
|
||||||
|
child: Text(value, style: GoogleFonts.poppins(fontSize: 12)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
228
lib/Screens/allTrips/remarks_list.dart
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
import '../../config/apiUrl.dart';
|
||||||
|
import '../../services/apiService.dart';
|
||||||
|
import '../../utils/auth_utils.dart';
|
||||||
|
|
||||||
|
class CommentModalList extends StatefulWidget {
|
||||||
|
final String planId;
|
||||||
|
final Color layoutColorForUser;
|
||||||
|
final String role;
|
||||||
|
|
||||||
|
const CommentModalList({
|
||||||
|
Key? key,
|
||||||
|
required this.planId,
|
||||||
|
required this.layoutColorForUser,
|
||||||
|
required this.role,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_CommentModalListState createState() => _CommentModalListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CommentModalListState extends State<CommentModalList> {
|
||||||
|
final ApiService apiService = ApiService();
|
||||||
|
late Future<List<Map<String, dynamic>>> _commentsFuture;
|
||||||
|
|
||||||
|
// Future<List<Map<String, dynamic>>> fetchComments1() async {
|
||||||
|
// final response = await http.get(
|
||||||
|
// Uri.parse(
|
||||||
|
// '$apiUrl/api/plans/getRemarksByPlanId?plan_id=${widget.planId}',
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// if (response.statusCode == 200) {
|
||||||
|
// final jsonData = json.decode(response.body);
|
||||||
|
// final List<dynamic> dataList = jsonData['data'];
|
||||||
|
// return dataList.cast<Map<String, dynamic>>();
|
||||||
|
// } else {
|
||||||
|
// throw Exception('Failed to load comments');
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
Future<List<Map<String, dynamic>>> fetchComments() async {
|
||||||
|
// final String apiUrldata =
|
||||||
|
// '$apiUrl/getRemarksByPlanId?plan_id=${widget.planId}&user_id=$userId';
|
||||||
|
|
||||||
|
final String apiUrldata =
|
||||||
|
'$apiUrl/api/plans/getRemarksByPlanId?plan_id=${widget.planId}';
|
||||||
|
|
||||||
|
final token = await getToken();
|
||||||
|
|
||||||
|
if (token == null) {
|
||||||
|
throw Exception('Token not found. Please log in.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final response = await http.get(
|
||||||
|
Uri.parse(apiUrldata),
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
'app-signature': 'ts-traveltool-2025-signature-123456',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final jsonData = json.decode(response.body);
|
||||||
|
final List<dynamic> dataList = jsonData['data'];
|
||||||
|
return dataList.cast<Map<String, dynamic>>();
|
||||||
|
} else if (response.statusCode == 403) {
|
||||||
|
print("403-FORB");
|
||||||
|
await apiService.logout(context);
|
||||||
|
return [];
|
||||||
|
// throw Exception('Failed to load users');
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to load comments');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_commentsFuture = fetchComments();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
title: Text('Comments', style: GoogleFonts.poppins(color: Colors.black)),
|
||||||
|
content: ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 500, // ✅ You can adjust this width
|
||||||
|
maxHeight:
|
||||||
|
400, // ✅ Optional: limit height to make it scrollable vertically
|
||||||
|
),
|
||||||
|
child: FutureBuilder<List<Map<String, dynamic>>>(
|
||||||
|
future: _commentsFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.hasError) {
|
||||||
|
return Text(
|
||||||
|
'No Comments For This Plan',
|
||||||
|
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!snapshot.hasData || snapshot.data!.isEmpty) {
|
||||||
|
return Text(
|
||||||
|
'No comments found.',
|
||||||
|
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final comments = snapshot.data!;
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: DataTable(
|
||||||
|
columns: [
|
||||||
|
DataColumn(
|
||||||
|
label: Text(
|
||||||
|
'Name',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 11,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DataColumn(
|
||||||
|
label: Text(
|
||||||
|
'Comment',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 11,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DataColumn(
|
||||||
|
label: Text(
|
||||||
|
'Updated On',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 11,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
rows:
|
||||||
|
comments.map((comment) {
|
||||||
|
final name = comment['created_by_name'] ?? 'Unknown';
|
||||||
|
final remark = comment['remarks'] ?? '';
|
||||||
|
final rawDateStr = comment['updated_on'];
|
||||||
|
String updatedOn = '';
|
||||||
|
|
||||||
|
if (rawDateStr != null && rawDateStr.isNotEmpty) {
|
||||||
|
try {
|
||||||
|
final parsedDate = DateTime.parse(rawDateStr);
|
||||||
|
updatedOn = DateFormat(
|
||||||
|
'd MMM yyyy',
|
||||||
|
).format(parsedDate); // e.g., 15 May 2025
|
||||||
|
} catch (e) {
|
||||||
|
updatedOn = rawDateStr.split(' ').first; // fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DataRow(
|
||||||
|
cells: [
|
||||||
|
DataCell(
|
||||||
|
Text(
|
||||||
|
name,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.black,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DataCell(
|
||||||
|
Text(
|
||||||
|
remark,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.black,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DataCell(
|
||||||
|
Text(
|
||||||
|
updatedOn,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.black,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: Text(
|
||||||
|
'Close',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: widget.layoutColorForUser,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
1384
lib/Screens/allTrips/travel_agent_list.dart
Normal file
344
lib/Screens/approvals/approval_dialogs.dart
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
Future<dynamic> showApprovalDialog(
|
||||||
|
BuildContext context,
|
||||||
|
Color layoutColor,
|
||||||
|
) async {
|
||||||
|
String selectedAction = ""; // "", "accept", "reject"
|
||||||
|
String remarks = "";
|
||||||
|
|
||||||
|
return showDialog<String>(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false, // force user to tap cancel
|
||||||
|
builder: (context) {
|
||||||
|
return StatefulBuilder(
|
||||||
|
builder: (context, setState) {
|
||||||
|
return AlertDialog(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
contentPadding: const EdgeInsets.all(24),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"To Approve or Reject Trip",
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: "Inter",
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.close, size: 15),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context, null); // Close the dialog
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
// Accept and Reject Buttons
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor:
|
||||||
|
selectedAction == "accept"
|
||||||
|
? Colors.green
|
||||||
|
: Colors.grey.shade200,
|
||||||
|
foregroundColor:
|
||||||
|
selectedAction == "accept"
|
||||||
|
? Colors.white
|
||||||
|
: Colors.black,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 10),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
selectedAction = "accept";
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: const Text("Accept"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 20),
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor:
|
||||||
|
selectedAction == "reject"
|
||||||
|
? Colors.redAccent
|
||||||
|
: Colors.grey.shade200,
|
||||||
|
foregroundColor:
|
||||||
|
selectedAction == "reject"
|
||||||
|
? Colors.white
|
||||||
|
: Colors.black,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 10),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
selectedAction = "reject";
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: const Text(
|
||||||
|
"Reject",
|
||||||
|
style: TextStyle(fontFamily: "Inter"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Dynamic Content Based on Selection
|
||||||
|
if (selectedAction == "accept") ...[
|
||||||
|
const Text(
|
||||||
|
"Do you want to approve?",
|
||||||
|
style: TextStyle(fontFamily: "Inter", fontSize: 14),
|
||||||
|
),
|
||||||
|
] else if (selectedAction == "reject") ...[
|
||||||
|
const Text(
|
||||||
|
"Please enter reason for rejection:",
|
||||||
|
style: TextStyle(fontFamily: "Inter", fontSize: 14),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
TextField(
|
||||||
|
style: TextStyle(fontFamily: "Inter", fontSize: 14),
|
||||||
|
maxLines: 3,
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(
|
||||||
|
RegExp(r'[a-zA-Z0-9 _-]'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onChanged: (value) => remarks = value,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "Remarks...",
|
||||||
|
hintStyle: const TextStyle(
|
||||||
|
fontSize: 10, // 👈 Set your desired font size here
|
||||||
|
color: Colors.grey,
|
||||||
|
fontFamily:
|
||||||
|
"Inter", // optional if you want consistent font
|
||||||
|
),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: const BorderSide(
|
||||||
|
color: Colors.blueGrey,
|
||||||
|
width: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: Colors.blueGrey,
|
||||||
|
width: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: BorderSide(color: layoutColor, width: 1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actionsAlignment: MainAxisAlignment.center,
|
||||||
|
actions: [
|
||||||
|
// Cancel Button
|
||||||
|
if (selectedAction.isNotEmpty)
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
foregroundColor: layoutColor,
|
||||||
|
side: BorderSide(color: layoutColor, width: 1.5),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context, null); // No result
|
||||||
|
},
|
||||||
|
child: const Text(
|
||||||
|
"Cancel",
|
||||||
|
style: TextStyle(fontFamily: "Inter", fontSize: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (selectedAction.isNotEmpty)
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: layoutColor,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
if (selectedAction == "accept") {
|
||||||
|
Navigator.pop(context, "approved"); // Return approval
|
||||||
|
} else if (selectedAction == "reject" &&
|
||||||
|
remarks.trim().isNotEmpty) {
|
||||||
|
Navigator.pop(context, remarks.trim()); // Return remarks
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text(
|
||||||
|
"OK",
|
||||||
|
style: TextStyle(fontFamily: "Inter", fontSize: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show confirm dialog for approval
|
||||||
|
Future<bool?> showApproveDialog1(BuildContext context, Color layoutColor) {
|
||||||
|
return showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(context) => AlertDialog(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
title: const Text(
|
||||||
|
"Confirm Approval",
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
content: const Text("Are you sure you want to approve this plan?"),
|
||||||
|
actions: [
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
foregroundColor: layoutColor,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
side: BorderSide(color: layoutColor, width: 2),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
),
|
||||||
|
onPressed: () => Navigator.pop(context, false),
|
||||||
|
child: const Text("Cancel"),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: layoutColor,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
side: BorderSide(color: layoutColor, width: 2),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
),
|
||||||
|
onPressed: () => Navigator.pop(context, true),
|
||||||
|
child: const Text("OK"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show confirm dialog for rejection with remarks input
|
||||||
|
Future<String?> showRejectDialog1(
|
||||||
|
BuildContext context,
|
||||||
|
Color layoutColor,
|
||||||
|
) async {
|
||||||
|
String remarks = "";
|
||||||
|
|
||||||
|
final confirmed = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return StatefulBuilder(
|
||||||
|
builder:
|
||||||
|
(context, setState) => AlertDialog(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
contentPadding: const EdgeInsets.all(36),
|
||||||
|
// title: const Text("Confirm Rejection"),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
"Confirm Rejection",
|
||||||
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const Text("Please enter reason for rejection."),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
TextField(
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(
|
||||||
|
RegExp(r'[a-zA-Z0-9 _-]'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
maxLines: 3,
|
||||||
|
onChanged: (value) => remarks = value,
|
||||||
|
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText: "Remarks...",
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
fontSize: 10, // 👈 Set your desired font size here
|
||||||
|
color: Colors.grey,
|
||||||
|
fontFamily:
|
||||||
|
"Inter", // optional if you want consistent font
|
||||||
|
),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: Colors.blueGrey,
|
||||||
|
width: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: Colors.blueGrey,
|
||||||
|
width: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(color: Colors.grey, width: 1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
foregroundColor: layoutColor,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
side: BorderSide(color: layoutColor, width: 2),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
),
|
||||||
|
onPressed: () => Navigator.pop(context, false),
|
||||||
|
child: const Text("Cancel"),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: layoutColor,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
side: BorderSide(color: layoutColor, width: 2),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
if (remarks.trim().isEmpty) return;
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
},
|
||||||
|
child: const Text("OK"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return confirmed == true ? remarks : null;
|
||||||
|
}
|
||||||
1791
lib/Screens/approvals/approval_list.dart
Normal file
128
lib/Screens/approvals/approval_tooltip.dart
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:super_tooltip/super_tooltip.dart';
|
||||||
|
|
||||||
|
class ApprovalTooltip extends StatefulWidget {
|
||||||
|
final List<Map<String, dynamic>> planStatusList;
|
||||||
|
final bool hasApprovals;
|
||||||
|
final String statusText;
|
||||||
|
final bool isApproverApproved;
|
||||||
|
final bool isApproverRejected;
|
||||||
|
final Color Function(String) getStatusColor;
|
||||||
|
|
||||||
|
const ApprovalTooltip({
|
||||||
|
super.key,
|
||||||
|
required this.planStatusList,
|
||||||
|
required this.hasApprovals,
|
||||||
|
required this.statusText,
|
||||||
|
required this.isApproverApproved,
|
||||||
|
required this.isApproverRejected,
|
||||||
|
required this.getStatusColor,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ApprovalTooltip> createState() => _ApprovalTooltipState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ApprovalTooltipState extends State<ApprovalTooltip> {
|
||||||
|
late SuperTooltip tooltip;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
tooltip = SuperTooltip(
|
||||||
|
popupDirection: TooltipDirection.down,
|
||||||
|
content: _buildTooltipContent(),
|
||||||
|
borderColor: Colors.grey.shade300,
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
shadowColor: Colors.black38,
|
||||||
|
barrierColor: Colors.transparent,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildTooltipContent() {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
if (!widget.hasApprovals)
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
"--- No Approvals ---",
|
||||||
|
style: const TextStyle(
|
||||||
|
fontFamily: "Archivo",
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
for (int i = 0; i < widget.planStatusList.length; i++) ...[
|
||||||
|
if (widget.planStatusList[i].entries.any((entry) =>
|
||||||
|
entry.key.contains('status') &&
|
||||||
|
entry.value != null &&
|
||||||
|
entry.value.toString().isNotEmpty)) ...[
|
||||||
|
_buildApprovalItem(
|
||||||
|
"Approver ${i + 1}",
|
||||||
|
widget.planStatusList[i].entries
|
||||||
|
.firstWhere(
|
||||||
|
(entry) => entry.key.contains('status'),
|
||||||
|
orElse: () => MapEntry('', ''),
|
||||||
|
)
|
||||||
|
.value
|
||||||
|
.toString(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildApprovalItem(String title, String value) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MouseRegion(
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: widget.getStatusColor(widget.statusText)),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 6),
|
||||||
|
child: Text(
|
||||||
|
widget.statusText,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: "Roboto",
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
fontSize: 12,
|
||||||
|
color: widget.getStatusColor(
|
||||||
|
widget.isApproverApproved
|
||||||
|
? "Approved"
|
||||||
|
: widget.isApproverRejected
|
||||||
|
? "Rejected"
|
||||||
|
: (widget.statusText),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,7 +4,6 @@ import 'package:http/http.dart' as http;
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
|
||||||
class LoginPage1 extends StatefulWidget {
|
class LoginPage1 extends StatefulWidget {
|
||||||
const LoginPage1({super.key});
|
const LoginPage1({super.key});
|
||||||
|
|
||||||
@ -21,17 +20,15 @@ class _LoginPageState extends State<LoginPage1> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// StatefulWidget for Body Content
|
// StatefulWidget for Body Content
|
||||||
class LoginWidget extends StatefulWidget{
|
class LoginWidget extends StatefulWidget {
|
||||||
const LoginWidget({super.key});
|
const LoginWidget({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_LoginWidgetState createState() => _LoginWidgetState();
|
_LoginWidgetState createState() => _LoginWidgetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LoginWidgetState extends State<LoginWidget>{
|
class _LoginWidgetState extends State<LoginWidget> {
|
||||||
|
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
final TextEditingController _emailController = TextEditingController();
|
final TextEditingController _emailController = TextEditingController();
|
||||||
final TextEditingController _passwordController = TextEditingController();
|
final TextEditingController _passwordController = TextEditingController();
|
||||||
@ -47,7 +44,8 @@ class _LoginWidgetState extends State<LoginWidget>{
|
|||||||
Uri.parse(url),
|
Uri.parse(url),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'},
|
'Accept': 'application/json'
|
||||||
|
},
|
||||||
body: jsonEncode({
|
body: jsonEncode({
|
||||||
'email': _emailController.text.trim(),
|
'email': _emailController.text.trim(),
|
||||||
'password': _passwordController.text.trim(),
|
'password': _passwordController.text.trim(),
|
||||||
@ -61,11 +59,12 @@ class _LoginWidgetState extends State<LoginWidget>{
|
|||||||
backgroundColor: Colors.green, // Set background to green
|
backgroundColor: Colors.green, // Set background to green
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
context.go('/home'); // Login Success
|
context.go('/listPlan'); // Login Success
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text("Login Failed: ${jsonDecode(response.body)['message']}")),
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
"Login Failed: ${jsonDecode(response.body)['message']}")),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -76,16 +75,13 @@ class _LoginWidgetState extends State<LoginWidget>{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context){
|
Widget build(BuildContext context) {
|
||||||
double myHeight = MediaQuery.of(context).size.height;
|
double myHeight = MediaQuery.of(context).size.height;
|
||||||
double myWidth = MediaQuery.of(context).size.width;
|
double myWidth = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
|
||||||
child: Form(
|
child: Form(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -94,19 +90,17 @@ class _LoginWidgetState extends State<LoginWidget>{
|
|||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
"Hello! , Welcome Back",
|
"Hello! , Welcome Back",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.blueAccent
|
color: Colors.blueAccent),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
SizedBox(height: myHeight / 10),
|
||||||
SizedBox(height: myHeight/10),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
child: const Text(
|
child: const Text(
|
||||||
@ -114,33 +108,32 @@ class _LoginWidgetState extends State<LoginWidget>{
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.blueAccent
|
color: Colors.blueAccent),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
SizedBox(height: 8),
|
||||||
SizedBox(height:8),
|
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _emailController,
|
controller: _emailController,
|
||||||
style: const TextStyle(fontSize: 12),
|
style: const TextStyle(fontSize: 12),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Enter the email',
|
labelText: 'Enter the email',
|
||||||
labelStyle: TextStyle(color: Colors.grey,),
|
labelStyle: TextStyle(
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
filled: true,
|
filled: true,
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(12), // Rounded corners// Remove the default border
|
borderRadius: BorderRadius.circular(
|
||||||
borderSide: BorderSide.none
|
12), // Rounded corners// Remove the default border
|
||||||
|
borderSide: BorderSide.none),
|
||||||
),
|
),
|
||||||
),
|
validator: (value) {
|
||||||
validator: (value){
|
if (value == null || value.isEmpty) {
|
||||||
if(value == null || value.isEmpty){
|
|
||||||
return 'Required Email';
|
return 'Required Email';
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
@ -149,18 +142,19 @@ class _LoginWidgetState extends State<LoginWidget>{
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Colors.blueAccent
|
color: Colors.blueAccent),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
SizedBox(height: 8),
|
||||||
SizedBox(height:8),
|
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller:_passwordController,
|
controller: _passwordController,
|
||||||
style: const TextStyle(fontSize: 12),
|
style: const TextStyle(fontSize: 12),
|
||||||
obscureText: _obscureText,
|
obscureText: _obscureText,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: "Enter the password",
|
labelText: "Enter the password",
|
||||||
labelStyle: TextStyle(color: Colors.grey,),
|
labelStyle: TextStyle(
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
@ -176,11 +170,9 @@ class _LoginWidgetState extends State<LoginWidget>{
|
|||||||
filled: true,
|
filled: true,
|
||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
borderSide: BorderSide.none
|
borderSide: BorderSide.none)),
|
||||||
)
|
validator: (value) {
|
||||||
),
|
if (value == null || value.isEmpty) {
|
||||||
validator: (value){
|
|
||||||
if(value == null || value.isEmpty){
|
|
||||||
return 'Required Password';
|
return 'Required Password';
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -189,21 +181,18 @@ class _LoginWidgetState extends State<LoginWidget>{
|
|||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Center(
|
Center(
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: (){
|
onPressed: () {
|
||||||
_login(context);
|
_login(context);
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.blueAccent, // Change button color
|
backgroundColor: Colors.blueAccent, // Change button color
|
||||||
foregroundColor: Colors.white, // Text color
|
foregroundColor: Colors.white, // Text color
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24,vertical: 12),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 24, vertical: 12),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
)
|
)),
|
||||||
),
|
child: const Text("Login")),
|
||||||
|
|
||||||
child: const Text(
|
|
||||||
"Login")
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -211,6 +200,3 @@ class _LoginWidgetState extends State<LoginWidget>{
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
492
lib/Screens/costCenter/costCenterDetails.dart
Normal file
@ -0,0 +1,492 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
import '../../config/apiUrl.dart';
|
||||||
|
import '../../services/apiService.dart';
|
||||||
|
import '../../utils/auth_utils.dart';
|
||||||
|
import '../../widgets/custom_text_forex.dart';
|
||||||
|
import 'costCenter_list.dart';
|
||||||
|
|
||||||
|
class CostCenterData extends StatefulWidget {
|
||||||
|
final Future<List<dynamic>> Function() fetchGetCostCenter;
|
||||||
|
final bool isDesktop;
|
||||||
|
final Color? layoutColor;
|
||||||
|
|
||||||
|
final int? costcenterId; // <-- Add this
|
||||||
|
final Map<String, dynamic>? costcenterData;
|
||||||
|
|
||||||
|
const CostCenterData({
|
||||||
|
super.key,
|
||||||
|
required this.isDesktop,
|
||||||
|
this.layoutColor,
|
||||||
|
required this.fetchGetCostCenter,
|
||||||
|
this.costcenterId,
|
||||||
|
this.costcenterData,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
CostCenterDataState createState() => CostCenterDataState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class CostCenterDataState extends State<CostCenterData> {
|
||||||
|
final ApiService apiService = ApiService();
|
||||||
|
Map<String, dynamic>? apiData;
|
||||||
|
|
||||||
|
// final Map<String, FocusNode> focusNodes = {
|
||||||
|
// "name": FocusNode(),
|
||||||
|
// "description": FocusNode(),
|
||||||
|
// };
|
||||||
|
Map<String, FocusNode> focusNodes = {};
|
||||||
|
Map<String, bool> focusStates = {};
|
||||||
|
final Map<String, TextEditingController> controllers = {};
|
||||||
|
Map<String, String> errorMessages = {};
|
||||||
|
|
||||||
|
String? selectedName;
|
||||||
|
String? selectedDescription;
|
||||||
|
String? userId;
|
||||||
|
int? costcenterDataId;
|
||||||
|
late String isActive = "1";
|
||||||
|
bool isDisable = false;
|
||||||
|
|
||||||
|
List<String> dataHeader = ["name", "description"];
|
||||||
|
|
||||||
|
Map<String, dynamic> costcenterDetails() {
|
||||||
|
final data = {
|
||||||
|
// "cost_center_id": int.parse(costcenterId),
|
||||||
|
"name": controllers["name"]?.text,
|
||||||
|
"description": controllers["description"]?.text,
|
||||||
|
"created_by": userId,
|
||||||
|
"is_active": isActive,
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
apiData = null;
|
||||||
|
for (var field in dataHeader) {
|
||||||
|
controllers[field] = TextEditingController();
|
||||||
|
focusNodes["${field}FocusNode"] = FocusNode();
|
||||||
|
focusStates["${field}Focused"] = false;
|
||||||
|
}
|
||||||
|
for (var key in focusNodes.keys) {
|
||||||
|
_addFocusListener(focusNodes[key]!, (focus) {
|
||||||
|
setState(() {
|
||||||
|
focusStates[key.replaceFirst("FocusNode", "Focused")] = focus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.costcenterId != null) {
|
||||||
|
print('Editing D ID: ${widget.costcenterId}');
|
||||||
|
updateCostCenterDetails();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _clearError() {
|
||||||
|
setState(() {
|
||||||
|
errorMessages.clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
for (var controller in controllers.values) {
|
||||||
|
controller.dispose();
|
||||||
|
}
|
||||||
|
for (var node in focusNodes.values) {
|
||||||
|
node.dispose();
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addFocusListener(FocusNode node, Function(bool) updateState) {
|
||||||
|
node.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
updateState(node.hasFocus);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateCostCenterDetails() {
|
||||||
|
print("Inside Update Function - ${widget.costcenterData}");
|
||||||
|
|
||||||
|
final data = widget.costcenterData;
|
||||||
|
|
||||||
|
if (data == null) return;
|
||||||
|
setState(() {
|
||||||
|
controllers['name']?.text = data['name'] ?? '';
|
||||||
|
controllers['description']?.text = data['description'].toString();
|
||||||
|
isActive = data["is_active"];
|
||||||
|
final costcenterId = int.tryParse(data['cost_center_id'].toString());
|
||||||
|
costcenterDataId = costcenterId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggleStatus() {
|
||||||
|
setState(() {
|
||||||
|
isActive = isActive == "1" ? "0" : "1";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validateData() {
|
||||||
|
errorMessages.clear();
|
||||||
|
|
||||||
|
final data = {
|
||||||
|
"name": controllers["name"]?.text,
|
||||||
|
"description": controllers["description"]?.text,
|
||||||
|
};
|
||||||
|
|
||||||
|
final requiredFields = ["name", "description"];
|
||||||
|
bool hasFocused = false;
|
||||||
|
|
||||||
|
// Check validation for each field
|
||||||
|
for (String field in requiredFields) {
|
||||||
|
if (data[field] == null || data[field]!.trim().isEmpty) {
|
||||||
|
errorMessages[field] = "Required";
|
||||||
|
|
||||||
|
// if (!hasFocused) {
|
||||||
|
// focusNodes[field]?.requestFocus();
|
||||||
|
// hasFocused = true;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorMessages.isEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> handleSubmit() async {
|
||||||
|
userId = await getUserId();
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
isDisable = true;
|
||||||
|
// This triggers UI rebuild with error messages
|
||||||
|
if (validateData()) {
|
||||||
|
postCostCenterData();
|
||||||
|
} else {
|
||||||
|
isDisable = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final costcenterData1 = costcenterDetails();
|
||||||
|
print("submit data - $costcenterData1");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> postCostCenterData({int isActive = 1}) async {
|
||||||
|
// final remarksData = getData();
|
||||||
|
|
||||||
|
final costcenterData = costcenterDetails();
|
||||||
|
|
||||||
|
print("initially value of the CostCenter - $costcenterData");
|
||||||
|
|
||||||
|
final String apiUrldata;
|
||||||
|
|
||||||
|
if (costcenterDataId != null) {
|
||||||
|
print("for edit costcenter id - $costcenterDataId");
|
||||||
|
apiUrldata = '$apiUrl/api/updateCostCenter/$costcenterDataId';
|
||||||
|
costcenterData["cost_center_id"] = costcenterDataId.toString();
|
||||||
|
costcenterData["updated_by"] = userId;
|
||||||
|
(costcenterData.containsKey("created_by"))
|
||||||
|
? costcenterData.remove("created_by")
|
||||||
|
: '';
|
||||||
|
} else {
|
||||||
|
print("for add CostCenter id - null");
|
||||||
|
apiUrldata = '$apiUrl/api/createCostCenter';
|
||||||
|
print("called apiUrl - $apiUrldata");
|
||||||
|
costcenterData["created_by"] = userId;
|
||||||
|
}
|
||||||
|
print("recently CostCenter data - $costcenterData");
|
||||||
|
final token = await getToken(); // Fetch token
|
||||||
|
|
||||||
|
if (token == null) {
|
||||||
|
throw Exception('Token not found. Please log in.');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final uri = Uri.parse(apiUrldata);
|
||||||
|
final headers = {
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'app-signature': 'ts-traveltool-2025-signature-123456',
|
||||||
|
};
|
||||||
|
final body = jsonEncode(costcenterData);
|
||||||
|
|
||||||
|
final response =
|
||||||
|
costcenterDataId != null
|
||||||
|
? await http.put(uri, headers: headers, body: body)
|
||||||
|
: await http.post(uri, headers: headers, body: body);
|
||||||
|
|
||||||
|
switch (response.statusCode) {
|
||||||
|
case 200:
|
||||||
|
print("Update - Response: ${response.body}");
|
||||||
|
_clearError();
|
||||||
|
widget.fetchGetCostCenter();
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.of(context).pop(); // Close modal only if mounted
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
isDisable = false;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 201:
|
||||||
|
print("Save - Response: ${response.body}");
|
||||||
|
_clearError();
|
||||||
|
await widget.fetchGetCostCenter();
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.of(context).pop(); // Close modal only if mounted
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
isDisable = false;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 403:
|
||||||
|
print("403-FORB");
|
||||||
|
await apiService.logout(context);
|
||||||
|
break;
|
||||||
|
// throw Exception('Failed to load users');
|
||||||
|
|
||||||
|
default:
|
||||||
|
print("Failed to submit costcenter. Status: ${response.statusCode}");
|
||||||
|
print("Error: ${response.body}");
|
||||||
|
setState(() {
|
||||||
|
isDisable = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print(" Error submitting plan: $e");
|
||||||
|
setState(() {
|
||||||
|
isDisable = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30),
|
||||||
|
// contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 10),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
// Row 1: Title + Edit + Delete buttons
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
(costcenterDataId != null)
|
||||||
|
? 'Edit CostCenter'
|
||||||
|
: 'Create CostCenter',
|
||||||
|
style: GoogleFonts.poppins(fontSize: 15, color: Colors.black),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Divider(thickness: 0.2, color: Colors.blueGrey.shade100),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Name *",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldForexWrapper(
|
||||||
|
isFocused: focusStates["nameFocused"] ?? false,
|
||||||
|
isDesktop: widget.isDesktop,
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: TextField(
|
||||||
|
controller: controllers["name"],
|
||||||
|
focusNode: focusNodes["nameFocusNode"],
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(
|
||||||
|
RegExp(r'[a-zA-Z0-9 _-]'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Name",
|
||||||
|
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (errorMessages["name"] != null) ...[
|
||||||
|
SizedBox(height: 5), // Space before error message
|
||||||
|
Text(
|
||||||
|
errorMessages["name"]!,
|
||||||
|
style: TextStyle(color: Colors.red, fontSize: 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 15),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Description *",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldForexWrapper(
|
||||||
|
isFocused: focusStates["descriptionFocused"] ?? false,
|
||||||
|
isDesktop: widget.isDesktop,
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 100,
|
||||||
|
child: TextField(
|
||||||
|
controller: controllers["description"],
|
||||||
|
focusNode: focusNodes["descriptionFocusNode"],
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(
|
||||||
|
RegExp(r'[a-zA-Z0-9 _-]'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
maxLines: null,
|
||||||
|
expands: true,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Description",
|
||||||
|
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (errorMessages["description"] != null) ...[
|
||||||
|
SizedBox(height: 5), // Space before error message
|
||||||
|
Text(
|
||||||
|
errorMessages["description"]!,
|
||||||
|
style: TextStyle(color: Colors.red, fontSize: 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 15),
|
||||||
|
if (costcenterDataId != null)
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Change Status ",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Tooltip(
|
||||||
|
message:
|
||||||
|
isActive == "1" ? "Tap to deactivate" : "Tap to activate",
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: toggleStatus,
|
||||||
|
child: Text(
|
||||||
|
isActive == "1" ? "Active" : "Inactive",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontFamily: "Inter",
|
||||||
|
color: isActive == "1" ? Colors.green : Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (costcenterDataId != null) SizedBox(height: 15),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
// SizedBox(
|
||||||
|
// child: ElevatedButton(
|
||||||
|
// onPressed: () {
|
||||||
|
// // You can get text from commentController.text
|
||||||
|
// Navigator.of(context).pop(); // Close the modal
|
||||||
|
// },
|
||||||
|
// style: ElevatedButton.styleFrom(
|
||||||
|
// backgroundColor: widget.layoutColor,
|
||||||
|
// shape: RoundedRectangleBorder(
|
||||||
|
// borderRadius: BorderRadius.circular(8),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// child: Text('Cancel',
|
||||||
|
// style: GoogleFonts.poppins(
|
||||||
|
// fontSize: 13, color: Colors.white)),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// SizedBox(
|
||||||
|
// width: 10,
|
||||||
|
// ),
|
||||||
|
SizedBox(
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed:
|
||||||
|
isDisable
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
setState(() {
|
||||||
|
isDisable = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
await handleSubmit();
|
||||||
|
// You can get text from commentController.text
|
||||||
|
// Navigator.of(context).pop(); // Close the modal
|
||||||
|
|
||||||
|
// Optional: re-enable only on error
|
||||||
|
// setState(() {
|
||||||
|
// isDisable = false;
|
||||||
|
// });
|
||||||
|
},
|
||||||
|
// onPressed: () {
|
||||||
|
// handleSubmit();
|
||||||
|
// // You can get text from commentController.text
|
||||||
|
// // Navigator.of(context).pop(); // Close the modal
|
||||||
|
// },
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: widget.layoutColor,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Save',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 11,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
// : SizedBox.shrink(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
984
lib/Screens/costCenter/costCenter_list.dart
Normal file
@ -0,0 +1,984 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
import '../../config/apiUrl.dart';
|
||||||
|
import '../../routes/custom_appBar.dart';
|
||||||
|
import '../../routes/custom_drawer.dart';
|
||||||
|
import '../../services/apiService.dart';
|
||||||
|
import '../../utils/auth_utils.dart';
|
||||||
|
import '../../utils/pagination.dart';
|
||||||
|
import '../../widgets/custom_breadcrumb_navigation.dart';
|
||||||
|
import 'costCenterDetails.dart';
|
||||||
|
|
||||||
|
class CostCenterList extends StatefulWidget {
|
||||||
|
const CostCenterList({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
CostCenterListState createState() => CostCenterListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class CostCenterListState extends State<CostCenterList> {
|
||||||
|
final GlobalKey<CostCenterListState> costCenterListKey =
|
||||||
|
GlobalKey<CostCenterListState>();
|
||||||
|
|
||||||
|
final ApiService apiService = ApiService();
|
||||||
|
Future<List<dynamic>>? futureCostCenter;
|
||||||
|
// late Future<List<dynamic>> futureCostCenter;
|
||||||
|
|
||||||
|
late Map<String, dynamic> depSingleData;
|
||||||
|
String? selectedCostCenterId;
|
||||||
|
String? orgId;
|
||||||
|
|
||||||
|
Color? layoutColor;
|
||||||
|
Color? bodyColor;
|
||||||
|
|
||||||
|
List allCostCenter = [];
|
||||||
|
List filteredCostCenter = [];
|
||||||
|
TextEditingController searchController = TextEditingController();
|
||||||
|
|
||||||
|
int currentPage = 0;
|
||||||
|
int itemsPerPage = 10;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_checkAuthAndLoadData();
|
||||||
|
// futureCostCenter = fetchGetCostCenter();
|
||||||
|
//
|
||||||
|
// futureCostCenter.then((object) {
|
||||||
|
// setState(() {
|
||||||
|
// allCostCenter = object;
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
// loadInitialData();
|
||||||
|
// });
|
||||||
|
|
||||||
|
// futurePlans = fetchPlans();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _checkAuthAndLoadData() async {
|
||||||
|
final String? token = await getToken(); // Your async function to get token
|
||||||
|
|
||||||
|
if (token == null || token.isEmpty) {
|
||||||
|
// Token doesn't exist → redirect to login
|
||||||
|
context.go(
|
||||||
|
"/",
|
||||||
|
); // or use: router.go("/") if you're using `GoRouter` directly
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!mounted) return;
|
||||||
|
try {
|
||||||
|
futureCostCenter = fetchGetCostCenter();
|
||||||
|
final roleUser = await getRoleUser();
|
||||||
|
if (roleUser != null &&
|
||||||
|
(roleUser == 'Org Admin' || roleUser == 'Travel Admin')) {
|
||||||
|
futureCostCenter?.then((object) {
|
||||||
|
setState(() {
|
||||||
|
allCostCenter = object;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
loadInitialData();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
apiService.logout(context);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("group : $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadInitialData() async {
|
||||||
|
String? layoutString = await getLayoutColor();
|
||||||
|
String? bodyStringColor = await getBodyColor();
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
layoutColor =
|
||||||
|
layoutString != null
|
||||||
|
? Color(int.parse(layoutString))
|
||||||
|
: Colors.redAccent;
|
||||||
|
|
||||||
|
bodyColor =
|
||||||
|
bodyStringColor != null
|
||||||
|
? Color(int.parse(bodyStringColor))
|
||||||
|
: Colors.white;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> getToken() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return prefs.getString('auth_token');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<dynamic>> refreshData() {
|
||||||
|
print("Calling Refresh Data");
|
||||||
|
|
||||||
|
futureCostCenter = fetchGetCostCenter();
|
||||||
|
|
||||||
|
return futureCostCenter!.then((object) {
|
||||||
|
print("Calling Refresh Data $object");
|
||||||
|
setState(() {
|
||||||
|
allCostCenter = object;
|
||||||
|
filteredCostCenter = object;
|
||||||
|
searchController.text = "";
|
||||||
|
});
|
||||||
|
return object;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<dynamic>> fetchGetCostCenter() async {
|
||||||
|
// return [];
|
||||||
|
final String apiUrlData = '$apiUrl/api/getCostCenterMaster?for=table_view';
|
||||||
|
|
||||||
|
final String? token = await getToken();
|
||||||
|
|
||||||
|
print("Fetch CostCenter");
|
||||||
|
print("2KN Here : $token");
|
||||||
|
|
||||||
|
if (token == null) {
|
||||||
|
throw Exception('Token not found. Please log in.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final response = await http.get(
|
||||||
|
Uri.parse(apiUrlData),
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'app-signature': 'ts-traveltool-2025-signature-123456',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
print("called api : $apiUrlData");
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final data = json.decode(response.body);
|
||||||
|
print(data['data']);
|
||||||
|
return data['data']; // Returning raw JSON list
|
||||||
|
} else if (response.statusCode == 403) {
|
||||||
|
print("403-FORB");
|
||||||
|
await apiService.logout(context);
|
||||||
|
return [];
|
||||||
|
// throw Exception('Failed to load users');
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to load users');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void filterCostCenter(String query) {
|
||||||
|
// print("all before filtering: $query");
|
||||||
|
// final lowerQuery = query.toLowerCase();
|
||||||
|
// setState(() {
|
||||||
|
// filteredCostCenter = allCostCenter.where((object) {
|
||||||
|
// return (object['cost_center_id']?.toLowerCase().contains(lowerQuery) ??
|
||||||
|
// false) ||
|
||||||
|
// (object['description']?.toLowerCase().contains(lowerQuery) ?? false) ||
|
||||||
|
// (object['user']?.toLowerCase().contains(lowerQuery) ?? false) ||
|
||||||
|
// (object['is_active']?.toLowerCase().contains(lowerQuery) ?? false);
|
||||||
|
// }).toList();
|
||||||
|
// });
|
||||||
|
// print("filteredPlans: $filteredCostCenter");
|
||||||
|
|
||||||
|
print("all before filtering: $query");
|
||||||
|
final lowerQuery = query.toLowerCase();
|
||||||
|
setState(() {
|
||||||
|
filteredCostCenter =
|
||||||
|
allCostCenter.where((object) {
|
||||||
|
final isActiveStatus =
|
||||||
|
object['is_active'] == "1" ? "active" : "inactive";
|
||||||
|
return (object['cost_center_id']?.toLowerCase().contains(
|
||||||
|
lowerQuery,
|
||||||
|
) ??
|
||||||
|
false) ||
|
||||||
|
(object['name']?.toLowerCase().contains(lowerQuery) ?? false) ||
|
||||||
|
(object['description']?.toLowerCase().contains(lowerQuery) ??
|
||||||
|
false) ||
|
||||||
|
(isActiveStatus.contains(lowerQuery));
|
||||||
|
}).toList();
|
||||||
|
currentPage = 0;
|
||||||
|
});
|
||||||
|
print("filteredCostCenter: $filteredCostCenter");
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ResponsiveBuilder(
|
||||||
|
builder: (context, sizingInfo) {
|
||||||
|
bool isDesktop =
|
||||||
|
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Color(0xFFf5f5f5),
|
||||||
|
// appBar: isDesktop ? null : const CustomAppBar(title: 'User Management'),
|
||||||
|
// drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
|
||||||
|
appBar: CustomAppBar(isDesktop: isDesktop),
|
||||||
|
drawer: CustomDrawer(isDesktop: false),
|
||||||
|
body: Padding(
|
||||||
|
padding:
|
||||||
|
isDesktop
|
||||||
|
? EdgeInsets.symmetric(
|
||||||
|
horizontal:
|
||||||
|
MediaQuery.of(context).size.width *
|
||||||
|
0.1, // 30% of screen width as horizontal padding
|
||||||
|
vertical:
|
||||||
|
MediaQuery.of(context).size.height *
|
||||||
|
0, // 5% of screen height as vertical padding
|
||||||
|
)
|
||||||
|
: EdgeInsets.all(0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// if (isDesktop) CustomDrawer(isDesktop: true),
|
||||||
|
// const Expanded(child: Center(child: Text("User Page Content"))),
|
||||||
|
Expanded(child: buildGroupList(isDesktop)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildGroupList(bool isDesktop) {
|
||||||
|
return Container(
|
||||||
|
margin: isDesktop ? const EdgeInsets.only(top: 10.0, bottom: 10.0) : null,
|
||||||
|
padding: const EdgeInsets.all(1),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isDesktop ? Colors.white : Color(0xFFFCFCFC),
|
||||||
|
),
|
||||||
|
// decoration: BoxDecoration(
|
||||||
|
// // color: Colors.amber,
|
||||||
|
// // color: bodyColor,
|
||||||
|
// color: Color(0xFFE1F5FE),
|
||||||
|
// border: Border.all(
|
||||||
|
// color: Colors.white,
|
||||||
|
// // color: Color(0xFFF7F7FB),
|
||||||
|
// width: 3.5)),
|
||||||
|
child: buildUserTable(isDesktop),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildUserTable(bool isDesktop) {
|
||||||
|
return Container(
|
||||||
|
// margin: isDesktop
|
||||||
|
// ? EdgeInsets.all(10.0)
|
||||||
|
// : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0),
|
||||||
|
// padding: const EdgeInsets.all(10),
|
||||||
|
height:
|
||||||
|
isDesktop
|
||||||
|
? MediaQuery.of(context).size.height * 0.98
|
||||||
|
: MediaQuery.of(context).size.height,
|
||||||
|
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(10.0),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.white,
|
||||||
|
padding: const EdgeInsets.all(10.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
// Divider(
|
||||||
|
// thickness: 0.2, // how "thick" the line is
|
||||||
|
// color: Colors.grey, // optional
|
||||||
|
// ),
|
||||||
|
Row(
|
||||||
|
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
child: BreadcrumbNavigation(
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
breadcrumbItems: [
|
||||||
|
BreadcrumbItem(
|
||||||
|
title: 'Org Settings',
|
||||||
|
tooltip: 'Go To Organization Settings',
|
||||||
|
onTap: (context) {
|
||||||
|
context.go("/OrganizationSettings");
|
||||||
|
},
|
||||||
|
),
|
||||||
|
BreadcrumbItem(title: 'Cost Center Details'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Text(
|
||||||
|
// 'Cost Center Details',
|
||||||
|
// style: GoogleFonts.poppins(
|
||||||
|
// fontSize: isDesktop ? 16 : 14,
|
||||||
|
// fontWeight: FontWeight.w600,
|
||||||
|
// color: Colors.black,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
SizedBox(width: MediaQuery.of(context).size.width * 0.08),
|
||||||
|
|
||||||
|
// SizedBox(width: MediaQuery.of(context).size.width * 0.16),
|
||||||
|
if (isDesktop)
|
||||||
|
Container(
|
||||||
|
width: MediaQuery.of(context).size.width * 0.2,
|
||||||
|
height: 40,
|
||||||
|
child: TextField(
|
||||||
|
controller: searchController,
|
||||||
|
onChanged: filterCostCenter,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "Search ...",
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Color(0xFF9E9DBD),
|
||||||
|
),
|
||||||
|
prefixIcon: Icon(
|
||||||
|
Icons.search,
|
||||||
|
color: Color(0xFF9E9DBD),
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: Colors.grey.shade200,
|
||||||
|
width: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: Colors.grey.shade300,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
style: GoogleFonts.poppins(fontSize: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// SizedBox(width: 16),
|
||||||
|
Spacer(),
|
||||||
|
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Color(0xFF114D8B),
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
disabledBackgroundColor: Color(0xFF114D8B),
|
||||||
|
disabledForegroundColor: Colors.white,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
side: BorderSide(color: Color(0xFF114D8B), width: 2),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 20,
|
||||||
|
vertical: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(context) => CostCenterData(
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
layoutColor: layoutColor!,
|
||||||
|
fetchGetCostCenter: refreshData,
|
||||||
|
|
||||||
|
// role:
|
||||||
|
// "Travel Agent"
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize:
|
||||||
|
MainAxisSize.min, // Ensures content fits nicely
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Add Cost Center",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: isDesktop ? 13 : 11,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 8), // spacing between icon and text
|
||||||
|
Icon(
|
||||||
|
Icons.add_circle_outline_rounded,
|
||||||
|
size: 15,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
if (!isDesktop) SizedBox(height: 5),
|
||||||
|
isDesktop
|
||||||
|
? SizedBox.shrink()
|
||||||
|
: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: MediaQuery.of(context).size.width * 0.8,
|
||||||
|
height: 35,
|
||||||
|
child: TextField(
|
||||||
|
controller: searchController,
|
||||||
|
onChanged: filterCostCenter,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "Search ...",
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Color(0xFF9E9DBD),
|
||||||
|
),
|
||||||
|
prefixIcon: Icon(
|
||||||
|
Icons.search,
|
||||||
|
color: Color(0xFF9E9DBD),
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: Colors.grey.shade200,
|
||||||
|
width: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: Colors.grey.shade300,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
style: GoogleFonts.poppins(fontSize: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// SizedBox(width: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
FutureBuilder<List<dynamic>>(
|
||||||
|
future: futureCostCenter,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final adjHgt = MediaQuery.of(context).size.height;
|
||||||
|
if (futureCostCenter == null) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
} else if (snapshot.hasError ||
|
||||||
|
!snapshot.hasData ||
|
||||||
|
snapshot.data!.isEmpty) {
|
||||||
|
return Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
// const Icon(Icons.error_outline,
|
||||||
|
// color: Colors.redAccent, size: 60),
|
||||||
|
// const SizedBox(height: 16),
|
||||||
|
// Text(
|
||||||
|
// "Oops!",
|
||||||
|
// style: GoogleFonts.poppins(
|
||||||
|
// fontSize: 20,
|
||||||
|
// fontWeight: FontWeight.bold,
|
||||||
|
// color: Colors.redAccent),
|
||||||
|
// ),
|
||||||
|
SizedBox(height: adjHgt / 4),
|
||||||
|
Text(
|
||||||
|
"No Data Found ",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
// Text(
|
||||||
|
// "Please Create CostCenter Details",
|
||||||
|
// textAlign: TextAlign.center,
|
||||||
|
// style: GoogleFonts.poppins(
|
||||||
|
// fontSize: 16,
|
||||||
|
// color: Colors.grey,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// const SizedBox(height: 20),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/* Here collect the list to displayed the data in table or card Used */
|
||||||
|
List<dynamic> object =
|
||||||
|
filteredCostCenter.isNotEmpty
|
||||||
|
? filteredCostCenter
|
||||||
|
: allCostCenter;
|
||||||
|
|
||||||
|
/* List is Sorting here */
|
||||||
|
object.sort((a, b) {
|
||||||
|
DateTime dateA = DateTime.parse(a['created_on']);
|
||||||
|
DateTime dateB = DateTime.parse(b['created_on']);
|
||||||
|
|
||||||
|
return dateB.compareTo(dateA); // Descending: newest first
|
||||||
|
});
|
||||||
|
|
||||||
|
/* For pagination for list ... */
|
||||||
|
List paginatedCostCenter =
|
||||||
|
object
|
||||||
|
.skip(currentPage * itemsPerPage)
|
||||||
|
.take(itemsPerPage)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
/* Table ... */
|
||||||
|
Widget table = LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
double minWidth = isDesktop ? constraints.maxWidth : 1300;
|
||||||
|
|
||||||
|
return ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(minWidth: minWidth),
|
||||||
|
child: DataTable(
|
||||||
|
dividerThickness: 0.5,
|
||||||
|
columnSpacing: isDesktop ? 24.0 : 16.0,
|
||||||
|
border: TableBorder(
|
||||||
|
horizontalInside: BorderSide(
|
||||||
|
width: 0.5,
|
||||||
|
color: Colors.grey.shade200,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
columns: [
|
||||||
|
DataColumn(
|
||||||
|
label: Text(
|
||||||
|
'Name',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DataColumn(
|
||||||
|
label: Text(
|
||||||
|
'Description',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DataColumn(
|
||||||
|
label: Text(
|
||||||
|
'Status',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DataColumn(
|
||||||
|
label: Text(
|
||||||
|
'Actions',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
rows:
|
||||||
|
paginatedCostCenter.map((tableObject) {
|
||||||
|
String costcenterId =
|
||||||
|
tableObject['cost_center_id']
|
||||||
|
.toString(); // Get user ID
|
||||||
|
bool isSelected =
|
||||||
|
selectedCostCenterId == costcenterId;
|
||||||
|
|
||||||
|
return DataRow(
|
||||||
|
cells: [
|
||||||
|
DataCell(
|
||||||
|
Text(
|
||||||
|
tableObject['name'] ?? '',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontFamily: "Inter",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DataCell(
|
||||||
|
Text(
|
||||||
|
tableObject['description'] ?? 'N/A',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontFamily: "Inter",
|
||||||
|
),
|
||||||
|
softWrap: true,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DataCell(
|
||||||
|
Text(
|
||||||
|
tableObject['is_active'] == "1"
|
||||||
|
? 'Active'
|
||||||
|
: 'Inactive',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontFamily: "Inter",
|
||||||
|
color:
|
||||||
|
tableObject['is_active'] == "1"
|
||||||
|
? Colors.green
|
||||||
|
: Colors.grey,
|
||||||
|
),
|
||||||
|
softWrap: true,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DataCell(
|
||||||
|
// UserActionsMenu(
|
||||||
|
// user: forex,
|
||||||
|
// getUserDetails: (id) =>
|
||||||
|
// apiService.getSingleUser(id),
|
||||||
|
// ),
|
||||||
|
GestureDetector(
|
||||||
|
child: Tooltip(
|
||||||
|
message: 'Edit CostCenter Details',
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/IconsImg/edit.png',
|
||||||
|
width: 20,
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () async {
|
||||||
|
// final userId = getUserId(user['user_id']);
|
||||||
|
// final usersData = await getUserDetails(userId);
|
||||||
|
//
|
||||||
|
final costcenterId = int.tryParse(
|
||||||
|
tableObject['cost_center_id']
|
||||||
|
.toString(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (costcenterId != null) {
|
||||||
|
print(
|
||||||
|
"Table cell - costcenter Id -- $costcenterId",
|
||||||
|
);
|
||||||
|
final data = await apiService
|
||||||
|
.getCostCenterDetailsFind(
|
||||||
|
context,
|
||||||
|
costcenterId,
|
||||||
|
);
|
||||||
|
print("CostCenterId -- $data");
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(context) => CostCenterData(
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
costcenterId:
|
||||||
|
costcenterId, // Pass the ID
|
||||||
|
costcenterData: data,
|
||||||
|
layoutColor: layoutColor!,
|
||||||
|
// fetchGetForex: fetchGetForex,
|
||||||
|
fetchGetCostCenter:
|
||||||
|
refreshData,
|
||||||
|
// role:
|
||||||
|
// "Travel Agent"
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
print("Invalid ID");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Card ... */
|
||||||
|
Widget buildMobileCardView(List<dynamic> paginatedUser) {
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: paginatedUser.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final cardObject = paginatedUser[index];
|
||||||
|
return Card(
|
||||||
|
color: Colors.white,
|
||||||
|
margin: EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 6,
|
||||||
|
),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
elevation: 3,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Status and Employee Code
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
cardObject['name'] ?? 'N/A',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 10,
|
||||||
|
color: Colors.black87,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
GestureDetector(
|
||||||
|
child: Tooltip(
|
||||||
|
message: 'Edit CostCenter Details',
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/IconsImg/edit.png',
|
||||||
|
width: 20,
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () async {
|
||||||
|
// final userId = getUserId(user['user_id']);
|
||||||
|
// final usersData = await getUserDetails(userId);
|
||||||
|
//
|
||||||
|
final costcenterId = int.tryParse(
|
||||||
|
cardObject['cost_center_id']
|
||||||
|
.toString(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (costcenterId != null) {
|
||||||
|
print(
|
||||||
|
"costcenterId -- $costcenterId",
|
||||||
|
);
|
||||||
|
final data = await apiService
|
||||||
|
.getCostCenterDetailsFind(
|
||||||
|
context,
|
||||||
|
costcenterId,
|
||||||
|
);
|
||||||
|
print("CostCenterId -- $data");
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(context) => CostCenterData(
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
costcenterId:
|
||||||
|
costcenterId, // Pass the ID
|
||||||
|
costcenterData: data,
|
||||||
|
layoutColor: layoutColor!,
|
||||||
|
// fetchGetCostCenter: fetchGetCostCenter,
|
||||||
|
fetchGetCostCenter:
|
||||||
|
refreshData,
|
||||||
|
// role:
|
||||||
|
// "Travel Agent"
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
print("Invalid ID");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// PopupMenuButton<int>(
|
||||||
|
// color: Colors.white,
|
||||||
|
// padding: EdgeInsets.zero,
|
||||||
|
// offset: Offset(0, 30),
|
||||||
|
// icon: Icon(
|
||||||
|
// Icons.more_vert,
|
||||||
|
// color: Color(0xFF475569),
|
||||||
|
// size: 14,
|
||||||
|
// ),
|
||||||
|
// itemBuilder: (context) => [
|
||||||
|
// CustomPopupMenuEntry(
|
||||||
|
// child: Container(
|
||||||
|
// padding: EdgeInsets.symmetric(
|
||||||
|
// horizontal: 8, vertical: 8),
|
||||||
|
// child: Row(
|
||||||
|
// mainAxisSize:
|
||||||
|
// MainAxisSize.min,
|
||||||
|
// mainAxisAlignment:
|
||||||
|
// MainAxisAlignment.center,
|
||||||
|
// children: [
|
||||||
|
// IconButton(
|
||||||
|
// icon: Icon(
|
||||||
|
// Icons
|
||||||
|
// .remove_red_eye,
|
||||||
|
// color: Color(
|
||||||
|
// 0xFF475569),
|
||||||
|
// size: 18),
|
||||||
|
// onPressed: () {
|
||||||
|
// print(
|
||||||
|
// "USerDAta - $user");
|
||||||
|
// // dynamic usersData = apiService
|
||||||
|
// // .getSingleUser(user[
|
||||||
|
// // 'user_id']
|
||||||
|
// // is String
|
||||||
|
// // ? int.parse(user[
|
||||||
|
// // 'user_id'])
|
||||||
|
// // : user[
|
||||||
|
// // 'user_id']);
|
||||||
|
// //
|
||||||
|
// // print(
|
||||||
|
// // "USerDAta - $usersData");
|
||||||
|
//
|
||||||
|
// context.go(
|
||||||
|
// "/CreateUserDetails",
|
||||||
|
// extra: {
|
||||||
|
// "selectedUser":
|
||||||
|
// user,
|
||||||
|
// "isViewMode": true
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
// }),
|
||||||
|
// IconButton(
|
||||||
|
// icon: Image.asset(
|
||||||
|
// 'assets/images/IconsImg/edit.png',
|
||||||
|
// width: 20,
|
||||||
|
// height: 15),
|
||||||
|
// onPressed: () {
|
||||||
|
// context.go(
|
||||||
|
// "/CreateUserDetails",
|
||||||
|
// extra: {
|
||||||
|
// "selectedUser":
|
||||||
|
// user,
|
||||||
|
// "isViewMode": false
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 2),
|
||||||
|
// Trip Id and Trip Name
|
||||||
|
// Name
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
cardObject['description'] ?? '',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 10,
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
cardObject['description'] ?? '',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 10,
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
// Actions
|
||||||
|
// Actions
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Expanded(
|
||||||
|
child: Column(
|
||||||
|
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child:
|
||||||
|
isDesktop
|
||||||
|
? (searchController.text.isNotEmpty &&
|
||||||
|
filteredCostCenter.isEmpty
|
||||||
|
? Center(
|
||||||
|
child: Text(
|
||||||
|
"No Matches Found",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
|
child: table,
|
||||||
|
))
|
||||||
|
: (searchController.text.isNotEmpty &&
|
||||||
|
filteredCostCenter.isEmpty
|
||||||
|
? Center(
|
||||||
|
child: Text(
|
||||||
|
"No Matches Found",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: buildMobileCardView(
|
||||||
|
paginatedCostCenter,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
// Expanded(
|
||||||
|
// child: isDesktop
|
||||||
|
// ? SingleChildScrollView(
|
||||||
|
// scrollDirection: Axis.vertical,
|
||||||
|
// child: table, // <-- your existing table
|
||||||
|
// )
|
||||||
|
// : buildMobileCardView(paginatedCostCenter),
|
||||||
|
// ),
|
||||||
|
PaginationControls(
|
||||||
|
currentPage: currentPage,
|
||||||
|
itemsPerPage: itemsPerPage,
|
||||||
|
totalItems: object.length,
|
||||||
|
activeColor: layoutColor, // your theme color
|
||||||
|
onPageChanged: (page) {
|
||||||
|
setState(() {
|
||||||
|
currentPage = page;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onItemsPerPageChanged: (items) {
|
||||||
|
setState(() {
|
||||||
|
itemsPerPage = items;
|
||||||
|
currentPage = 0;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -9,27 +9,19 @@ class HomePage extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
return ResponsiveBuilder(builder: (context, sizingInfo) {
|
||||||
return ResponsiveBuilder(
|
|
||||||
builder: (context, sizingInfo) {
|
|
||||||
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: isDesktop ? null : const CustomAppBar(title:'Home'),
|
appBar: CustomAppBar(isDesktop: isDesktop),
|
||||||
drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
|
drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
|
||||||
body:
|
body: Row(
|
||||||
Row(
|
|
||||||
children: [
|
children: [
|
||||||
if(isDesktop)
|
if (isDesktop) CustomDrawer(isDesktop: true),
|
||||||
CustomDrawer(isDesktop: true),
|
|
||||||
const Expanded(child: Center(child: Text("Home Page Content"))),
|
const Expanded(child: Center(child: Text("Home Page Content"))),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
423
lib/Screens/dashboard/status_dashboard (copy).dart
Normal file
@ -0,0 +1,423 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:html' as html;
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:frontend/routes/custom_router.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
import '../../config/apiUrl.dart';
|
||||||
|
import '../../routes/custom_appBar.dart';
|
||||||
|
import '../../routes/custom_drawer.dart';
|
||||||
|
import '../../services/apiService.dart';
|
||||||
|
import '../../utils/auth_utils.dart';
|
||||||
|
import '../../utils/pagination.dart';
|
||||||
|
|
||||||
|
class StatusDashboard extends StatefulWidget {
|
||||||
|
const StatusDashboard({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
StatusDashboardState createState() => StatusDashboardState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class StatusDashboardState extends State<StatusDashboard> {
|
||||||
|
Map<String, dynamic>? apiData;
|
||||||
|
String? organizationId;
|
||||||
|
late bool _dialogShown = false;
|
||||||
|
late StreamSubscription<html.PopStateEvent> _popStateListener;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_checkAuthAndLoadData();
|
||||||
|
|
||||||
|
checkbackbutton();
|
||||||
|
|
||||||
|
// loadDashboardData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkbackbutton() async {
|
||||||
|
// Push a dummy state so back button triggers popstate instead of navigating
|
||||||
|
html.window.history.pushState(null, 'home', html.window.location.href);
|
||||||
|
|
||||||
|
_popStateListener = html.window.onPopState.listen((event) {
|
||||||
|
if (!_dialogShown && mounted) {
|
||||||
|
_showBackConfirmationDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-push to prevent leaving
|
||||||
|
html.window.history.pushState(null, 'home', html.window.location.href);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_popStateListener.cancel(); // ✅ Remove the browser popstate listener
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showBackConfirmationDialog() {
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
_dialogShown = true;
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(context) => AlertDialog(
|
||||||
|
title: Text("Confirm"),
|
||||||
|
content: Text("Do you want to logout?"),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context); // Close dialog
|
||||||
|
_dialogShown = false;
|
||||||
|
},
|
||||||
|
child: Text("Cancel"),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.pop(context); // Close dialog
|
||||||
|
_dialogShown = false;
|
||||||
|
await _logoutAndRedirect(context);
|
||||||
|
},
|
||||||
|
child: Text("Logout"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _logoutAndRedirect(BuildContext context) async {
|
||||||
|
print("logue 0");
|
||||||
|
|
||||||
|
// Example: clear session or shared preferences
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.clear();
|
||||||
|
|
||||||
|
context.go("/");
|
||||||
|
|
||||||
|
print("logue 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
void _checkAuthAndLoadData() async {
|
||||||
|
final String? token = await getToken(); // Your async function to get token
|
||||||
|
|
||||||
|
if (token == null || token.isEmpty) {
|
||||||
|
// Token doesn't exist → redirect to login
|
||||||
|
context.go(
|
||||||
|
"/",
|
||||||
|
); // or use: router.go("/") if you're using `GoRouter` directly
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
checkbackbutton();
|
||||||
|
});
|
||||||
|
// If token exists, load the dashboard data
|
||||||
|
loadDashboardData();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadDashboardData() async {
|
||||||
|
try {
|
||||||
|
final data = await fetchStatusDashboard();
|
||||||
|
setState(() {
|
||||||
|
apiData = data;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
print("Error loading dashboard: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> getToken() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return prefs.getString('auth_token');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> getOrgId() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final String? userDataString = prefs.getString('user_data');
|
||||||
|
|
||||||
|
if (userDataString != null) {
|
||||||
|
try {
|
||||||
|
final Map<String, dynamic> userData = jsonDecode(userDataString);
|
||||||
|
return userData["org_id"]?.toString();
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> fetchStatusDashboard() async {
|
||||||
|
organizationId = await getOrgId();
|
||||||
|
|
||||||
|
final String apiUrlData =
|
||||||
|
'$apiUrl/api/plans/statusDashboard?org_id=$organizationId';
|
||||||
|
final String? token = await getToken();
|
||||||
|
|
||||||
|
print("Fetch StatusDashboard -- 2KN Here : $token");
|
||||||
|
|
||||||
|
if (token == null) {
|
||||||
|
throw Exception('Token not found. Please log in.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final response = await http.get(
|
||||||
|
Uri.parse(apiUrlData),
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
print("called api : $apiUrlData");
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final apiData = json.decode(response.body);
|
||||||
|
print("Fetch StatusDashboard -- Reponse Here : $apiData");
|
||||||
|
return apiData; // Returning raw JSON list
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to load users');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getStatusColor(String status) {
|
||||||
|
if (status == "Domestic") return Colors.white;
|
||||||
|
if (status == "International") return Colors.white;
|
||||||
|
if (status == "Partially Approved") return Colors.yellow.shade100;
|
||||||
|
if (status == "Pending Approval") return Colors.greenAccent.shade100;
|
||||||
|
if (status == "Approved") return Colors.green.shade100;
|
||||||
|
if (status == "Completed") return Colors.green.shade500;
|
||||||
|
if (status == "Rejected") return Colors.red.shade100;
|
||||||
|
if (status == "Cancelled") return Colors.red.shade200;
|
||||||
|
return Colors.grey.shade100;
|
||||||
|
}
|
||||||
|
|
||||||
|
IconData getStatusIcon(String title) {
|
||||||
|
switch (title) {
|
||||||
|
case "Domestic":
|
||||||
|
return Icons.directions_bus_filled_outlined;
|
||||||
|
case "International":
|
||||||
|
return Icons.airplanemode_active;
|
||||||
|
case "Partially Approved":
|
||||||
|
return Icons.pending;
|
||||||
|
case "Approved":
|
||||||
|
return Icons.check_circle_outline;
|
||||||
|
case "Completed":
|
||||||
|
return Icons.done_all;
|
||||||
|
case "Rejected":
|
||||||
|
return Icons.cancel_outlined;
|
||||||
|
case "Cancelled":
|
||||||
|
return Icons.cancel_schedule_send_outlined;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return Icons.info_outline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final typeBasedCount = List<Map<String, dynamic>>.from(
|
||||||
|
(apiData?['data']?['typeBasedCount']) ?? [],
|
||||||
|
);
|
||||||
|
|
||||||
|
print("apiData - => $apiData");
|
||||||
|
print("typeBasedCount => $typeBasedCount");
|
||||||
|
|
||||||
|
final statusBasedCount = List<Map<String, dynamic>>.from(
|
||||||
|
(apiData?['data']?['statusBasedCount']) ?? [],
|
||||||
|
);
|
||||||
|
print("statusBasedCount - => $statusBasedCount");
|
||||||
|
// 👇 Local function to create the card widget
|
||||||
|
Widget buildInfoCard(String title, int count, double width) {
|
||||||
|
print('title $title');
|
||||||
|
final color = getStatusColor(title);
|
||||||
|
final icon = getStatusIcon(title);
|
||||||
|
return Card(
|
||||||
|
elevation: 3,
|
||||||
|
color: color,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
child: Container(
|
||||||
|
width: width,
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
icon,
|
||||||
|
size: 32,
|
||||||
|
color:
|
||||||
|
(title == 'Domestic' || title == 'International')
|
||||||
|
? Colors.green
|
||||||
|
: Colors.black54,
|
||||||
|
), // 👈 Add Icon here
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize:
|
||||||
|
(title == 'Domestic' || title == 'International')
|
||||||
|
? 16
|
||||||
|
: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
count.toString(),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
// color: Colors.blue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleBackButton() {
|
||||||
|
final location = GoRouterState.of(context).uri.toString();
|
||||||
|
|
||||||
|
print("location - $location");
|
||||||
|
if (location.contains('/StatusDashboard')) {
|
||||||
|
// Do nothing or show "Press again to exit" toast
|
||||||
|
print("Blocked back on dashboard");
|
||||||
|
} else {
|
||||||
|
print("dashboard ..");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponsiveBuilder(
|
||||||
|
builder: (context, sizingInfo) {
|
||||||
|
bool isDesktop =
|
||||||
|
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||||
|
|
||||||
|
return PopScope(
|
||||||
|
canPop: false, // Allow back navigation only if not login screen
|
||||||
|
onPopInvokedWithResult: (didPop, result) {
|
||||||
|
if (didPop) return;
|
||||||
|
_handleBackButton(); // Show exit confirmation dialog
|
||||||
|
},
|
||||||
|
child: Scaffold(
|
||||||
|
backgroundColor: const Color(0xFFf5f5f5),
|
||||||
|
appBar: CustomAppBar(isDesktop: isDesktop),
|
||||||
|
drawer: CustomDrawer(isDesktop: false),
|
||||||
|
body: Padding(
|
||||||
|
padding:
|
||||||
|
isDesktop
|
||||||
|
? EdgeInsets.symmetric(
|
||||||
|
horizontal:
|
||||||
|
MediaQuery.of(context).size.width *
|
||||||
|
0.1, // 30% of screen width as horizontal padding
|
||||||
|
vertical: 10, // 5% of screen height as vertical padding
|
||||||
|
)
|
||||||
|
: EdgeInsets.all(0),
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
minHeight: constraints.maxHeight,
|
||||||
|
),
|
||||||
|
child: IntrinsicHeight(
|
||||||
|
// Only needed if child layout depends on height
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color:
|
||||||
|
isDesktop
|
||||||
|
? Colors.white
|
||||||
|
: const Color(0xFFFCFCFC),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(30.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Wrap(
|
||||||
|
spacing: 10,
|
||||||
|
runSpacing: 10,
|
||||||
|
children:
|
||||||
|
typeBasedCount.map((item) {
|
||||||
|
double cardWidth =
|
||||||
|
isDesktop
|
||||||
|
? (MediaQuery.of(
|
||||||
|
context,
|
||||||
|
).size.width *
|
||||||
|
0.75 -
|
||||||
|
10) /
|
||||||
|
2 // 80% width padding adjusted
|
||||||
|
: MediaQuery.of(
|
||||||
|
context,
|
||||||
|
).size.width -
|
||||||
|
24; // full width with padding
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
width: cardWidth,
|
||||||
|
child: buildInfoCard(
|
||||||
|
item['value'],
|
||||||
|
item['count'],
|
||||||
|
cardWidth,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Wrap(
|
||||||
|
spacing: 10,
|
||||||
|
runSpacing: 10,
|
||||||
|
children:
|
||||||
|
statusBasedCount.map((item) {
|
||||||
|
double cardWidth =
|
||||||
|
isDesktop
|
||||||
|
? (MediaQuery.of(
|
||||||
|
context,
|
||||||
|
).size.width *
|
||||||
|
0.90 -
|
||||||
|
50) /
|
||||||
|
6 // desktop layout: 6 cards per row
|
||||||
|
: MediaQuery.of(
|
||||||
|
context,
|
||||||
|
).size.width -
|
||||||
|
24; // mobile: full width
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
width: cardWidth,
|
||||||
|
child: buildInfoCard(
|
||||||
|
item['value'],
|
||||||
|
item['count'],
|
||||||
|
cardWidth,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
1256
lib/Screens/dashboard/status_dashboard.dart
Normal file
934
lib/Screens/dashboard/status_dashboard_web.dart
Normal file
@ -0,0 +1,934 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:html' as html;
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:frontend/routes/custom_router.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
import '../../config/apiUrl.dart';
|
||||||
|
import '../../routes/custom_appBar.dart';
|
||||||
|
import '../../routes/custom_drawer.dart';
|
||||||
|
import '../../services/apiService.dart';
|
||||||
|
import '../../utils/auth_utils.dart';
|
||||||
|
import '../../utils/pagination.dart';
|
||||||
|
|
||||||
|
class StatusDashboard extends StatefulWidget {
|
||||||
|
const StatusDashboard({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
StatusDashboardState createState() => StatusDashboardState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class StatusDashboardState extends State<StatusDashboard> {
|
||||||
|
Map<String, dynamic>? apiData;
|
||||||
|
String? organizationId;
|
||||||
|
late bool _dialogShown = false;
|
||||||
|
bool loader = false;
|
||||||
|
|
||||||
|
late StreamSubscription<html.PopStateEvent> _popStateListener;
|
||||||
|
String selectedView = 'Today';
|
||||||
|
String AccessKeyFlag = "Both";
|
||||||
|
Map<String, dynamic>? currentData;
|
||||||
|
List typeData = [];
|
||||||
|
List statusData = [];
|
||||||
|
List<int> flightData = [];
|
||||||
|
List<int> accommodationData = [];
|
||||||
|
List<int> forexData = [];
|
||||||
|
final ApiService apiService = ApiService();
|
||||||
|
|
||||||
|
final categoryIcons = {
|
||||||
|
"flight": Icons.flight,
|
||||||
|
"accommodation": Icons.hotel_outlined,
|
||||||
|
"forex": Icons.attach_money,
|
||||||
|
};
|
||||||
|
|
||||||
|
List<String> statusLabels = [
|
||||||
|
"Pending Approval",
|
||||||
|
"Partially Approved",
|
||||||
|
"Approved",
|
||||||
|
"Rejected",
|
||||||
|
"Cancelled",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Extract counts for "Both" section
|
||||||
|
List<int> getStatusValues(Map<String, dynamic> serviceData) {
|
||||||
|
final both = serviceData['Both'] ?? {};
|
||||||
|
return statusLabels.map((label) => (both[label] ?? 0) as int).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_checkAuthAndLoadData();
|
||||||
|
|
||||||
|
checkbackbutton();
|
||||||
|
|
||||||
|
// loadDashboardData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkbackbutton() async {
|
||||||
|
// Push a dummy state so back button triggers popstate instead of navigating
|
||||||
|
html.window.history.pushState(null, 'home', html.window.location.href);
|
||||||
|
|
||||||
|
_popStateListener = html.window.onPopState.listen((event) {
|
||||||
|
if (!_dialogShown && mounted) {
|
||||||
|
_showBackConfirmationDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-push to prevent leaving
|
||||||
|
html.window.history.pushState(null, 'home', html.window.location.href);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_popStateListener.cancel(); // ✅ Remove the browser popstate listener
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showBackConfirmationDialog() {
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
_dialogShown = true;
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(context) => AlertDialog(
|
||||||
|
title: Text("Confirm"),
|
||||||
|
content: Text("Do you want to logout?"),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context); // Close dialog
|
||||||
|
_dialogShown = false;
|
||||||
|
},
|
||||||
|
child: Text("Cancel"),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
// Navigator.pop(context); // Close dialog
|
||||||
|
_dialogShown = false;
|
||||||
|
await _logoutAndRedirect(context);
|
||||||
|
},
|
||||||
|
child: Text("Logout"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _logoutAndRedirect(BuildContext context) async {
|
||||||
|
print("logue 0");
|
||||||
|
Navigator.pop(context); // closes dialog
|
||||||
|
_dialogShown = false;
|
||||||
|
await apiService.logout(context);
|
||||||
|
// Example: clear session or shared preferences
|
||||||
|
// final prefs = await SharedPreferences.getInstance();
|
||||||
|
// await prefs.clear();
|
||||||
|
|
||||||
|
// context.go("/");
|
||||||
|
|
||||||
|
print("logue 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
void _checkAuthAndLoadData() async {
|
||||||
|
final String? token = await getToken(); // Your async function to get token
|
||||||
|
|
||||||
|
if (token == null || token.isEmpty) {
|
||||||
|
// Token doesn't exist → redirect to login
|
||||||
|
context.go(
|
||||||
|
"/",
|
||||||
|
); // or use: router.go("/") if you're using `GoRouter` directly
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
checkbackbutton();
|
||||||
|
});
|
||||||
|
// If token exists, load the dashboard data
|
||||||
|
loadDashboardData();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadDashboardData() async {
|
||||||
|
try {
|
||||||
|
final data = await fetchStatusDashboard();
|
||||||
|
setState(() {
|
||||||
|
apiData = data;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
print("Error loading dashboard: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> getToken() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return prefs.getString('auth_token');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> getOrgId() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final String? userDataString = prefs.getString('user_data');
|
||||||
|
|
||||||
|
if (userDataString != null) {
|
||||||
|
try {
|
||||||
|
final Map<String, dynamic> userData = jsonDecode(userDataString);
|
||||||
|
return userData["org_id"]?.toString();
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>> fetchStatusDashboard() async {
|
||||||
|
organizationId = await getOrgId();
|
||||||
|
|
||||||
|
final String apiUrlData =
|
||||||
|
'$apiUrl/api/plans/statusDashboard?org_id=$organizationId';
|
||||||
|
final String? token = await getToken();
|
||||||
|
|
||||||
|
print("Fetch StatusDashboard -- 2KN Here : $token");
|
||||||
|
|
||||||
|
if (token == null) {
|
||||||
|
throw Exception('Token not found. Please log in.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final response = await http.get(
|
||||||
|
Uri.parse(apiUrlData),
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
print("called api : $apiUrlData");
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final apiData = json.decode(response.body);
|
||||||
|
print("Fetch StatusDashboard -- Reponse Here : $apiData");
|
||||||
|
return apiData; // Returning raw JSON list
|
||||||
|
} else if (response.statusCode == 403) {
|
||||||
|
print("403-FORB");
|
||||||
|
await apiService.logout(context);
|
||||||
|
return {};
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to load users');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getStatusColor(String status) {
|
||||||
|
if (status == "Domestic") return Colors.white;
|
||||||
|
if (status == "International") return Colors.white;
|
||||||
|
if (status == "Partially Approved") return Colors.yellow.shade100;
|
||||||
|
if (status == "Pending Approval") return Colors.greenAccent.shade100;
|
||||||
|
if (status == "Approved") return Colors.green.shade100;
|
||||||
|
if (status == "Completed") return Colors.green.shade500;
|
||||||
|
if (status == "Rejected") return Colors.red.shade100;
|
||||||
|
if (status == "Cancelled") return Colors.red.shade200;
|
||||||
|
return Colors.grey.shade100;
|
||||||
|
}
|
||||||
|
|
||||||
|
IconData getStatusIcon(String title) {
|
||||||
|
switch (title) {
|
||||||
|
case "Domestic":
|
||||||
|
return Icons.directions_bus_filled_outlined;
|
||||||
|
case "International":
|
||||||
|
return Icons.airplanemode_active;
|
||||||
|
case "Partially Approved":
|
||||||
|
return Icons.pending;
|
||||||
|
case "Approved":
|
||||||
|
return Icons.check_circle_outline;
|
||||||
|
case "Completed":
|
||||||
|
return Icons.done_all;
|
||||||
|
case "Rejected":
|
||||||
|
return Icons.cancel_outlined;
|
||||||
|
case "Cancelled":
|
||||||
|
return Icons.cancel_schedule_send_outlined;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return Icons.info_outline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final typeBasedCount = List<Map<String, dynamic>>.from(
|
||||||
|
(apiData?['data']?['typeBasedCount']) ?? [],
|
||||||
|
);
|
||||||
|
|
||||||
|
print("apiData - => $apiData");
|
||||||
|
print("typeBasedCount => $typeBasedCount");
|
||||||
|
|
||||||
|
final statusBasedCount = List<Map<String, dynamic>>.from(
|
||||||
|
(apiData?['data']?['statusBasedCount']) ?? [],
|
||||||
|
);
|
||||||
|
|
||||||
|
final statusBasedTodayCount = List<Map<String, dynamic>>.from(
|
||||||
|
(apiData?['data']?['statusBasedTodayCount']) ?? [],
|
||||||
|
);
|
||||||
|
|
||||||
|
final statusBasedWeeklyCount = List<Map<String, dynamic>>.from(
|
||||||
|
(apiData?['data']?['statusBasedWeeklyCount']) ?? [],
|
||||||
|
);
|
||||||
|
|
||||||
|
final typeBasedTodayCount = List<Map<String, dynamic>>.from(
|
||||||
|
(apiData?['data']?['typeBasedTodayCount']) ?? [],
|
||||||
|
);
|
||||||
|
|
||||||
|
final typeBasedWeeklyCount = List<Map<String, dynamic>>.from(
|
||||||
|
(apiData?['data']?['typeBasedWeeklyCount']) ?? [],
|
||||||
|
);
|
||||||
|
|
||||||
|
final typeTodayBasedCount = List<Map<String, dynamic>>.from(
|
||||||
|
(apiData?['data']?['typeTodayBasedCount']) ?? [],
|
||||||
|
);
|
||||||
|
|
||||||
|
final typeWeeklyBasedCount = List<Map<String, dynamic>>.from(
|
||||||
|
(apiData?['data']?['typeWeeklyBasedCount']) ?? [],
|
||||||
|
);
|
||||||
|
|
||||||
|
final statusTodayBasedCount = List<Map<String, dynamic>>.from(
|
||||||
|
(apiData?['data']?['statusTodayBasedCount']) ?? [],
|
||||||
|
);
|
||||||
|
|
||||||
|
final statusWeeklyBasedCount = List<Map<String, dynamic>>.from(
|
||||||
|
(apiData?['data']?['statusWeeklyBasedCount']) ?? [],
|
||||||
|
);
|
||||||
|
|
||||||
|
final List todayList = List<Map<String, dynamic>>.from(
|
||||||
|
apiData?['data']?['todayTripCounts'] ?? [],
|
||||||
|
);
|
||||||
|
|
||||||
|
final List weekList = List<Map<String, dynamic>>.from(
|
||||||
|
apiData?['data']?['weekTripCounts'] ?? [],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Merge all maps in the list into one map
|
||||||
|
Map<String, dynamic> todayBasedCount = {};
|
||||||
|
Map<String, dynamic> weekBasedCount = {};
|
||||||
|
|
||||||
|
for (final item in todayList) {
|
||||||
|
todayBasedCount.addAll(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final item in weekList) {
|
||||||
|
weekBasedCount.addAll(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
// currentData = selectedView == "Today" ? todayBasedCount : weekBasedCount;
|
||||||
|
typeData =
|
||||||
|
selectedView == "Today" ? typeBasedTodayCount : typeBasedWeeklyCount;
|
||||||
|
statusData =
|
||||||
|
selectedView == "Today"
|
||||||
|
? statusBasedTodayCount
|
||||||
|
: statusBasedWeeklyCount;
|
||||||
|
|
||||||
|
flightData =
|
||||||
|
selectedView == "Today"
|
||||||
|
? getStatusValues(todayBasedCount['flight'] ?? {})
|
||||||
|
: getStatusValues(weekBasedCount['flight'] ?? {});
|
||||||
|
accommodationData =
|
||||||
|
selectedView == "Today"
|
||||||
|
? getStatusValues(todayBasedCount['acomodation'] ?? {})
|
||||||
|
: getStatusValues(weekBasedCount['acomodation'] ?? {});
|
||||||
|
forexData =
|
||||||
|
selectedView == "Today"
|
||||||
|
? getStatusValues(todayBasedCount['forex'] ?? {})
|
||||||
|
: getStatusValues(weekBasedCount['forex'] ?? {});
|
||||||
|
});
|
||||||
|
|
||||||
|
print("statusBasedCount - => $statusBasedCount");
|
||||||
|
print("Current Data - => $currentData");
|
||||||
|
// 👇 Local function to create the card widget
|
||||||
|
Widget buildInfoCard(String title, int count, double width) {
|
||||||
|
print('title $title');
|
||||||
|
final color = getStatusColor(title);
|
||||||
|
final icon = getStatusIcon(title);
|
||||||
|
return Card(
|
||||||
|
elevation: 3,
|
||||||
|
color: color,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
child: Container(
|
||||||
|
width: width,
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
icon,
|
||||||
|
size: 32,
|
||||||
|
color:
|
||||||
|
(title == 'Domestic' || title == 'International')
|
||||||
|
? Colors.green
|
||||||
|
: Colors.black54,
|
||||||
|
), // 👈 Add Icon here
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize:
|
||||||
|
(title == 'Domestic' || title == 'International')
|
||||||
|
? 16
|
||||||
|
: 14,
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
count.toString(),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
// color: Colors.blue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleBackButton() {
|
||||||
|
final location = GoRouterState.of(context).uri.toString();
|
||||||
|
|
||||||
|
print("location - $location");
|
||||||
|
if (location.contains('/StatusDashboard')) {
|
||||||
|
// Do nothing or show "Press again to exit" toast
|
||||||
|
print("Blocked back on dashboard");
|
||||||
|
} else {
|
||||||
|
print("dashboard ..");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String capitalize(String s) => s[0].toUpperCase() + s.substring(1);
|
||||||
|
|
||||||
|
Widget toggleChip(String label) {
|
||||||
|
bool isSelected = selectedView == label;
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
selectedView = label;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected ? const Color(0xFF003A78) : Colors.transparent,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
height: 36,
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 10,
|
||||||
|
color: isSelected ? Colors.white : Colors.black,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildTopCard({
|
||||||
|
// required IconData icon,
|
||||||
|
required Color color,
|
||||||
|
required String label,
|
||||||
|
required int count,
|
||||||
|
required Color bgColor,
|
||||||
|
}) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(1),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(1),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Tooltip(
|
||||||
|
message: label,
|
||||||
|
child: Image.asset(
|
||||||
|
label == 'Domestic'
|
||||||
|
? 'assets/images/IconsImg/Domestic_new.png'
|
||||||
|
: 'assets/images/IconsImg/International_new.png',
|
||||||
|
// width: 65,
|
||||||
|
// height: label == 'Domestic' ? 37 : 40,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Icon(icon, color: color, size: 40),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Text(
|
||||||
|
count.toString(),
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: bgColor,
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 10,
|
||||||
|
color: color,
|
||||||
|
// color: const Color(0xFFEAF3FB),
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget statusCard(String label, int count) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFEAF3FB),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
count.toString(),
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 18,
|
||||||
|
color: Colors.black87,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildCategoryCard(
|
||||||
|
String title,
|
||||||
|
IconData icon,
|
||||||
|
List<String> statusLabels,
|
||||||
|
List<int> values,
|
||||||
|
) {
|
||||||
|
final statusIcons = [
|
||||||
|
Icons.calendar_today,
|
||||||
|
Icons.assignment,
|
||||||
|
Icons.verified,
|
||||||
|
Icons.block,
|
||||||
|
Icons.cancel,
|
||||||
|
];
|
||||||
|
|
||||||
|
final statusColors = [
|
||||||
|
Color(0xFFFBFFCA),
|
||||||
|
Color(0xFFFFF1CD), // Colors.yellow.shade100, // Color(0xFFCAE77B),
|
||||||
|
Color(0xFFDAFFE8), // Colors.green.shade100, // Color(0xFF72D480),
|
||||||
|
Color(0xFFFFD6D3), // Colors.red.shade100, // Color(0xFFF88C8C),
|
||||||
|
Color(0xFFFFA8A8), // Colors.red.shade200, // Color(0xFFE94B4B),
|
||||||
|
];
|
||||||
|
|
||||||
|
final statusIconColors = [
|
||||||
|
Color(0xFFB1BC1A),
|
||||||
|
Color(0xFFCC9300),
|
||||||
|
Color(0xFF509269),
|
||||||
|
Color(0xFFD25E55),
|
||||||
|
Color(0xFFAE0303),
|
||||||
|
];
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
// decoration: BoxDecoration(
|
||||||
|
// color: Colors.white,
|
||||||
|
// borderRadius: BorderRadius.circular(12),
|
||||||
|
// ),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.blueGrey,
|
||||||
|
blurRadius: 5,
|
||||||
|
offset: Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
// color: Colors.blue,
|
||||||
|
color: Color(0xFF004A8E),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Icon(icon, color: Colors.white, size: 20),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
color: Colors.black87,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
...List.generate(values.length, (index) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
// Icon(statusIcons[index], size: 15, color: statusIconColors[index]),
|
||||||
|
Container(
|
||||||
|
width: 28,
|
||||||
|
height: 28,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: statusColors[index], // background color
|
||||||
|
borderRadius: BorderRadius.circular(
|
||||||
|
4,
|
||||||
|
), // square with slight rounding
|
||||||
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Icon(
|
||||||
|
statusIcons[index],
|
||||||
|
size: 15,
|
||||||
|
color: statusIconColors[index], // icon color
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
statusLabels[index],
|
||||||
|
style: GoogleFonts.poppins(color: Colors.black87),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
values[index].toString(),
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 10,
|
||||||
|
color: Colors.black87,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Widget buildCategoryCard(String title, IconData icon,statusLabels,statusvalue) {
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// return Container(
|
||||||
|
// padding: const EdgeInsets.all(16),
|
||||||
|
// // decoration: BoxDecoration(
|
||||||
|
// // color: Colors.white,
|
||||||
|
// // borderRadius: BorderRadius.circular(12),
|
||||||
|
// // ),
|
||||||
|
//
|
||||||
|
// child: Column(
|
||||||
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
// children: [
|
||||||
|
// Row(
|
||||||
|
// children: [
|
||||||
|
// Container(
|
||||||
|
// padding: const EdgeInsets.all(8),
|
||||||
|
// decoration: const BoxDecoration(
|
||||||
|
// color: Colors.blue,
|
||||||
|
// shape: BoxShape.circle,
|
||||||
|
// ),
|
||||||
|
// child: Icon(
|
||||||
|
// icon,
|
||||||
|
// color: Colors.white,
|
||||||
|
// size: 20,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// const SizedBox(width: 10),
|
||||||
|
// Text(title, style: GoogleFonts.poppins(
|
||||||
|
// color: Colors.black87,
|
||||||
|
// fontWeight: FontWeight.bold
|
||||||
|
// ),),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// const SizedBox(height: 12),
|
||||||
|
// ...List.generate(statusvalue.length, (index) {
|
||||||
|
// return Padding(
|
||||||
|
// padding: const EdgeInsets.symmetric(vertical: 10),
|
||||||
|
// child: Row(
|
||||||
|
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
// children: [
|
||||||
|
// Row(
|
||||||
|
// children: [
|
||||||
|
// // Icon(statusIcons[index], size: 20, color: statusColors[index]),
|
||||||
|
// Container(
|
||||||
|
// padding: const EdgeInsets.all(8),
|
||||||
|
// decoration: BoxDecoration(
|
||||||
|
// color: statusColors[index], // Background color behind the icon
|
||||||
|
// borderRadius: BorderRadius.circular(10), // Rounded square
|
||||||
|
// boxShadow: [
|
||||||
|
// BoxShadow(
|
||||||
|
// color: Colors.grey, // Shadow color
|
||||||
|
// spreadRadius: 1,
|
||||||
|
// blurRadius: 5,
|
||||||
|
// offset: const Offset(0, 2), // Shadow position
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// child: Icon(
|
||||||
|
// statusIcons[index],
|
||||||
|
// color: statusIconColors[index],
|
||||||
|
// size: 20,
|
||||||
|
// // color: statusColors[index],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// const SizedBox(width: 6),
|
||||||
|
// Text(statusLabels[index]),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
//
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// }),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
return ResponsiveBuilder(
|
||||||
|
builder: (context, sizingInfo) {
|
||||||
|
bool isDesktop =
|
||||||
|
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||||
|
|
||||||
|
return PopScope(
|
||||||
|
canPop: false, // Allow back navigation only if not login screen
|
||||||
|
onPopInvokedWithResult: (didPop, result) {
|
||||||
|
if (didPop) return;
|
||||||
|
_handleBackButton(); // Show exit confirmation dialog
|
||||||
|
},
|
||||||
|
child: Scaffold(
|
||||||
|
backgroundColor: const Color(0xFFf5f5f5),
|
||||||
|
appBar: CustomAppBar(isDesktop: isDesktop),
|
||||||
|
drawer: CustomDrawer(isDesktop: false),
|
||||||
|
body: Padding(
|
||||||
|
padding:
|
||||||
|
isDesktop
|
||||||
|
? EdgeInsets.symmetric(
|
||||||
|
horizontal:
|
||||||
|
MediaQuery.of(context).size.width *
|
||||||
|
0.1, // 30% of screen width as horizontal padding
|
||||||
|
vertical: 10, // 5% of screen height as vertical padding
|
||||||
|
)
|
||||||
|
: EdgeInsets.all(0),
|
||||||
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
// child: ConstrainedBox(
|
||||||
|
// constraints: BoxConstraints(
|
||||||
|
// minHeight: constraints.maxHeight,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// First Row: Toggle Buttons Row
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
height: 36,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFEAEAEA),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
toggleChip("Today"),
|
||||||
|
toggleChip("This Week"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Second Row: Domestic & International and Status
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
// LEFT SIDE
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.blueGrey,
|
||||||
|
blurRadius: 5,
|
||||||
|
offset: Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(15),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
...typeData.map((item) {
|
||||||
|
return Expanded(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
buildTopCard(
|
||||||
|
// icon: item['value'] == "Domestic"
|
||||||
|
// ? Icons.home
|
||||||
|
// : Icons.travel_explore,
|
||||||
|
color:
|
||||||
|
item['value'] == "Domestic"
|
||||||
|
? Color(0xFF0DB04B)
|
||||||
|
: Color(0xFF004A8E),
|
||||||
|
label: item['value'],
|
||||||
|
count: item['count'],
|
||||||
|
bgColor:
|
||||||
|
item['value'] == "Domestic"
|
||||||
|
? const Color(0xFFD6FBE4)
|
||||||
|
: const Color(0xFFD9E8FF),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 20),
|
||||||
|
// RIGHT SIDE
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.blueGrey,
|
||||||
|
blurRadius: 5,
|
||||||
|
offset: Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(15),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Status",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 19,
|
||||||
|
color: Colors.black87,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 23),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceAround,
|
||||||
|
children: [
|
||||||
|
...statusData
|
||||||
|
.map(
|
||||||
|
(item) => statusCard(
|
||||||
|
item['value'],
|
||||||
|
item['count'],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Third Row: Flight, Accommodation, Forex
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: buildCategoryCard(
|
||||||
|
"Flight",
|
||||||
|
Icons.flight,
|
||||||
|
statusLabels,
|
||||||
|
flightData,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: buildCategoryCard(
|
||||||
|
"Accomodation",
|
||||||
|
Icons.hotel_outlined,
|
||||||
|
statusLabels,
|
||||||
|
accommodationData,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: buildCategoryCard(
|
||||||
|
"Forex",
|
||||||
|
Icons.attach_money,
|
||||||
|
statusLabels,
|
||||||
|
forexData,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
483
lib/Screens/department/departmentDetails.dart
Normal file
@ -0,0 +1,483 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
import '../../config/apiUrl.dart';
|
||||||
|
import '../../services/apiService.dart';
|
||||||
|
import '../../utils/auth_utils.dart';
|
||||||
|
import '../../widgets/custom_text_forex.dart';
|
||||||
|
import 'department_list.dart';
|
||||||
|
|
||||||
|
class DepartmentData extends StatefulWidget {
|
||||||
|
final Future<List<dynamic>> Function() fetchGetDepartment;
|
||||||
|
final bool isDesktop;
|
||||||
|
final Color? layoutColor;
|
||||||
|
|
||||||
|
final int? departmentId; // <-- Add this
|
||||||
|
final Map<String, dynamic>? departmentData;
|
||||||
|
|
||||||
|
const DepartmentData({
|
||||||
|
super.key,
|
||||||
|
required this.isDesktop,
|
||||||
|
this.layoutColor,
|
||||||
|
required this.fetchGetDepartment,
|
||||||
|
this.departmentId,
|
||||||
|
this.departmentData,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
DepartmentDataState createState() => DepartmentDataState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class DepartmentDataState extends State<DepartmentData> {
|
||||||
|
final ApiService apiService = ApiService();
|
||||||
|
Map<String, dynamic>? apiData;
|
||||||
|
|
||||||
|
// final Map<String, FocusNode> focusNodes = {
|
||||||
|
// "name": FocusNode(),
|
||||||
|
// "description": FocusNode(),
|
||||||
|
// };
|
||||||
|
|
||||||
|
final Map<String, TextEditingController> controllers = {};
|
||||||
|
Map<String, String> errorMessages = {};
|
||||||
|
Map<String, FocusNode> focusNodes = {};
|
||||||
|
Map<String, bool> focusStates = {};
|
||||||
|
String? selectedName;
|
||||||
|
String? selectedDescription;
|
||||||
|
String? userId;
|
||||||
|
int? departmentDataId;
|
||||||
|
late String isActive = "1";
|
||||||
|
bool isDisable = false;
|
||||||
|
|
||||||
|
List<String> dataHeader = ["dropdown_value", "description"];
|
||||||
|
|
||||||
|
Map<String, dynamic> departmentDetails() {
|
||||||
|
final data = {
|
||||||
|
// "department_id": int.parse(departmentId),
|
||||||
|
"dropdown_value": controllers["dropdown_value"]?.text,
|
||||||
|
"description": controllers["description"]?.text,
|
||||||
|
"created_by": userId,
|
||||||
|
"is_active": isActive,
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
apiData = null;
|
||||||
|
for (var field in dataHeader) {
|
||||||
|
controllers[field] = TextEditingController();
|
||||||
|
focusNodes["${field}FocusNode"] = FocusNode();
|
||||||
|
focusStates["${field}Focused"] = false;
|
||||||
|
}
|
||||||
|
for (var key in focusNodes.keys) {
|
||||||
|
_addFocusListener(focusNodes[key]!, (focus) {
|
||||||
|
setState(() {
|
||||||
|
focusStates[key.replaceFirst("FocusNode", "Focused")] = focus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.departmentId != null) {
|
||||||
|
print('Editing D ID: ${widget.departmentId}');
|
||||||
|
updateDepartmentDetails();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _clearError() {
|
||||||
|
setState(() {
|
||||||
|
errorMessages.clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
for (var controller in controllers.values) {
|
||||||
|
controller.dispose();
|
||||||
|
}
|
||||||
|
for (var node in focusNodes.values) {
|
||||||
|
node.dispose();
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addFocusListener(FocusNode node, Function(bool) updateState) {
|
||||||
|
node.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
updateState(node.hasFocus);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateDepartmentDetails() {
|
||||||
|
print("Inside Update Function - ${widget.departmentData}");
|
||||||
|
|
||||||
|
final data = widget.departmentData;
|
||||||
|
|
||||||
|
if (data == null) return;
|
||||||
|
setState(() {
|
||||||
|
controllers['dropdown_value']?.text = data['dropdown_value'] ?? '';
|
||||||
|
// controllers['description']?.text = data['description'].toString();
|
||||||
|
isActive = data["is_active"];
|
||||||
|
final departmentId = int.tryParse(data['id'].toString());
|
||||||
|
departmentDataId = departmentId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggleStatus() {
|
||||||
|
setState(() {
|
||||||
|
isActive = isActive == "1" ? "0" : "1";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validateData() {
|
||||||
|
errorMessages.clear();
|
||||||
|
|
||||||
|
final data = {
|
||||||
|
"dropdown_value": controllers["dropdown_value"]?.text,
|
||||||
|
// "description": controllers["description"]?.text,
|
||||||
|
};
|
||||||
|
|
||||||
|
final requiredFields = ["dropdown_value"];
|
||||||
|
bool hasFocused = false;
|
||||||
|
|
||||||
|
// Check validation for each field
|
||||||
|
for (String field in requiredFields) {
|
||||||
|
if (data[field] == null || data[field]!.trim().isEmpty) {
|
||||||
|
errorMessages[field] = "Required";
|
||||||
|
|
||||||
|
// if (!hasFocused) {
|
||||||
|
// focusNodes[field]?.requestFocus();
|
||||||
|
// hasFocused = true;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorMessages.isEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> handleSubmit() async {
|
||||||
|
userId = await getUserId();
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
isDisable = true;
|
||||||
|
// This triggers UI rebuild with error messages
|
||||||
|
if (validateData()) {
|
||||||
|
postDepartmentData();
|
||||||
|
} else {
|
||||||
|
isDisable = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final departmentData1 = departmentDetails();
|
||||||
|
print("submit data - $departmentData1");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> postDepartmentData({int isActive = 1}) async {
|
||||||
|
// final remarksData = getData();
|
||||||
|
|
||||||
|
final departmentData = departmentDetails();
|
||||||
|
|
||||||
|
print("initially value of the Department - $departmentData");
|
||||||
|
|
||||||
|
final String apiUrldata;
|
||||||
|
|
||||||
|
if (departmentDataId != null) {
|
||||||
|
print("for edit department id - $departmentDataId");
|
||||||
|
apiUrldata = '$apiUrl/api/updateDepartment/$departmentDataId';
|
||||||
|
departmentData["id"] = departmentDataId.toString();
|
||||||
|
departmentData["updated_by"] = userId;
|
||||||
|
(departmentData.containsKey("created_by"))
|
||||||
|
? departmentData.remove("created_by")
|
||||||
|
: '';
|
||||||
|
} else {
|
||||||
|
print("for add Department id - null");
|
||||||
|
apiUrldata = '$apiUrl/api/createDepartment';
|
||||||
|
print("called apiUrl - $apiUrldata");
|
||||||
|
departmentData["created_by"] = userId;
|
||||||
|
}
|
||||||
|
print("recently Department data - $departmentData");
|
||||||
|
final token = await getToken(); // Fetch token
|
||||||
|
|
||||||
|
if (token == null) {
|
||||||
|
throw Exception('Token not found. Please log in.');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final uri = Uri.parse(apiUrldata);
|
||||||
|
final headers = {
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'app-signature': 'ts-traveltool-2025-signature-123456',
|
||||||
|
};
|
||||||
|
final body = jsonEncode(departmentData);
|
||||||
|
|
||||||
|
final response =
|
||||||
|
departmentDataId != null
|
||||||
|
? await http.put(uri, headers: headers, body: body)
|
||||||
|
: await http.post(uri, headers: headers, body: body);
|
||||||
|
|
||||||
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||||
|
print("successfully!");
|
||||||
|
print("Response: ${response.body}");
|
||||||
|
_clearError();
|
||||||
|
widget.fetchGetDepartment();
|
||||||
|
// dispose();
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.of(context).pop(); // Close modal only if mounted
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
isDisable = false;
|
||||||
|
});
|
||||||
|
} else if (response.statusCode == 404) {
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
final message = jsonDecode(response.body)['message'] ?? 'Unknown error';
|
||||||
|
setState(() {
|
||||||
|
isDisable = false;
|
||||||
|
});
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(message),
|
||||||
|
backgroundColor: Colors.redAccent,
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (response.statusCode == 403) {
|
||||||
|
print("403-FORB");
|
||||||
|
await apiService.logout(context);
|
||||||
|
return null;
|
||||||
|
// throw Exception('Failed to load users');
|
||||||
|
} else {
|
||||||
|
print("Failed to submit. Status: ${response.statusCode}");
|
||||||
|
print("Error: ${response.body}");
|
||||||
|
setState(() {
|
||||||
|
isDisable = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print(" Error submitting plan: $e");
|
||||||
|
setState(() {
|
||||||
|
isDisable = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30),
|
||||||
|
// contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 10),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
// Row 1: Title + Edit + Delete buttons
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
(departmentDataId != null)
|
||||||
|
? 'Edit Department'
|
||||||
|
: 'Create Department',
|
||||||
|
style: GoogleFonts.poppins(fontSize: 15, color: Colors.black),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Divider(thickness: 0.2, color: Colors.blueGrey.shade100),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Name *",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldForexWrapper(
|
||||||
|
isFocused: focusStates["dropdown_valueFocused"] ?? false,
|
||||||
|
isDesktop: widget.isDesktop,
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: TextField(
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(
|
||||||
|
RegExp(r'[a-zA-Z0-9 _-]'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
controller: controllers["dropdown_value"],
|
||||||
|
focusNode: focusNodes["dropdown_valueFocusNode"],
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Name",
|
||||||
|
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (errorMessages["dropdown_value"] != null) ...[
|
||||||
|
SizedBox(height: 5), // Space before error message
|
||||||
|
Text(
|
||||||
|
errorMessages["dropdown_value"]!,
|
||||||
|
style: TextStyle(color: Colors.red, fontSize: 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 15),
|
||||||
|
// Column(
|
||||||
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
// children: [
|
||||||
|
// Text(
|
||||||
|
// "Description *",
|
||||||
|
// style: GoogleFonts.poppins(
|
||||||
|
// fontSize: 12,
|
||||||
|
// fontWeight: FontWeight.w600,
|
||||||
|
// color: Color(0xFF575A74),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// SizedBox(height: 5),
|
||||||
|
// CustomTextFieldForexWrapper(
|
||||||
|
// isFocused: focusStates["descriptionFocused"] ?? false,
|
||||||
|
// isDesktop: widget.isDesktop,
|
||||||
|
// color: Colors.transparent,
|
||||||
|
// child: SizedBox(
|
||||||
|
// height: 100,
|
||||||
|
// child: TextField(
|
||||||
|
// controller: controllers["description"],
|
||||||
|
// focusNode: focusNodes["descriptionFocusNode"],
|
||||||
|
// style: const TextStyle(fontSize: 12),
|
||||||
|
// maxLines: null,
|
||||||
|
// expands: true,
|
||||||
|
// decoration: const InputDecoration(
|
||||||
|
// labelText: "Description",
|
||||||
|
// labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
|
||||||
|
// floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
// border: InputBorder.none,
|
||||||
|
// contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// if (errorMessages["description"] != null) ...[
|
||||||
|
// SizedBox(height: 5), // Space before error message
|
||||||
|
// Text(
|
||||||
|
// errorMessages["description"]!,
|
||||||
|
// style: TextStyle(color: Colors.red, fontSize: 12),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// SizedBox(height: 15),
|
||||||
|
if (departmentDataId != null)
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Change Status ",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Tooltip(
|
||||||
|
message:
|
||||||
|
isActive == "1" ? "Tap to deactivate" : "Tap to activate",
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: toggleStatus,
|
||||||
|
child: Text(
|
||||||
|
isActive == "1" ? "Active" : "Inactive",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontFamily: "Inter",
|
||||||
|
color: isActive == "1" ? Colors.green : Colors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (departmentDataId != null) SizedBox(height: 15),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
// SizedBox(
|
||||||
|
// child: ElevatedButton(
|
||||||
|
// onPressed: () {
|
||||||
|
// // You can get text from commentController.text
|
||||||
|
// Navigator.of(context).pop(); // Close the modal
|
||||||
|
// },
|
||||||
|
// style: ElevatedButton.styleFrom(
|
||||||
|
// backgroundColor: widget.layoutColor,
|
||||||
|
// shape: RoundedRectangleBorder(
|
||||||
|
// borderRadius: BorderRadius.circular(8),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// child: Text('Cancel',
|
||||||
|
// style: GoogleFonts.poppins(
|
||||||
|
// fontSize: 13, color: Colors.white)),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// SizedBox(
|
||||||
|
// width: 10,
|
||||||
|
// ),
|
||||||
|
SizedBox(
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed:
|
||||||
|
isDisable
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
setState(() {
|
||||||
|
isDisable = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
await handleSubmit();
|
||||||
|
// You can get text from commentController.text
|
||||||
|
// Navigator.of(context).pop(); // Close the modal
|
||||||
|
|
||||||
|
// Optional: re-enable only on error
|
||||||
|
// setState(() {
|
||||||
|
// isDisable = false;
|
||||||
|
// });
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: widget.layoutColor,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Save',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 11,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
// : SizedBox.shrink(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
980
lib/Screens/department/department_list.dart
Normal file
@ -0,0 +1,980 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
import '../../config/apiUrl.dart';
|
||||||
|
import '../../routes/custom_appBar.dart';
|
||||||
|
import '../../routes/custom_drawer.dart';
|
||||||
|
import '../../services/apiService.dart';
|
||||||
|
import '../../utils/auth_utils.dart';
|
||||||
|
import '../../utils/pagination.dart';
|
||||||
|
import '../../widgets/custom_breadcrumb_navigation.dart';
|
||||||
|
import 'departmentDetails.dart';
|
||||||
|
|
||||||
|
class DepartmentList extends StatefulWidget {
|
||||||
|
const DepartmentList({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
DepartmentListState createState() => DepartmentListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class DepartmentListState extends State<DepartmentList> {
|
||||||
|
final GlobalKey<DepartmentListState> departmentListKey =
|
||||||
|
GlobalKey<DepartmentListState>();
|
||||||
|
|
||||||
|
final ApiService apiService = ApiService();
|
||||||
|
// late Future<List<dynamic>> futureDepartment;
|
||||||
|
Future<List<dynamic>>? futureDepartment;
|
||||||
|
|
||||||
|
late Map<String, dynamic> depSingleData;
|
||||||
|
String? selectedDepartmentId;
|
||||||
|
String? orgId;
|
||||||
|
|
||||||
|
Color? layoutColor;
|
||||||
|
Color? bodyColor;
|
||||||
|
|
||||||
|
List allDepartment = [];
|
||||||
|
List filteredDepartment = [];
|
||||||
|
TextEditingController searchController = TextEditingController();
|
||||||
|
|
||||||
|
int currentPage = 0;
|
||||||
|
int itemsPerPage = 10;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_checkAuthAndLoadData();
|
||||||
|
// futureDepartment = fetchGetDepartment();
|
||||||
|
//
|
||||||
|
// futureDepartment.then((object) {
|
||||||
|
// setState(() {
|
||||||
|
// allDepartment = object;
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
// loadInitialData();
|
||||||
|
// });
|
||||||
|
|
||||||
|
// futurePlans = fetchPlans();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _checkAuthAndLoadData() async {
|
||||||
|
final String? token = await getToken(); // Your async function to get token
|
||||||
|
|
||||||
|
if (token == null || token.isEmpty) {
|
||||||
|
// Token doesn't exist → redirect to login
|
||||||
|
context.go(
|
||||||
|
"/",
|
||||||
|
); // or use: router.go("/") if you're using `GoRouter` directly
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!mounted) return;
|
||||||
|
try {
|
||||||
|
futureDepartment = fetchGetDepartment();
|
||||||
|
final roleUser = await getRoleUser();
|
||||||
|
if (roleUser != null &&
|
||||||
|
(roleUser == 'Org Admin' || roleUser == 'Travel Admin')) {
|
||||||
|
futureDepartment?.then((object) {
|
||||||
|
setState(() {
|
||||||
|
allDepartment = object;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
loadInitialData();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
apiService.logout(context);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("group : $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadInitialData() async {
|
||||||
|
String? layoutString = await getLayoutColor();
|
||||||
|
String? bodyStringColor = await getBodyColor();
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
layoutColor =
|
||||||
|
layoutString != null
|
||||||
|
? Color(int.parse(layoutString))
|
||||||
|
: Colors.redAccent;
|
||||||
|
|
||||||
|
bodyColor =
|
||||||
|
bodyStringColor != null
|
||||||
|
? Color(int.parse(bodyStringColor))
|
||||||
|
: Colors.white;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> getToken() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
return prefs.getString('auth_token');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<dynamic>> refreshData() {
|
||||||
|
print("Calling Refresh Data");
|
||||||
|
|
||||||
|
futureDepartment = fetchGetDepartment();
|
||||||
|
|
||||||
|
return futureDepartment!.then((object) {
|
||||||
|
print("Calling Refresh Data $object");
|
||||||
|
setState(() {
|
||||||
|
allDepartment = object;
|
||||||
|
filteredDepartment = object;
|
||||||
|
searchController.text = "";
|
||||||
|
});
|
||||||
|
return object;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<dynamic>> fetchGetDepartment() async {
|
||||||
|
// return [];
|
||||||
|
final String apiUrlData = '$apiUrl/api/getDepartmentList?for=table_view';
|
||||||
|
|
||||||
|
final String? token = await getToken();
|
||||||
|
|
||||||
|
print("Fetch Department");
|
||||||
|
print("2KN Here : $token");
|
||||||
|
|
||||||
|
if (token == null) {
|
||||||
|
throw Exception('Token not found. Please log in.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final response = await http.get(
|
||||||
|
Uri.parse(apiUrlData),
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'app-signature': 'ts-traveltool-2025-signature-123456',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
print("called api : $apiUrlData");
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
final data = json.decode(response.body);
|
||||||
|
return data['data']; // Returning raw JSON list
|
||||||
|
} else if (response.statusCode == 403) {
|
||||||
|
print("403-FORB");
|
||||||
|
await apiService.logout(context);
|
||||||
|
return [];
|
||||||
|
// throw Exception('Failed to load users');
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to load users');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void filterDepartment(String query) {
|
||||||
|
// print("all before filtering: $query");
|
||||||
|
// final lowerQuery = query.toLowerCase();
|
||||||
|
// setState(() {
|
||||||
|
// filteredDepartment = allDepartment.where((object) {
|
||||||
|
// return (object['department_id']?.toLowerCase().contains(lowerQuery) ??
|
||||||
|
// false) ||
|
||||||
|
// (object['description']?.toLowerCase().contains(lowerQuery) ?? false) ||
|
||||||
|
// (object['user']?.toLowerCase().contains(lowerQuery) ?? false) ||
|
||||||
|
// (object['is_active']?.toLowerCase().contains(lowerQuery) ?? false);
|
||||||
|
// }).toList();
|
||||||
|
// });
|
||||||
|
// print("filteredPlans: $filteredDepartment");
|
||||||
|
|
||||||
|
print("all before filtering: $query");
|
||||||
|
final lowerQuery = query.toLowerCase();
|
||||||
|
setState(() {
|
||||||
|
filteredDepartment =
|
||||||
|
allDepartment.where((object) {
|
||||||
|
final isActiveStatus =
|
||||||
|
object['is_active'] == "1" ? "active" : "inactive";
|
||||||
|
return (object['id']?.toLowerCase().contains(lowerQuery) ??
|
||||||
|
false) ||
|
||||||
|
(object['dropdown_value']?.toLowerCase().contains(lowerQuery) ??
|
||||||
|
false) ||
|
||||||
|
// (object['description']?.toLowerCase().contains(lowerQuery) ??
|
||||||
|
// false) ||
|
||||||
|
(isActiveStatus.contains(lowerQuery));
|
||||||
|
}).toList();
|
||||||
|
currentPage = 0;
|
||||||
|
});
|
||||||
|
print("filteredDepartment: $filteredDepartment");
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ResponsiveBuilder(
|
||||||
|
builder: (context, sizingInfo) {
|
||||||
|
bool isDesktop =
|
||||||
|
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Color(0xFFf5f5f5),
|
||||||
|
// appBar: isDesktop ? null : const CustomAppBar(title: 'User Management'),
|
||||||
|
// drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
|
||||||
|
appBar: CustomAppBar(isDesktop: isDesktop),
|
||||||
|
drawer: CustomDrawer(isDesktop: false),
|
||||||
|
body: Padding(
|
||||||
|
padding:
|
||||||
|
isDesktop
|
||||||
|
? EdgeInsets.symmetric(
|
||||||
|
horizontal:
|
||||||
|
MediaQuery.of(context).size.width *
|
||||||
|
0.1, // 30% of screen width as horizontal padding
|
||||||
|
vertical:
|
||||||
|
MediaQuery.of(context).size.height *
|
||||||
|
0, // 5% of screen height as vertical padding
|
||||||
|
)
|
||||||
|
: EdgeInsets.all(0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// if (isDesktop) CustomDrawer(isDesktop: true),
|
||||||
|
// const Expanded(child: Center(child: Text("User Page Content"))),
|
||||||
|
Expanded(child: buildGroupList(isDesktop)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildGroupList(bool isDesktop) {
|
||||||
|
return Container(
|
||||||
|
margin: isDesktop ? const EdgeInsets.only(top: 10.0, bottom: 10.0) : null,
|
||||||
|
padding: const EdgeInsets.all(1),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isDesktop ? Colors.white : Color(0xFFFCFCFC),
|
||||||
|
),
|
||||||
|
// decoration: BoxDecoration(
|
||||||
|
// // color: Colors.amber,
|
||||||
|
// // color: bodyColor,
|
||||||
|
// color: Color(0xFFE1F5FE),
|
||||||
|
// border: Border.all(
|
||||||
|
// color: Colors.white,
|
||||||
|
// // color: Color(0xFFF7F7FB),
|
||||||
|
// width: 3.5)),
|
||||||
|
child: buildUserTable(isDesktop),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildUserTable(bool isDesktop) {
|
||||||
|
return Container(
|
||||||
|
// margin: isDesktop
|
||||||
|
// ? EdgeInsets.all(10.0)
|
||||||
|
// : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0),
|
||||||
|
// padding: const EdgeInsets.all(10),
|
||||||
|
height:
|
||||||
|
isDesktop
|
||||||
|
? MediaQuery.of(context).size.height * 0.98
|
||||||
|
: MediaQuery.of(context).size.height,
|
||||||
|
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(10.0),
|
||||||
|
child: Container(
|
||||||
|
color: Colors.white,
|
||||||
|
padding: const EdgeInsets.all(10.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
// Divider(
|
||||||
|
// thickness: 0.2, // how "thick" the line is
|
||||||
|
// color: Colors.grey, // optional
|
||||||
|
// ),
|
||||||
|
Row(
|
||||||
|
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
child: BreadcrumbNavigation(
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
breadcrumbItems: [
|
||||||
|
BreadcrumbItem(
|
||||||
|
title: 'Org Settings',
|
||||||
|
tooltip: 'Go To Organization Settings',
|
||||||
|
onTap: (context) {
|
||||||
|
context.go("/OrganizationSettings");
|
||||||
|
},
|
||||||
|
),
|
||||||
|
BreadcrumbItem(title: 'Department Details'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Text(
|
||||||
|
// 'Department Details',
|
||||||
|
// style: GoogleFonts.poppins(
|
||||||
|
// fontSize: isDesktop ? 16 : 14,
|
||||||
|
// fontWeight: FontWeight.w600,
|
||||||
|
// color: Colors.black,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop)
|
||||||
|
SizedBox(width: MediaQuery.of(context).size.width * 0.08),
|
||||||
|
|
||||||
|
// SizedBox(width: MediaQuery.of(context).size.width * 0.16),
|
||||||
|
if (isDesktop)
|
||||||
|
Container(
|
||||||
|
width: MediaQuery.of(context).size.width * 0.2,
|
||||||
|
height: 40,
|
||||||
|
child: TextField(
|
||||||
|
controller: searchController,
|
||||||
|
|
||||||
|
onChanged: filterDepartment,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "Search ...",
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Color(0xFF9E9DBD),
|
||||||
|
),
|
||||||
|
prefixIcon: Icon(
|
||||||
|
Icons.search,
|
||||||
|
color: Color(0xFF9E9DBD),
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: Colors.grey.shade200,
|
||||||
|
width: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: Colors.grey.shade300,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
style: GoogleFonts.poppins(fontSize: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// SizedBox(width: 16),
|
||||||
|
Spacer(),
|
||||||
|
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Color(0xFF114D8B),
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
disabledBackgroundColor: Color(0xFF114D8B),
|
||||||
|
disabledForegroundColor: Colors.white,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
side: BorderSide(color: Color(0xFF114D8B), width: 2),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 20,
|
||||||
|
vertical: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(context) => DepartmentData(
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
layoutColor: layoutColor!,
|
||||||
|
fetchGetDepartment: refreshData,
|
||||||
|
|
||||||
|
// role:
|
||||||
|
// "Travel Agent"
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize:
|
||||||
|
MainAxisSize.min, // Ensures content fits nicely
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Add Department",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: isDesktop ? 13 : 11,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 8), // spacing between icon and text
|
||||||
|
Icon(
|
||||||
|
Icons.add_circle_outline_rounded,
|
||||||
|
size: 15,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
if (!isDesktop) SizedBox(height: 5),
|
||||||
|
isDesktop
|
||||||
|
? SizedBox.shrink()
|
||||||
|
: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: MediaQuery.of(context).size.width * 0.8,
|
||||||
|
height: 35,
|
||||||
|
child: TextField(
|
||||||
|
controller: searchController,
|
||||||
|
onChanged: filterDepartment,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "Search ...",
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Color(0xFF9E9DBD),
|
||||||
|
),
|
||||||
|
prefixIcon: Icon(
|
||||||
|
Icons.search,
|
||||||
|
color: Color(0xFF9E9DBD),
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: Colors.grey.shade200,
|
||||||
|
width: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: Colors.grey.shade300,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
style: GoogleFonts.poppins(fontSize: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// SizedBox(width: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
FutureBuilder<List<dynamic>>(
|
||||||
|
future: futureDepartment,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final adjHgt = MediaQuery.of(context).size.height;
|
||||||
|
if (futureDepartment == null) {
|
||||||
|
CircularProgressIndicator();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
} else if (snapshot.hasError ||
|
||||||
|
!snapshot.hasData ||
|
||||||
|
snapshot.data!.isEmpty) {
|
||||||
|
return Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
// const Icon(Icons.error_outline,
|
||||||
|
// color: Colors.redAccent, size: 60),
|
||||||
|
// const SizedBox(height: 16),
|
||||||
|
// Text(
|
||||||
|
// "Oops!",
|
||||||
|
// style: GoogleFonts.poppins(
|
||||||
|
// fontSize: 20,
|
||||||
|
// fontWeight: FontWeight.bold,
|
||||||
|
// color: Colors.redAccent),
|
||||||
|
// ),
|
||||||
|
SizedBox(height: adjHgt / 4),
|
||||||
|
Text(
|
||||||
|
"No Data Found ",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
// Text(
|
||||||
|
// "Please Create Department Details",
|
||||||
|
// textAlign: TextAlign.center,
|
||||||
|
// style: GoogleFonts.poppins(
|
||||||
|
// fontSize: 16,
|
||||||
|
// color: Colors.grey,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// const SizedBox(height: 20),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
/* Here collect the list to displayed the data in table or card Used */
|
||||||
|
List<dynamic> object =
|
||||||
|
filteredDepartment.isNotEmpty
|
||||||
|
? filteredDepartment
|
||||||
|
: allDepartment;
|
||||||
|
|
||||||
|
/* List is Sorting here */
|
||||||
|
object.sort((a, b) {
|
||||||
|
DateTime dateA = DateTime.parse(a['created_on']);
|
||||||
|
DateTime dateB = DateTime.parse(b['created_on']);
|
||||||
|
|
||||||
|
return dateB.compareTo(dateA); // Descending: newest first
|
||||||
|
});
|
||||||
|
|
||||||
|
/* For pagination for list ... */
|
||||||
|
List paginatedDepartment =
|
||||||
|
object
|
||||||
|
.skip(currentPage * itemsPerPage)
|
||||||
|
.take(itemsPerPage)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
/* Table ... */
|
||||||
|
Widget table = LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
double minWidth = isDesktop ? constraints.maxWidth : 1300;
|
||||||
|
|
||||||
|
return ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(minWidth: minWidth),
|
||||||
|
child: DataTable(
|
||||||
|
dividerThickness: 0.5,
|
||||||
|
columnSpacing: isDesktop ? 24.0 : 16.0,
|
||||||
|
border: TableBorder(
|
||||||
|
horizontalInside: BorderSide(
|
||||||
|
width: 0.5,
|
||||||
|
color: Colors.grey.shade200,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
columns: [
|
||||||
|
DataColumn(
|
||||||
|
label: Text(
|
||||||
|
'Name',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// DataColumn(
|
||||||
|
// label: Text(
|
||||||
|
// 'Description',
|
||||||
|
// style: GoogleFonts.poppins(
|
||||||
|
// fontSize: 13,
|
||||||
|
// fontWeight: FontWeight.w600,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
DataColumn(
|
||||||
|
label: Text(
|
||||||
|
'Status',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DataColumn(
|
||||||
|
label: Text(
|
||||||
|
'Actions',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
rows:
|
||||||
|
paginatedDepartment.map((tableObject) {
|
||||||
|
String departmentId =
|
||||||
|
tableObject['id'].toString(); // Get user ID
|
||||||
|
bool isSelected =
|
||||||
|
selectedDepartmentId == departmentId;
|
||||||
|
|
||||||
|
return DataRow(
|
||||||
|
cells: [
|
||||||
|
DataCell(
|
||||||
|
Text(
|
||||||
|
tableObject['dropdown_value'] ?? '',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontFamily: "Inter",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// DataCell(
|
||||||
|
// Text(
|
||||||
|
// tableObject['description'] ?? 'N/A',
|
||||||
|
// style: TextStyle(
|
||||||
|
// fontSize: 13,
|
||||||
|
// fontFamily: "Inter",
|
||||||
|
// ),
|
||||||
|
// softWrap: true,
|
||||||
|
// overflow: TextOverflow.ellipsis,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
DataCell(
|
||||||
|
Text(
|
||||||
|
tableObject['is_active'] == "1"
|
||||||
|
? 'Active'
|
||||||
|
: 'Inactive',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontFamily: "Inter",
|
||||||
|
color:
|
||||||
|
tableObject['is_active'] == "1"
|
||||||
|
? Colors.green
|
||||||
|
: Colors.grey,
|
||||||
|
),
|
||||||
|
softWrap: true,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DataCell(
|
||||||
|
// UserActionsMenu(
|
||||||
|
// user: forex,
|
||||||
|
// getUserDetails: (id) =>
|
||||||
|
// apiService.getSingleUser(id),
|
||||||
|
// ),
|
||||||
|
GestureDetector(
|
||||||
|
child: Tooltip(
|
||||||
|
message: 'Edit Department Details',
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/IconsImg/edit.png',
|
||||||
|
width: 20,
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () async {
|
||||||
|
// final userId = getUserId(user['user_id']);
|
||||||
|
// final usersData = await getUserDetails(userId);
|
||||||
|
//
|
||||||
|
final departmentId = int.tryParse(
|
||||||
|
tableObject['id'].toString(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (departmentId != null) {
|
||||||
|
print(
|
||||||
|
"Table cell - department Id -- $departmentId",
|
||||||
|
);
|
||||||
|
final data = await apiService
|
||||||
|
.getDepartmentDetailsFind(
|
||||||
|
context,
|
||||||
|
departmentId,
|
||||||
|
);
|
||||||
|
print("DepartmentId -- $data");
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(context) => DepartmentData(
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
departmentId:
|
||||||
|
departmentId, // Pass the ID
|
||||||
|
departmentData: data,
|
||||||
|
layoutColor: layoutColor!,
|
||||||
|
// fetchGetForex: fetchGetForex,
|
||||||
|
fetchGetDepartment:
|
||||||
|
refreshData,
|
||||||
|
// role:
|
||||||
|
// "Travel Agent"
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
print("Invalid ID");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/* Card ... */
|
||||||
|
Widget buildMobileCardView(List<dynamic> paginatedUser) {
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: paginatedUser.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final cardObject = paginatedUser[index];
|
||||||
|
return Card(
|
||||||
|
color: Colors.white,
|
||||||
|
margin: EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 6,
|
||||||
|
),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
elevation: 3,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Status and Employee Code
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
cardObject['dropdown_value'] ?? 'N/A',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 10,
|
||||||
|
color: Colors.black87,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
GestureDetector(
|
||||||
|
child: Tooltip(
|
||||||
|
message: 'Edit Department Details',
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/IconsImg/edit.png',
|
||||||
|
width: 20,
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () async {
|
||||||
|
// final userId = getUserId(user['user_id']);
|
||||||
|
// final usersData = await getUserDetails(userId);
|
||||||
|
//
|
||||||
|
final departmentId = int.tryParse(
|
||||||
|
cardObject['id'].toString(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (departmentId != null) {
|
||||||
|
print(
|
||||||
|
"departmentId -- $departmentId",
|
||||||
|
);
|
||||||
|
final data = await apiService
|
||||||
|
.getDepartmentDetailsFind(
|
||||||
|
context,
|
||||||
|
departmentId,
|
||||||
|
);
|
||||||
|
print("DepartmentId -- $data");
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(context) => DepartmentData(
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
departmentId:
|
||||||
|
departmentId, // Pass the ID
|
||||||
|
departmentData: data,
|
||||||
|
layoutColor: layoutColor!,
|
||||||
|
// fetchGetDepartment: fetchGetDepartment,
|
||||||
|
fetchGetDepartment:
|
||||||
|
refreshData,
|
||||||
|
// role:
|
||||||
|
// "Travel Agent"
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
print("Invalid ID");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// PopupMenuButton<int>(
|
||||||
|
// color: Colors.white,
|
||||||
|
// padding: EdgeInsets.zero,
|
||||||
|
// offset: Offset(0, 30),
|
||||||
|
// icon: Icon(
|
||||||
|
// Icons.more_vert,
|
||||||
|
// color: Color(0xFF475569),
|
||||||
|
// size: 14,
|
||||||
|
// ),
|
||||||
|
// itemBuilder: (context) => [
|
||||||
|
// CustomPopupMenuEntry(
|
||||||
|
// child: Container(
|
||||||
|
// padding: EdgeInsets.symmetric(
|
||||||
|
// horizontal: 8, vertical: 8),
|
||||||
|
// child: Row(
|
||||||
|
// mainAxisSize:
|
||||||
|
// MainAxisSize.min,
|
||||||
|
// mainAxisAlignment:
|
||||||
|
// MainAxisAlignment.center,
|
||||||
|
// children: [
|
||||||
|
// IconButton(
|
||||||
|
// icon: Icon(
|
||||||
|
// Icons
|
||||||
|
// .remove_red_eye,
|
||||||
|
// color: Color(
|
||||||
|
// 0xFF475569),
|
||||||
|
// size: 18),
|
||||||
|
// onPressed: () {
|
||||||
|
// print(
|
||||||
|
// "USerDAta - $user");
|
||||||
|
// // dynamic usersData = apiService
|
||||||
|
// // .getSingleUser(user[
|
||||||
|
// // 'user_id']
|
||||||
|
// // is String
|
||||||
|
// // ? int.parse(user[
|
||||||
|
// // 'user_id'])
|
||||||
|
// // : user[
|
||||||
|
// // 'user_id']);
|
||||||
|
// //
|
||||||
|
// // print(
|
||||||
|
// // "USerDAta - $usersData");
|
||||||
|
//
|
||||||
|
// context.go(
|
||||||
|
// "/CreateUserDetails",
|
||||||
|
// extra: {
|
||||||
|
// "selectedUser":
|
||||||
|
// user,
|
||||||
|
// "isViewMode": true
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
// }),
|
||||||
|
// IconButton(
|
||||||
|
// icon: Image.asset(
|
||||||
|
// 'assets/images/IconsImg/edit.png',
|
||||||
|
// width: 20,
|
||||||
|
// height: 15),
|
||||||
|
// onPressed: () {
|
||||||
|
// context.go(
|
||||||
|
// "/CreateUserDetails",
|
||||||
|
// extra: {
|
||||||
|
// "selectedUser":
|
||||||
|
// user,
|
||||||
|
// "isViewMode": false
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 2),
|
||||||
|
// Trip Id and Trip Name
|
||||||
|
// Name
|
||||||
|
// Row(
|
||||||
|
// children: [
|
||||||
|
// Column(
|
||||||
|
// crossAxisAlignment:
|
||||||
|
// CrossAxisAlignment.start,
|
||||||
|
// children: [
|
||||||
|
// Text(
|
||||||
|
// cardObject['description'] ?? '',
|
||||||
|
// style: GoogleFonts.poppins(
|
||||||
|
// fontSize: 10,
|
||||||
|
// color: Colors.black87,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// SizedBox(width: 10),
|
||||||
|
// Column(
|
||||||
|
// crossAxisAlignment:
|
||||||
|
// CrossAxisAlignment.start,
|
||||||
|
// children: [
|
||||||
|
// Text(
|
||||||
|
// cardObject['description'] ?? '',
|
||||||
|
// style: GoogleFonts.poppins(
|
||||||
|
// fontSize: 10,
|
||||||
|
// color: Colors.black87,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// Actions
|
||||||
|
// Actions
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Expanded(
|
||||||
|
child: Column(
|
||||||
|
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child:
|
||||||
|
isDesktop
|
||||||
|
? (searchController.text.isNotEmpty &&
|
||||||
|
filteredDepartment.isEmpty
|
||||||
|
? Center(
|
||||||
|
child: Text(
|
||||||
|
"No Matches Found",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
|
child: table,
|
||||||
|
))
|
||||||
|
: (searchController.text.isNotEmpty &&
|
||||||
|
filteredDepartment.isEmpty
|
||||||
|
? Center(
|
||||||
|
child: Text(
|
||||||
|
"No Matches Found",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: buildMobileCardView(
|
||||||
|
paginatedDepartment,
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
// Expanded(
|
||||||
|
// child: isDesktop
|
||||||
|
// ? SingleChildScrollView(
|
||||||
|
// scrollDirection: Axis.vertical,
|
||||||
|
// child: table, // <-- your existing table
|
||||||
|
// )
|
||||||
|
// : buildMobileCardView(paginatedDepartment),
|
||||||
|
// ),
|
||||||
|
PaginationControls(
|
||||||
|
currentPage: currentPage,
|
||||||
|
itemsPerPage: itemsPerPage,
|
||||||
|
totalItems: object.length,
|
||||||
|
activeColor: layoutColor, // your theme color
|
||||||
|
onPageChanged: (page) {
|
||||||
|
setState(() {
|
||||||
|
currentPage = page;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onItemsPerPageChanged: (items) {
|
||||||
|
setState(() {
|
||||||
|
itemsPerPage = items;
|
||||||
|
currentPage = 0;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
782
lib/Screens/group/group.dart
Normal file
@ -0,0 +1,782 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:core';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:dropdown_search/dropdown_search.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:frontend/Screens/organization/mailSettings.dart';
|
||||||
|
import 'package:frontend/Screens/organization/themeColor.dart';
|
||||||
|
import 'package:frontend/utils/auth_utils.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
|
||||||
|
import '../../config/apiUrl.dart';
|
||||||
|
import '../../routes/custom_appBar.dart';
|
||||||
|
import '../../routes/custom_drawer.dart';
|
||||||
|
import '../../services/apiService.dart';
|
||||||
|
import '../../widgets/custom_text_field.dart';
|
||||||
|
import '../../widgets/custom_user_form.dart';
|
||||||
|
import 'groupList.dart';
|
||||||
|
|
||||||
|
class Group extends StatefulWidget {
|
||||||
|
final Map<String, dynamic>? group;
|
||||||
|
|
||||||
|
const Group({Key? key, required this.group}) : super(key: key);
|
||||||
|
|
||||||
|
static Group fromState(GoRouterState state) {
|
||||||
|
return Group(group: state.extra as Map<String, dynamic>?);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
_groupState createState() => _groupState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _groupState extends State<Group> {
|
||||||
|
final ApiService apiService = ApiService();
|
||||||
|
|
||||||
|
Color? layoutColor;
|
||||||
|
Color? bodyColor;
|
||||||
|
|
||||||
|
String? orgId;
|
||||||
|
String? userId;
|
||||||
|
String? selectedGroupId;
|
||||||
|
|
||||||
|
String? selectedDomestic;
|
||||||
|
String? selectedInternational;
|
||||||
|
|
||||||
|
List<dynamic>? apiAllPolicy;
|
||||||
|
List<dynamic>? apiForDomestic;
|
||||||
|
List<dynamic>? apiForInternational;
|
||||||
|
Map<String, String> errorMessages = {};
|
||||||
|
|
||||||
|
final Map<String, TextEditingController> controllers = {};
|
||||||
|
Map<String, FocusNode> focusNodes = {};
|
||||||
|
Map<String, bool> focusStates = {};
|
||||||
|
|
||||||
|
List<String> dataHeader = ["name", "description"];
|
||||||
|
|
||||||
|
Map<String, dynamic> get groupData {
|
||||||
|
final data = {
|
||||||
|
"name": controllers["name"]?.text,
|
||||||
|
"description": controllers["description"]?.text,
|
||||||
|
"domestic_policy_id": selectedDomestic,
|
||||||
|
"international_policy_id": selectedInternational,
|
||||||
|
"org_id": orgId,
|
||||||
|
"created_by": userId,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only add group_id if it's an edit operation
|
||||||
|
if (widget.group != null && widget.group!.containsKey('group_id')) {
|
||||||
|
data["group_id"] = selectedGroupId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
loadAllServices();
|
||||||
|
|
||||||
|
for (var field in dataHeader) {
|
||||||
|
controllers[field] = TextEditingController();
|
||||||
|
focusNodes["${field}FocusNode"] = FocusNode();
|
||||||
|
focusStates["${field}Focused"] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Focus Nodes KeysII: ${focusNodes.keys.toList()}");
|
||||||
|
print("Focus States KeysII: ${focusStates.keys.toList()}");
|
||||||
|
print("Text Controllers KeysII: ${controllers.keys.toList()}");
|
||||||
|
|
||||||
|
updateData();
|
||||||
|
loadInitialData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadInitialData() async {
|
||||||
|
String? layoutString = await getLayoutColor();
|
||||||
|
String? bodyStringColor = await getBodyColor();
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
layoutColor =
|
||||||
|
layoutString != null
|
||||||
|
? Color(int.parse(layoutString))
|
||||||
|
: Colors.redAccent;
|
||||||
|
|
||||||
|
bodyColor =
|
||||||
|
bodyStringColor != null
|
||||||
|
? Color(int.parse(bodyStringColor))
|
||||||
|
: Colors.white;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateData() async {
|
||||||
|
// Ensure apiselectedUser is not null before printing
|
||||||
|
if (widget.group != null) {
|
||||||
|
print("API Selected User Has Data - ${widget.group}");
|
||||||
|
setState(() {
|
||||||
|
// ✅ Wrap in setState to update the UI
|
||||||
|
selectedGroupId = widget.group?["group_id"] ?? "";
|
||||||
|
controllers["name"]?.text = widget.group?["name"] ?? "";
|
||||||
|
controllers["description"]?.text = widget.group?["description"] ?? "";
|
||||||
|
|
||||||
|
if (widget.group?["domestic_policy_id"] != null) {
|
||||||
|
selectedDomestic = widget.group!["domestic_policy_id"].toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.group?["international_policy_id"] != null) {
|
||||||
|
selectedInternational =
|
||||||
|
widget.group!["international_policy_id"].toString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
print("API Selected User Has Data - No data available yet");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadAllServices() async {
|
||||||
|
try {
|
||||||
|
final result = await apiService.fetchAllPolicy(context);
|
||||||
|
|
||||||
|
orgId = await getOrgId();
|
||||||
|
userId = await getUserId();
|
||||||
|
final roleUser = await getRoleUser();
|
||||||
|
if (roleUser != null &&
|
||||||
|
(roleUser == 'Org Admin' || roleUser == 'Travel Admin')) {
|
||||||
|
setState(() {
|
||||||
|
apiAllPolicy = result;
|
||||||
|
|
||||||
|
apiForDomestic =
|
||||||
|
result.where((policy) => policy["domestic"] == "1").toList();
|
||||||
|
apiForInternational =
|
||||||
|
result.where((policy) => policy["international"] == "1").toList();
|
||||||
|
});
|
||||||
|
print("Fetched services: $apiAllPolicy");
|
||||||
|
print("Domestic policies: $apiForDomestic");
|
||||||
|
print("International policies: $apiForInternational");
|
||||||
|
} else {
|
||||||
|
apiService.logout(context);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error fetching role list: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isValidData(Map<String, dynamic> data) {
|
||||||
|
errorMessages.clear(); // Reset errors
|
||||||
|
|
||||||
|
// Required fields that must not be empty
|
||||||
|
List<String> requiredFields = ["name", "description"];
|
||||||
|
|
||||||
|
// Check validation for each field
|
||||||
|
for (String field in requiredFields) {
|
||||||
|
if (data[field] == null || data[field].toString().trim().isEmpty) {
|
||||||
|
errorMessages[field] = "Required";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ✅ At least one of the two policies must be selected
|
||||||
|
if ((selectedDomestic == null || selectedDomestic!.isEmpty) &&
|
||||||
|
(selectedInternational == null || selectedInternational!.isEmpty)) {
|
||||||
|
errorMessages["domestic_policy_id"] = "Select at least one policy";
|
||||||
|
errorMessages["international_policy_id"] = "Select at least one policy";
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorMessages.isEmpty;
|
||||||
|
|
||||||
|
return errorMessages.isEmpty; // Valid if there are no errors
|
||||||
|
}
|
||||||
|
|
||||||
|
void _clearError(String field) {
|
||||||
|
if (mounted && errorMessages.containsKey(field)) {
|
||||||
|
setState(() {
|
||||||
|
errorMessages.remove(field);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleSubmit() {
|
||||||
|
print("HandleSubmiy - $groupData");
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
if (!isValidData(groupData)) {
|
||||||
|
print("USERDETAILS : $groupData");
|
||||||
|
print("Validation Failed: Required fields are missing.");
|
||||||
|
return; // Stop execution if validation fails
|
||||||
|
} else {
|
||||||
|
print("USERDETAILS : $groupData");
|
||||||
|
postGroupData(groupData);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> postGroupData(Map<String, dynamic> groupData) async {
|
||||||
|
final token = await getToken(); // Fetch token
|
||||||
|
|
||||||
|
final bool isEdit = widget.group != null && widget.group!.isNotEmpty;
|
||||||
|
print("isEdit: $isEdit");
|
||||||
|
print("postGroupData- ${widget.group?['group_id']}");
|
||||||
|
|
||||||
|
String apiUrlData;
|
||||||
|
|
||||||
|
if (isEdit) {
|
||||||
|
if (selectedGroupId == null) {
|
||||||
|
print("selectedGroupId is null. Cannot proceed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int? groupId = int.tryParse(selectedGroupId!);
|
||||||
|
if (groupId == null) {
|
||||||
|
print("groupId is invalid. selectedGroupId = $selectedGroupId");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
apiUrlData = '$apiUrl/api/groups/update/$groupId';
|
||||||
|
} else {
|
||||||
|
apiUrlData = '$apiUrl/api/groups/create';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token == null) {
|
||||||
|
throw Exception('Token not found. Please log in.');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response =
|
||||||
|
await (isEdit
|
||||||
|
? http.put(
|
||||||
|
Uri.parse(apiUrlData),
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'app-signature': 'ts-traveltool-2025-signature-123456',
|
||||||
|
},
|
||||||
|
body: jsonEncode(groupData),
|
||||||
|
)
|
||||||
|
: http.post(
|
||||||
|
Uri.parse(apiUrlData),
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'app-signature': 'ts-traveltool-2025-signature-123456',
|
||||||
|
},
|
||||||
|
body: jsonEncode(groupData),
|
||||||
|
));
|
||||||
|
|
||||||
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||||
|
print("GRPDATA");
|
||||||
|
|
||||||
|
print("Group submitted successfully!");
|
||||||
|
print("Response: ${response.body}");
|
||||||
|
await apiService.handleTokenRefresh(context, userId!);
|
||||||
|
context.go('/group');
|
||||||
|
} else if (response.statusCode == 403) {
|
||||||
|
print("403-FORB");
|
||||||
|
await apiService.logout(context);
|
||||||
|
return null;
|
||||||
|
// throw Exception('Failed to load users');
|
||||||
|
} else {
|
||||||
|
print("Failed to submit group. Status: ${response.statusCode}");
|
||||||
|
print("Error: ${response.body}");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Error submitting group: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ResponsiveBuilder(
|
||||||
|
builder: (context, sizingInfo) {
|
||||||
|
bool isDesktop =
|
||||||
|
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
// backgroundColor: Colors.white,
|
||||||
|
backgroundColor: Color(0xFFf5f5f5),
|
||||||
|
appBar: CustomAppBar(isDesktop: isDesktop),
|
||||||
|
drawer: CustomDrawer(isDesktop: false),
|
||||||
|
body: Padding(
|
||||||
|
padding:
|
||||||
|
isDesktop
|
||||||
|
? EdgeInsets.symmetric(
|
||||||
|
horizontal:
|
||||||
|
MediaQuery.of(context).size.width *
|
||||||
|
0.1, // 30% of screen width as horizontal padding
|
||||||
|
vertical:
|
||||||
|
MediaQuery.of(context).size.height *
|
||||||
|
0, // 5% of screen height as vertical padding
|
||||||
|
)
|
||||||
|
: EdgeInsets.all(8),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// if (isDesktop) CustomDrawer(isDesktop: true),
|
||||||
|
Expanded(child: buildData(isDesktop, context)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildData(bool isDesktop, context) {
|
||||||
|
return Container(
|
||||||
|
margin: isDesktop ? const EdgeInsets.only(top: 10.0, bottom: 10.0) : null,
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isDesktop ? Colors.white : Color(0xFFFCFCFC),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
// color: bodyColor,
|
||||||
|
// color: Color(0xFFE1F5FE),
|
||||||
|
child: buildOrganizationLayout(isDesktop),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
color: Colors.white,
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child:
|
||||||
|
isDesktop
|
||||||
|
? Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: _buildSubmit(isDesktop),
|
||||||
|
)
|
||||||
|
: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: _buildSubmit(isDesktop),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildOrganizationLayout(isDesktop) {
|
||||||
|
return Container(
|
||||||
|
// margin: isDesktop
|
||||||
|
// ? EdgeInsets.all(10.0)
|
||||||
|
// : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0),
|
||||||
|
height:
|
||||||
|
isDesktop
|
||||||
|
? MediaQuery.of(context).size.height * 0.98
|
||||||
|
: MediaQuery.of(context).size.height,
|
||||||
|
// decoration: BoxDecoration(
|
||||||
|
// border: isDesktop
|
||||||
|
// ? Border.all(
|
||||||
|
// width: 2,
|
||||||
|
// color: Colors.white,
|
||||||
|
// // color: Color(0xFFF7F7FB),
|
||||||
|
// )
|
||||||
|
// : null,
|
||||||
|
// color: Colors.white,
|
||||||
|
//
|
||||||
|
// // color: Color(0xFFF7F7FB),
|
||||||
|
//
|
||||||
|
// // color: Colors.amber,
|
||||||
|
// ),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
// color: Colors.white,
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text("Create Group", style: GoogleFonts.poppins(fontSize: 18)),
|
||||||
|
// Container(
|
||||||
|
// color: Color(0xFFE9EBF6),
|
||||||
|
// child: IconButton(
|
||||||
|
// icon: Icon(
|
||||||
|
// Icons.close,
|
||||||
|
// ),
|
||||||
|
// onPressed: () {
|
||||||
|
// context.go('/group');
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// SizedBox(
|
||||||
|
// height: 5,
|
||||||
|
// ),
|
||||||
|
Container(
|
||||||
|
margin: const EdgeInsets.all(10),
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
color: Colors.white,
|
||||||
|
height: MediaQuery.of(context).size.height * 0.6,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// Row(
|
||||||
|
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
// children: [
|
||||||
|
// Text(
|
||||||
|
// "Mail Settings",
|
||||||
|
// style: TextStyle(color: Colors.blueAccent),
|
||||||
|
// ),
|
||||||
|
// Icon(
|
||||||
|
// Icons.keyboard_arrow_down_outlined,
|
||||||
|
// color: Colors.blueAccent,
|
||||||
|
// size: 30,
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
isDesktop
|
||||||
|
? Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: _buildFirstRow(isDesktop),
|
||||||
|
)
|
||||||
|
: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: _buildFirstRow(isDesktop),
|
||||||
|
),
|
||||||
|
|
||||||
|
SizedBox(height: 10),
|
||||||
|
|
||||||
|
isDesktop
|
||||||
|
? Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: _buildSecondRow(isDesktop),
|
||||||
|
)
|
||||||
|
: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: _buildSecondRow(isDesktop),
|
||||||
|
),
|
||||||
|
|
||||||
|
// SizedBox(height: 15),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildFirstRow(bool isDesktop) {
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Group Name",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: false,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
width:
|
||||||
|
isDesktop
|
||||||
|
? MediaQuery.of(context).size.width * 0.34
|
||||||
|
: MediaQuery.of(context).size.width * 0.85,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: TextField(
|
||||||
|
controller: controllers["name"],
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9 _-]')),
|
||||||
|
],
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
onChanged: (value) {
|
||||||
|
_clearError("name");
|
||||||
|
},
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "group name",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (errorMessages["name"] != null) ...[
|
||||||
|
SizedBox(height: 5), // Space before error message
|
||||||
|
Text(
|
||||||
|
errorMessages["name"]!,
|
||||||
|
style: TextStyle(color: Colors.red, fontSize: 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Description",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: false,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
width:
|
||||||
|
isDesktop
|
||||||
|
? MediaQuery.of(context).size.width * 0.34
|
||||||
|
: MediaQuery.of(context).size.width * 0.85,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: TextField(
|
||||||
|
controller: controllers["description"],
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9 _-]')),
|
||||||
|
],
|
||||||
|
onChanged: (value) {
|
||||||
|
_clearError("description");
|
||||||
|
},
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "description",
|
||||||
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (errorMessages["description"] != null) ...[
|
||||||
|
SizedBox(height: 5), // Space before error message
|
||||||
|
Text(
|
||||||
|
errorMessages["description"]!,
|
||||||
|
style: TextStyle(color: Colors.red, fontSize: 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildSecondRow(bool isDesktop) {
|
||||||
|
return [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Select Policy For Domestic",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: false,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
width:
|
||||||
|
isDesktop
|
||||||
|
? MediaQuery.of(context).size.width * 0.34
|
||||||
|
: MediaQuery.of(context).size.width * 0.85,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child:
|
||||||
|
apiForDomestic == null
|
||||||
|
? Center(
|
||||||
|
child: Transform.scale(
|
||||||
|
scale: 0.5,
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: DropdownSearch<String>(
|
||||||
|
selectedItem:
|
||||||
|
selectedDomestic == null
|
||||||
|
? null
|
||||||
|
: apiForDomestic!
|
||||||
|
.firstWhere(
|
||||||
|
(policy) =>
|
||||||
|
policy['policy_id'] ==
|
||||||
|
selectedDomestic,
|
||||||
|
)['name']
|
||||||
|
.toString(),
|
||||||
|
popupProps: PopupProps.menu(
|
||||||
|
showSearchBox: true,
|
||||||
|
fit: FlexFit.loose,
|
||||||
|
constraints: BoxConstraints(maxHeight: 250),
|
||||||
|
searchFieldProps: TextFieldProps(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "Search Policy...",
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
items:
|
||||||
|
apiForDomestic!
|
||||||
|
.map((policy) => policy['name'].toString())
|
||||||
|
.toList(),
|
||||||
|
dropdownDecoratorProps: DropDownDecoratorProps(
|
||||||
|
dropdownSearchDecoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(horizontal: 1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
dropdownBuilder:
|
||||||
|
(context, selectedItem) => Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text(
|
||||||
|
selectedItem ?? "Select",
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onChanged: (String? newValue) {
|
||||||
|
setState(() {
|
||||||
|
selectedDomestic =
|
||||||
|
apiForDomestic!.firstWhere(
|
||||||
|
(policy) => policy['name'] == newValue,
|
||||||
|
)['policy_id'];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Select Policy For International",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldWrapper(
|
||||||
|
isFocused: false,
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
width:
|
||||||
|
isDesktop
|
||||||
|
? MediaQuery.of(context).size.width * 0.34
|
||||||
|
: MediaQuery.of(context).size.width * 0.85,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child:
|
||||||
|
apiForInternational == null
|
||||||
|
? Center(
|
||||||
|
child: Transform.scale(
|
||||||
|
scale: 0.5,
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: DropdownSearch<String>(
|
||||||
|
selectedItem:
|
||||||
|
selectedInternational == null
|
||||||
|
? null
|
||||||
|
: apiForInternational!
|
||||||
|
.firstWhere(
|
||||||
|
(policy) =>
|
||||||
|
policy['policy_id'] ==
|
||||||
|
selectedInternational,
|
||||||
|
)['name']
|
||||||
|
.toString(),
|
||||||
|
popupProps: PopupProps.menu(
|
||||||
|
showSearchBox: true,
|
||||||
|
fit: FlexFit.loose,
|
||||||
|
constraints: BoxConstraints(maxHeight: 250),
|
||||||
|
searchFieldProps: TextFieldProps(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "Search Policy...",
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
items:
|
||||||
|
apiForInternational!
|
||||||
|
.map((policy) => policy['name'].toString())
|
||||||
|
.toList(),
|
||||||
|
dropdownDecoratorProps: DropDownDecoratorProps(
|
||||||
|
dropdownSearchDecoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(horizontal: 1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
dropdownBuilder:
|
||||||
|
(context, selectedItem) => Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text(
|
||||||
|
selectedItem ?? "Select",
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onChanged: (String? newValue) {
|
||||||
|
setState(() {
|
||||||
|
selectedInternational =
|
||||||
|
apiForInternational!.firstWhere(
|
||||||
|
(policy) => policy['name'] == newValue,
|
||||||
|
)['policy_id'];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildSubmit(isDesktop) {
|
||||||
|
return [
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
foregroundColor: layoutColor,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
side: BorderSide(color: layoutColor ?? Colors.green, width: 2),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
context.go('/group');
|
||||||
|
},
|
||||||
|
child: Text("Cancel"),
|
||||||
|
),
|
||||||
|
SizedBox(width: 20),
|
||||||
|
MouseRegion(
|
||||||
|
// cursor: widget.isViewMode
|
||||||
|
// ? SystemMouseCursors.forbidden
|
||||||
|
// : SystemMouseCursors.click,
|
||||||
|
child: ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: layoutColor, // Keep original color
|
||||||
|
foregroundColor: Colors.white, // Keep original color
|
||||||
|
// disabledBackgroundColor:
|
||||||
|
// Colors.blueAccent, // Ensure color remains when disabled
|
||||||
|
// disabledForegroundColor: Colors.white,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
// side: BorderSide(color: Colors.blueAccent, width: 2),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
),
|
||||||
|
onPressed: handleSubmit, // Disable when in view mode
|
||||||
|
child: Text("Submit"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
917
lib/Screens/group/groupDetails.dart
Normal file
@ -0,0 +1,917 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:dropdown_search/dropdown_search.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
import '../../config/apiUrl.dart';
|
||||||
|
import '../../services/apiService.dart';
|
||||||
|
import '../../utils/auth_utils.dart';
|
||||||
|
import '../../widgets/custom_text_forex.dart';
|
||||||
|
import 'groupList.dart';
|
||||||
|
|
||||||
|
class GroupData extends StatefulWidget {
|
||||||
|
final Future<void> Function() fetchGetGroup;
|
||||||
|
final bool isDesktop;
|
||||||
|
final Color? layoutColor;
|
||||||
|
|
||||||
|
final int? groupId; // <-- Add this
|
||||||
|
final Map<String, dynamic>? groupData;
|
||||||
|
|
||||||
|
const GroupData({
|
||||||
|
super.key,
|
||||||
|
required this.isDesktop,
|
||||||
|
this.layoutColor,
|
||||||
|
required this.fetchGetGroup,
|
||||||
|
this.groupId,
|
||||||
|
this.groupData,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
GroupDataState createState() => GroupDataState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class GroupDataState extends State<GroupData> {
|
||||||
|
final ApiService apiService = ApiService();
|
||||||
|
|
||||||
|
Map<String, String> DomesticMap = {};
|
||||||
|
Map<String, String> InternationalMap = {};
|
||||||
|
|
||||||
|
late List<dynamic>? apiForDomestic;
|
||||||
|
late List<dynamic>? apiForInternational;
|
||||||
|
|
||||||
|
Map<String, dynamic>? apiData;
|
||||||
|
final Map<String, TextEditingController> controllers = {};
|
||||||
|
Map<String, FocusNode> focusNodes = {};
|
||||||
|
Map<String, bool> focusStates = {};
|
||||||
|
|
||||||
|
Map<String, String> errorMessages = {};
|
||||||
|
|
||||||
|
List<dynamic> domesticList = [];
|
||||||
|
List<dynamic> internationalList = [];
|
||||||
|
|
||||||
|
String? selectedName;
|
||||||
|
String? selectedDescription;
|
||||||
|
|
||||||
|
String? selectedInternationalPolicyID;
|
||||||
|
String? selectedInternationalPolicyName;
|
||||||
|
|
||||||
|
String? selectedDomesticPolicyID;
|
||||||
|
String? selectedDomesticPolicyName;
|
||||||
|
|
||||||
|
String? userId;
|
||||||
|
int? groupDataId;
|
||||||
|
|
||||||
|
late String isActive = "1";
|
||||||
|
bool isDisable = false;
|
||||||
|
|
||||||
|
List<String> dataHeader = [
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"domestic_policy_id",
|
||||||
|
"domestic_policy_name",
|
||||||
|
"international_policy_id",
|
||||||
|
"international_policy_name",
|
||||||
|
];
|
||||||
|
|
||||||
|
Map<String, dynamic> group_Detials() {
|
||||||
|
final data = {
|
||||||
|
"name": controllers["name"]?.text,
|
||||||
|
"description": controllers["description"]?.text,
|
||||||
|
"domestic_policy_id": selectedDomesticPolicyID,
|
||||||
|
"international_policy_id": selectedInternationalPolicyID,
|
||||||
|
"domestic_policy_name": selectedDomesticPolicyName,
|
||||||
|
"international_policy_name": selectedInternationalPolicyName,
|
||||||
|
"is_active": isActive,
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
loadAllServices();
|
||||||
|
});
|
||||||
|
|
||||||
|
apiForDomestic = null;
|
||||||
|
apiForInternational = null;
|
||||||
|
|
||||||
|
apiData = null;
|
||||||
|
// for (var field in dataHeader) {
|
||||||
|
// controllers[field] = TextEditingController();
|
||||||
|
// }
|
||||||
|
|
||||||
|
for (var field in dataHeader) {
|
||||||
|
controllers[field] = TextEditingController();
|
||||||
|
focusNodes["${field}FocusNode"] = FocusNode();
|
||||||
|
focusStates["${field}Focused"] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Focus Nodes KeysII: ${focusNodes.keys.toList()}");
|
||||||
|
print("Focus States KeysII: ${focusStates.keys.toList()}");
|
||||||
|
print("Text Controllers KeysII: ${controllers.keys.toList()}");
|
||||||
|
|
||||||
|
for (var key in focusNodes.keys) {
|
||||||
|
_addFocusListener(focusNodes[key]!, (focus) {
|
||||||
|
setState(() {
|
||||||
|
focusStates[key.replaceFirst("FocusNode", "Focused")] = focus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.groupId != null) {
|
||||||
|
print('Editing group ID: ${widget.groupId}');
|
||||||
|
updateGroupDetails();
|
||||||
|
}
|
||||||
|
_clearError();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _clearError() {
|
||||||
|
setState(() {
|
||||||
|
errorMessages.clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
for (var node in focusNodes.values) {
|
||||||
|
node.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var controller in controllers.values) {
|
||||||
|
controller.dispose();
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addFocusListener(FocusNode node, Function(bool) updateState) {
|
||||||
|
node.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
updateState(node.hasFocus);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateGroupDetails() {
|
||||||
|
print("Update - ${widget.groupData}");
|
||||||
|
|
||||||
|
final data = widget.groupData;
|
||||||
|
|
||||||
|
if (data == null) return;
|
||||||
|
setState(() {
|
||||||
|
selectedDomesticPolicyID = data['domestic_policy_id']; // For dropdown
|
||||||
|
selectedDomesticPolicyName = "";
|
||||||
|
selectedInternationalPolicyID = data['international_policy_id'];
|
||||||
|
selectedInternationalPolicyName = ""; // Optional if used elsewhere
|
||||||
|
controllers['name']?.text = data['name'] ?? '';
|
||||||
|
controllers['description']?.text = data['description'] ?? '';
|
||||||
|
isActive = data["is_active"];
|
||||||
|
final groupId = int.tryParse(data['group_id'].toString());
|
||||||
|
groupDataId = groupId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadAllServices() async {
|
||||||
|
try {
|
||||||
|
final result = await apiService.fetchAllPolicy(context);
|
||||||
|
|
||||||
|
userId = await getUserId();
|
||||||
|
final roleUser = await getRoleUser();
|
||||||
|
if (roleUser != null &&
|
||||||
|
(roleUser == 'Org Admin' || roleUser == 'Travel Admin')) {
|
||||||
|
setState(() {
|
||||||
|
apiForDomestic =
|
||||||
|
result.where((policy) => policy["domestic"] == "1").toList();
|
||||||
|
apiForInternational =
|
||||||
|
result.where((policy) => policy["international"] == "1").toList();
|
||||||
|
});
|
||||||
|
print("Domestic policies: $apiForDomestic");
|
||||||
|
print("International policies: $apiForInternational");
|
||||||
|
} else {
|
||||||
|
apiService.logout(context);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error fetching role list: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggleStatus() {
|
||||||
|
setState(() {
|
||||||
|
isActive = isActive == "1" ? "0" : "1";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validateData() {
|
||||||
|
errorMessages.clear();
|
||||||
|
|
||||||
|
final data = {
|
||||||
|
"name": controllers["name"]?.text,
|
||||||
|
"description": controllers["description"]?.text,
|
||||||
|
"domestic_policy_id": selectedDomesticPolicyID,
|
||||||
|
"international_policy_id": selectedInternationalPolicyID,
|
||||||
|
"domestic_policy_name": selectedDomesticPolicyName,
|
||||||
|
"international_policy_name": selectedInternationalPolicyName,
|
||||||
|
};
|
||||||
|
|
||||||
|
final requiredFields = ["name", "description"];
|
||||||
|
|
||||||
|
// Check validation for each field
|
||||||
|
for (String field in requiredFields) {
|
||||||
|
if (data[field] == null || data[field].toString().trim().isEmpty) {
|
||||||
|
errorMessages[field] = "Required";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorMessages.isEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> handleSubmit() async {
|
||||||
|
userId = await getUserId();
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
isDisable = true;
|
||||||
|
// This triggers UI rebuild with error messages
|
||||||
|
if (validateData()) {
|
||||||
|
postGroupData();
|
||||||
|
} else {
|
||||||
|
isDisable = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final groupData1 = group_Detials();
|
||||||
|
print("GroupDAta - $groupData1");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> postGroupData({int isActive = 1}) async {
|
||||||
|
final orgId = await getOrgId();
|
||||||
|
final groupData = group_Detials();
|
||||||
|
final String apiUrlData;
|
||||||
|
|
||||||
|
// static here
|
||||||
|
groupData["org_id"] = orgId;
|
||||||
|
|
||||||
|
// dynamic here
|
||||||
|
if (groupDataId != null) {
|
||||||
|
apiUrlData = '$apiUrl/api/groups/update/$groupDataId';
|
||||||
|
groupData["group_id"] = groupDataId.toString();
|
||||||
|
groupData["updated_by"] = userId;
|
||||||
|
} else {
|
||||||
|
apiUrlData = '$apiUrl/api/groups/create';
|
||||||
|
groupData["created_by"] = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
final token = await getToken(); // Fetch token
|
||||||
|
|
||||||
|
if (token == null) {
|
||||||
|
throw Exception('Token not found. Please log in.');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final uri = Uri.parse(apiUrlData);
|
||||||
|
final headers = {
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'app-signature': 'ts-traveltool-2025-signature-123456',
|
||||||
|
};
|
||||||
|
final body = jsonEncode(groupData);
|
||||||
|
|
||||||
|
final response =
|
||||||
|
groupDataId != null
|
||||||
|
? await http.put(uri, headers: headers, body: body)
|
||||||
|
: await http.post(uri, headers: headers, body: body);
|
||||||
|
|
||||||
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||||
|
print("Group Details Created successfully!");
|
||||||
|
print("Response: ${response.body}");
|
||||||
|
// _clearError();
|
||||||
|
_clearError();
|
||||||
|
await apiService.handleTokenRefresh(context, userId!);
|
||||||
|
await widget.fetchGetGroup();
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.of(context).pop(); // Close modal only if mounted
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
isDisable = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// dispose();
|
||||||
|
} else if (response.statusCode == 404) {
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
isDisable = false;
|
||||||
|
});
|
||||||
|
final message = jsonDecode(response.body)['message'] ?? 'Unknown error';
|
||||||
|
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(message),
|
||||||
|
backgroundColor: Colors.redAccent,
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (response.statusCode == 403) {
|
||||||
|
print("403-FORB");
|
||||||
|
await apiService.logout(context);
|
||||||
|
return null;
|
||||||
|
// throw Exception('Failed to load users');
|
||||||
|
} else {
|
||||||
|
print("Failed to submit plan. Status: ${response.statusCode}");
|
||||||
|
print("Error: ${response.body}");
|
||||||
|
final message = jsonDecode(response.body);
|
||||||
|
final errorMessage =
|
||||||
|
message['messages']?['error'] ?? 'Unknown error occurred';
|
||||||
|
|
||||||
|
if (errorMessage.contains("Duplicate entry")) {
|
||||||
|
_clearError();
|
||||||
|
await widget.fetchGetGroup();
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text("Group Already Exists!"),
|
||||||
|
backgroundColor: Colors.orange,
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
isDisable = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print(" Error submitting plan: $e");
|
||||||
|
setState(() {
|
||||||
|
isDisable = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
late Map<String, String> DomesticMap;
|
||||||
|
late List<String> DomesticID;
|
||||||
|
late Map<String, String> InternationalMap;
|
||||||
|
late List<String> InternationalID;
|
||||||
|
|
||||||
|
domesticList = apiForDomestic ?? [];
|
||||||
|
internationalList = apiForInternational ?? [];
|
||||||
|
|
||||||
|
// print("domesticListFromAPI--$domesticList");
|
||||||
|
|
||||||
|
// Map id to names
|
||||||
|
DomesticMap = {
|
||||||
|
for (var object in domesticList)
|
||||||
|
object['policy_id'] as String: object['name'] as String,
|
||||||
|
};
|
||||||
|
|
||||||
|
// print("domestic -- map--$DomesticMap");
|
||||||
|
|
||||||
|
// Extract only id for processing
|
||||||
|
DomesticID = DomesticMap.keys.toList();
|
||||||
|
|
||||||
|
// print("domestic -- id--$DomesticID");
|
||||||
|
|
||||||
|
// print("domestic -- selected --$selectedDomesticPolicyID");
|
||||||
|
|
||||||
|
InternationalMap = {
|
||||||
|
for (var item in internationalList)
|
||||||
|
item['policy_id'] as String: item['name'] as String,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract only id for processing
|
||||||
|
InternationalID = InternationalMap.keys.toList();
|
||||||
|
|
||||||
|
selectedInternationalPolicyID ??= null;
|
||||||
|
|
||||||
|
return AlertDialog(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
// contentPadding: const EdgeInsets.fromLTRB(44, 40, 44, 40),
|
||||||
|
contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30),
|
||||||
|
// contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 10),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
// Row 1: Title + Edit + Delete buttons
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
(groupDataId != null) ? 'Edit Group' : 'Create Group',
|
||||||
|
style: GoogleFonts.poppins(fontSize: 15, color: Colors.black),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Divider(thickness: 0.2, color: Colors.blueGrey.shade100),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Name *",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldForexWrapper(
|
||||||
|
// isFocused: false,
|
||||||
|
isFocused: focusStates["nameFocused"] ?? false,
|
||||||
|
isDesktop: widget.isDesktop,
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: focusNodes["nameFocusNode"],
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(
|
||||||
|
RegExp(r'[a-zA-Z0-9 _-]'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
controller: controllers["name"],
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Name",
|
||||||
|
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (errorMessages["name"] != null) ...[
|
||||||
|
SizedBox(height: 5), // Space before error message
|
||||||
|
Text(
|
||||||
|
errorMessages["name"]!,
|
||||||
|
style: TextStyle(color: Colors.red, fontSize: 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Text(
|
||||||
|
// "Select Policy For Domestic",
|
||||||
|
// style: GoogleFonts.poppins(
|
||||||
|
// fontSize: 12,
|
||||||
|
// fontWeight: FontWeight.w600,
|
||||||
|
// color: Color(0xFF575A74),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Select Policy For Domestic",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.remove_circle_sharp,
|
||||||
|
size: 12,
|
||||||
|
color: Colors.redAccent,
|
||||||
|
),
|
||||||
|
tooltip: "Reset",
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
selectedDomesticPolicyID = null;
|
||||||
|
selectedDomesticPolicyName = null;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldForexWrapper(
|
||||||
|
isFocused: focusStates["domestic_policy_nameFocused"] ?? false,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 0),
|
||||||
|
isDesktop: widget.isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: Focus(
|
||||||
|
focusNode: focusNodes["domestic_policy_nameFocusNode"],
|
||||||
|
onFocusChange: (hasFocus) {
|
||||||
|
setState(() {
|
||||||
|
focusStates["domestic_policy_nameFocused"] = hasFocus;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: GestureDetector(
|
||||||
|
//
|
||||||
|
onTap: () {
|
||||||
|
// Request focus when user taps
|
||||||
|
focusNodes["domestic_policy_nameFocusNode"]
|
||||||
|
?.requestFocus();
|
||||||
|
},
|
||||||
|
child: DropdownSearch<String>(
|
||||||
|
selectedItem: DomesticMap[selectedDomesticPolicyID],
|
||||||
|
popupProps: PopupProps.menu(
|
||||||
|
showSearchBox: true, // Enables search functionality
|
||||||
|
menuProps: const MenuProps(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
constraints: BoxConstraints(maxHeight: 250),
|
||||||
|
itemBuilder:
|
||||||
|
(context, object, isSelected) => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8.0,
|
||||||
|
vertical: 6.0,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
object,
|
||||||
|
style: GoogleFonts.poppins(fontSize: 11.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
searchFieldProps: TextFieldProps(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "Search ...",
|
||||||
|
hintStyle: GoogleFonts.poppins(fontSize: 11),
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
items: DomesticMap.values.toList(),
|
||||||
|
dropdownDecoratorProps: DropDownDecoratorProps(
|
||||||
|
dropdownSearchDecoration: InputDecoration(
|
||||||
|
// border: InputBorder.none,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color:
|
||||||
|
(focusStates["domestic_policy_nameFocused"] ??
|
||||||
|
false)
|
||||||
|
? widget.layoutColor!
|
||||||
|
: Colors.white,
|
||||||
|
// width: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color:
|
||||||
|
(focusStates["domestic_policy_nameFocused"] ??
|
||||||
|
false)
|
||||||
|
? widget.layoutColor!
|
||||||
|
: Colors.white,
|
||||||
|
// : const Color(0xFFD6D5E6),
|
||||||
|
// width: 0.5,
|
||||||
|
// const Color(0xFFD6D5E6),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: widget.layoutColor!,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10.0,
|
||||||
|
vertical: 8.0,
|
||||||
|
),
|
||||||
|
// contentPadding: EdgeInsets.symmetric(horizontal: 1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
dropdownBuilder:
|
||||||
|
(context, selectedItem) => Align(
|
||||||
|
// Center-align selected item
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text(
|
||||||
|
selectedItem ?? "Select ",
|
||||||
|
style: GoogleFonts.poppins(fontSize: 11),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onChanged: (String? newValue) {
|
||||||
|
setState(() {
|
||||||
|
// Find the country_code based on selected country_name
|
||||||
|
selectedDomesticPolicyID =
|
||||||
|
DomesticMap.entries
|
||||||
|
.firstWhere(
|
||||||
|
(entry) => entry.value == newValue,
|
||||||
|
)
|
||||||
|
.key;
|
||||||
|
selectedDomesticPolicyName = newValue;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Select Policy For International",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.remove_circle_sharp,
|
||||||
|
size: 12,
|
||||||
|
color: Colors.redAccent,
|
||||||
|
),
|
||||||
|
tooltip: "Reset",
|
||||||
|
onPressed: () {
|
||||||
|
setState(() {
|
||||||
|
selectedInternationalPolicyID = null;
|
||||||
|
selectedInternationalPolicyName = null;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldForexWrapper(
|
||||||
|
isFocused:
|
||||||
|
focusStates["international_policy_nameFocused"] ?? false,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 0),
|
||||||
|
isDesktop: widget.isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: Focus(
|
||||||
|
focusNode: focusNodes["international_policy_nameFocusNode"],
|
||||||
|
onFocusChange: (hasFocus) {
|
||||||
|
setState(() {
|
||||||
|
focusStates["international_policy_nameFocused"] =
|
||||||
|
hasFocus;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
// Request focus when user taps
|
||||||
|
focusNodes["international_policy_nameFocusNode"]
|
||||||
|
?.requestFocus();
|
||||||
|
},
|
||||||
|
child: DropdownSearch<String>(
|
||||||
|
selectedItem:
|
||||||
|
InternationalMap[selectedInternationalPolicyID],
|
||||||
|
popupProps: PopupProps.menu(
|
||||||
|
showSearchBox: true, // Enables search functionality
|
||||||
|
menuProps: const MenuProps(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
constraints: BoxConstraints(maxHeight: 250),
|
||||||
|
itemBuilder:
|
||||||
|
(context, item, isSelected) => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8.0,
|
||||||
|
vertical: 6.0,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
item,
|
||||||
|
style: GoogleFonts.poppins(fontSize: 11.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
searchFieldProps: TextFieldProps(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "Search ...",
|
||||||
|
hintStyle: GoogleFonts.poppins(fontSize: 11),
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
items: InternationalMap.values.toList(),
|
||||||
|
dropdownDecoratorProps: DropDownDecoratorProps(
|
||||||
|
dropdownSearchDecoration: InputDecoration(
|
||||||
|
// border: InputBorder.none,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color:
|
||||||
|
(focusStates["international_policy_nameFocused"] ??
|
||||||
|
false)
|
||||||
|
? widget.layoutColor!
|
||||||
|
: Colors.white,
|
||||||
|
width: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color:
|
||||||
|
(focusStates["international_policy_nameFocused"] ??
|
||||||
|
false)
|
||||||
|
? widget.layoutColor!
|
||||||
|
: Colors.white,
|
||||||
|
// : const Color(0xFFD6D5E6),
|
||||||
|
width: 0.5,
|
||||||
|
// const Color(0xFFD6D5E6),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: widget.layoutColor!,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10.0,
|
||||||
|
vertical: 8.0,
|
||||||
|
),
|
||||||
|
// contentPadding: EdgeInsets.symmetric(horizontal: 1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
dropdownBuilder:
|
||||||
|
(context, selectedItem) => Align(
|
||||||
|
// Center-align selected item
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text(
|
||||||
|
selectedItem ?? "Select",
|
||||||
|
style: GoogleFonts.poppins(fontSize: 11),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onChanged: (String? newValue) {
|
||||||
|
setState(() {
|
||||||
|
// Find the country_code based on selected country_name
|
||||||
|
selectedInternationalPolicyID =
|
||||||
|
InternationalMap.entries
|
||||||
|
.firstWhere(
|
||||||
|
(entry) => entry.value == newValue,
|
||||||
|
)
|
||||||
|
.key;
|
||||||
|
selectedInternationalPolicyName = newValue;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Description *",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldForexWrapper(
|
||||||
|
isFocused: focusStates["descriptionFocused"] ?? false,
|
||||||
|
|
||||||
|
isDesktop: widget.isDesktop,
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 100,
|
||||||
|
child: TextField(
|
||||||
|
controller: controllers["description"],
|
||||||
|
focusNode: focusNodes["descriptionFocusNode"],
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(
|
||||||
|
RegExp(r'[a-zA-Z0-9 _-]'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
maxLines: null,
|
||||||
|
expands: true,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Description",
|
||||||
|
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (errorMessages["description"] != null) ...[
|
||||||
|
SizedBox(height: 5), // Space before error message
|
||||||
|
Text(
|
||||||
|
errorMessages["description"]!,
|
||||||
|
style: TextStyle(color: Colors.red, fontSize: 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 15),
|
||||||
|
if (groupDataId != null)
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Change Status ",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Tooltip(
|
||||||
|
message:
|
||||||
|
isActive == "1" ? "Tap to deactivate" : "Tap to activate",
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: toggleStatus,
|
||||||
|
child: Text(
|
||||||
|
isActive == "1" ? "Active" : "Inactive",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontFamily: "Inter",
|
||||||
|
color: isActive == "1" ? Colors.green : Colors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (groupDataId != null) SizedBox(height: 15),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
// SizedBox(
|
||||||
|
// child: ElevatedButton(
|
||||||
|
// onPressed: () {
|
||||||
|
// // You can get text from commentController.text
|
||||||
|
// Navigator.of(context).pop(); // Close the modal
|
||||||
|
// },
|
||||||
|
// style: ElevatedButton.styleFrom(
|
||||||
|
// backgroundColor: widget.layoutColor,
|
||||||
|
// shape: RoundedRectangleBorder(
|
||||||
|
// borderRadius: BorderRadius.circular(8),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// child: Text('Cancel',
|
||||||
|
// style: GoogleFonts.poppins(
|
||||||
|
// fontSize: 13, color: Colors.white)),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// SizedBox(
|
||||||
|
// width: 10,
|
||||||
|
// ),
|
||||||
|
SizedBox(
|
||||||
|
child: ElevatedButton(
|
||||||
|
// onPressed: () {
|
||||||
|
// handleSubmit();
|
||||||
|
// // You can get text from commentController.text
|
||||||
|
// // Navigator.of(context).pop(); // Close the modal
|
||||||
|
// },
|
||||||
|
onPressed:
|
||||||
|
isDisable
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
setState(() {
|
||||||
|
isDisable = true;
|
||||||
|
});
|
||||||
|
await handleSubmit();
|
||||||
|
// You can get text from commentController.text
|
||||||
|
// Navigator.of(context).pop(); // Close the modal
|
||||||
|
|
||||||
|
// Optional: re-enable only on error
|
||||||
|
// setState(() {
|
||||||
|
// isDisable = false;
|
||||||
|
// });
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: widget.layoutColor,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Save',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 11,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
// : SizedBox.shrink(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
1089
lib/Screens/group/groupList.dart
Normal file
472
lib/Screens/group/groupListBackUp.dart
Normal file
@ -0,0 +1,472 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:frontend/Screens/group/group.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
|
||||||
|
import '../../config/apiUrl.dart';
|
||||||
|
import '../../routes/custom_appBar.dart';
|
||||||
|
import '../../routes/custom_drawer.dart';
|
||||||
|
import '../../services/apiService.dart';
|
||||||
|
import '../../utils/auth_utils.dart';
|
||||||
|
import 'groupDetails.dart';
|
||||||
|
|
||||||
|
class GroupListBackUp extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_GroupListBackUpState createState() => _GroupListBackUpState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GroupListBackUpState extends State<GroupListBackUp> {
|
||||||
|
final ApiService apiService = ApiService();
|
||||||
|
|
||||||
|
List<dynamic>? apiAllGroups;
|
||||||
|
Color? layoutColor;
|
||||||
|
Color? bodyColor;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
loadAllGroups();
|
||||||
|
loadInitialData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadInitialData() async {
|
||||||
|
String? layoutString = await getLayoutColor();
|
||||||
|
String? bodyStringColor = await getBodyColor();
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
layoutColor =
|
||||||
|
layoutString != null
|
||||||
|
? Color(int.parse(layoutString))
|
||||||
|
: Colors.redAccent;
|
||||||
|
|
||||||
|
bodyColor =
|
||||||
|
bodyStringColor != null
|
||||||
|
? Color(int.parse(bodyStringColor))
|
||||||
|
: Colors.white;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadAllGroups() async {
|
||||||
|
try {
|
||||||
|
final result = await apiService.fetchAllGroup(context);
|
||||||
|
setState(() {
|
||||||
|
apiAllGroups = result;
|
||||||
|
});
|
||||||
|
print("Fetched services: $apiAllGroups");
|
||||||
|
} catch (e) {
|
||||||
|
print('Error fetching role list: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleActiveStatus(
|
||||||
|
Map<String, dynamic> groupData,
|
||||||
|
String groupId,
|
||||||
|
String currentStatus,
|
||||||
|
) async {
|
||||||
|
print("Toggling user status - $groupId (Current: $currentStatus)");
|
||||||
|
|
||||||
|
final String apiUrlData =
|
||||||
|
'$apiUrl/api/groups/update/$groupId'; // API for updating user
|
||||||
|
final String? token = await getToken();
|
||||||
|
|
||||||
|
if (token == null) {
|
||||||
|
print("Error: Token not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle status: If active ("1"), set to inactive ("0"); otherwise, activate ("1")
|
||||||
|
String newStatus = (currentStatus == "1") ? "0" : "1";
|
||||||
|
|
||||||
|
print("STatus 1 - $newStatus");
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await http.put(
|
||||||
|
Uri.parse(apiUrlData),
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'app-signature': 'ts-traveltool-2025-signature-123456',
|
||||||
|
},
|
||||||
|
body: jsonEncode({
|
||||||
|
"is_active": newStatus, // Set new status dynamically
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||||
|
print("User status updated successfully to $newStatus!");
|
||||||
|
loadAllGroups(); // Refresh users list after update
|
||||||
|
} else {
|
||||||
|
print("Failed to update user status. Status: ${response.statusCode}");
|
||||||
|
print("Error: ${response.body}");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Error updating user status: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void deleteGroup(Map<String, dynamic> groupdata, groupId, status) {
|
||||||
|
print("GroupId : $groupId");
|
||||||
|
print("Groupstatus: $status");
|
||||||
|
print("GroupsData: $groupdata");
|
||||||
|
|
||||||
|
// handleActiveStatus(groupdata, groupId, status);
|
||||||
|
print("Calling handleActiveStatus with: id=$groupId, status=$status");
|
||||||
|
handleActiveStatus(groupdata, groupId.toString(), status.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> refreshData() async {
|
||||||
|
loadAllGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Future<void> deleteGroupFromApi(int groupId) async {
|
||||||
|
// try {
|
||||||
|
// await apiService.deleteGroup(groupId); // your delete API call
|
||||||
|
// deleteGroup(groupId); // remove from UI list
|
||||||
|
// } catch (e) {
|
||||||
|
// print('Error deleting group: $e');
|
||||||
|
// }
|
||||||
|
// }'
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ResponsiveBuilder(
|
||||||
|
builder: (context, sizingInfo) {
|
||||||
|
bool isDesktop =
|
||||||
|
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
// backgroundColor: Colors.white,
|
||||||
|
backgroundColor: Color(0xFFf5f5f5),
|
||||||
|
appBar: CustomAppBar(isDesktop: isDesktop),
|
||||||
|
drawer: CustomDrawer(isDesktop: false),
|
||||||
|
body: Padding(
|
||||||
|
padding:
|
||||||
|
isDesktop
|
||||||
|
? EdgeInsets.symmetric(
|
||||||
|
horizontal:
|
||||||
|
MediaQuery.of(context).size.width *
|
||||||
|
0.1, // 30% of screen width as horizontal padding
|
||||||
|
vertical:
|
||||||
|
MediaQuery.of(context).size.height *
|
||||||
|
0, // 5% of screen height as vertical padding
|
||||||
|
)
|
||||||
|
: EdgeInsets.all(8),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// if (isDesktop) CustomDrawer(isDesktop: true),
|
||||||
|
Expanded(child: buildGroupListLayout(isDesktop)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildGroupListLayout(bool isDesktop) {
|
||||||
|
return Container(
|
||||||
|
margin: isDesktop ? const EdgeInsets.only(top: 10.0, bottom: 10.0) : null,
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isDesktop ? Colors.white : Color(0xFFFCFCFC),
|
||||||
|
),
|
||||||
|
// decoration: BoxDecoration(
|
||||||
|
// // color: Colors.amber,
|
||||||
|
// // color: bodyColor,
|
||||||
|
// color: Color(0xFFE1F5FE),
|
||||||
|
// border: Border.all(
|
||||||
|
// color: Colors.white,
|
||||||
|
// // color: Color(0xFFF7F7FB),
|
||||||
|
// width: 3.5)),
|
||||||
|
child: buildGroupData(isDesktop),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Widget buildGroupListView(bool isDesktop) {
|
||||||
|
// return Container(
|
||||||
|
// child: Text("DAta"),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
Widget buildGroupData(isDesktop) {
|
||||||
|
return Container(
|
||||||
|
// margin: isDesktop
|
||||||
|
// ? EdgeInsets.all(10.0)
|
||||||
|
// : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0),
|
||||||
|
// padding: const EdgeInsets.all(10),
|
||||||
|
height:
|
||||||
|
isDesktop
|
||||||
|
? MediaQuery.of(context).size.height * 0.98
|
||||||
|
: MediaQuery.of(context).size.height,
|
||||||
|
// decoration: BoxDecoration(
|
||||||
|
// border: isDesktop
|
||||||
|
// ? Border.all(
|
||||||
|
// width: 2,
|
||||||
|
// color: Colors.white,
|
||||||
|
// // color: Color(0xFFF7F7FB),
|
||||||
|
// )
|
||||||
|
// : null,
|
||||||
|
// color: Colors.white,
|
||||||
|
// // color: Color(0xFFF7F7FB),
|
||||||
|
//
|
||||||
|
// // color: Colors.amber,
|
||||||
|
// ),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Group List',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: isDesktop ? 16 : 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.keyboard_arrow_down),
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
backgroundColor: Color(0xFF114D8B),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
// side: BorderSide(color: , width: 1),
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
// List<dynamic> users = await futureUsers;
|
||||||
|
// context.go('/CreateGroup');
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(context) => GroupData(
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
groupId: null,
|
||||||
|
layoutColor: layoutColor!,
|
||||||
|
fetchGetGroup: refreshData,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text('New Group', style: GoogleFonts.poppins(fontSize: 12)),
|
||||||
|
SizedBox(width: 5),
|
||||||
|
Icon(Icons.add_circle_outline_rounded, color: Colors.white),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
height: MediaQuery.of(context).size.height * 0.75,
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
// margin: const EdgeInsets.only(bottom: 10),
|
||||||
|
color: Colors.white,
|
||||||
|
// color: Colors.red.shade100,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
|
child: Column(children: [buildGroupListView(isDesktop)]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildGroupListView(bool isDesktop) {
|
||||||
|
if (apiAllGroups == null || apiAllGroups!.isEmpty) {
|
||||||
|
return Center(child: Text("No groups found."));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: apiAllGroups!.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final group = apiAllGroups![index];
|
||||||
|
return Card(
|
||||||
|
// color: bodyColor,
|
||||||
|
// color: Color(0xFFF5F5F5),
|
||||||
|
color: Colors.white,
|
||||||
|
margin: EdgeInsets.symmetric(vertical: 6, horizontal: 10),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"Group Name",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 11.5,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"Domestic Policy",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 11.5,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"International Policy",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 11.5,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"Description",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 11.5,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 4),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"${group['name']}",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"${group['domestic_policy_name'] ?? 'N/A'}",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"${group['international_policy_name']}",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
"${group['description'] ?? 'N/A'}",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
if (group['group_id'] != null) {
|
||||||
|
final newGroupID = int.tryParse(
|
||||||
|
group['group_id'].toString(),
|
||||||
|
);
|
||||||
|
if (newGroupID != null) {
|
||||||
|
final data = await apiService.getGroupDetailsFind(
|
||||||
|
context,
|
||||||
|
newGroupID,
|
||||||
|
); // ✅ Always an int
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(context) => GroupData(
|
||||||
|
isDesktop: isDesktop,
|
||||||
|
groupId: newGroupID,
|
||||||
|
// Pass the ID
|
||||||
|
groupData: data,
|
||||||
|
layoutColor: layoutColor!,
|
||||||
|
fetchGetGroup: refreshData,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("something went wrong check properly");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// onTap: () {
|
||||||
|
// context.go("/CreateGroup", extra: group);
|
||||||
|
// },
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/IconsImg/edit.png',
|
||||||
|
width: 20,
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 5),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
final idStr = group['group_id'];
|
||||||
|
final id = int.tryParse(idStr.toString());
|
||||||
|
|
||||||
|
if (id == null) {
|
||||||
|
print("group_id is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final status = group['is_active'];
|
||||||
|
// print("GroupId : ${group['group_id']} ");
|
||||||
|
deleteGroup(group, id, status);
|
||||||
|
},
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/IconsImg/delete.png',
|
||||||
|
width: 20,
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
801
lib/Screens/hotels/hotelsDetails.dart
Normal file
@ -0,0 +1,801 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:dropdown_search/dropdown_search.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
|
import '../../config/apiUrl.dart';
|
||||||
|
import '../../services/apiService.dart';
|
||||||
|
import '../../utils/auth_utils.dart';
|
||||||
|
import '../../widgets/custom_text_forex.dart';
|
||||||
|
import 'hotels_list.dart';
|
||||||
|
|
||||||
|
class HotelsData extends StatefulWidget {
|
||||||
|
final Future<List<dynamic>> Function() fetchGetHotels;
|
||||||
|
final bool isDesktop;
|
||||||
|
final Color? layoutColor;
|
||||||
|
|
||||||
|
final int? hotelsId; // <-- Add this
|
||||||
|
final Map<String, dynamic>? hotelsData;
|
||||||
|
|
||||||
|
const HotelsData({
|
||||||
|
super.key,
|
||||||
|
required this.isDesktop,
|
||||||
|
this.layoutColor,
|
||||||
|
required this.fetchGetHotels,
|
||||||
|
this.hotelsId,
|
||||||
|
this.hotelsData,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
HotelsDataState createState() => HotelsDataState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class HotelsDataState extends State<HotelsData> {
|
||||||
|
final ApiService apiService = ApiService();
|
||||||
|
Map<String, String> countryMap = {};
|
||||||
|
late List<dynamic>? apiCountryData;
|
||||||
|
late List<dynamic>? apiAirlineCountryData;
|
||||||
|
Map<String, dynamic>? apiData;
|
||||||
|
final Map<String, TextEditingController> controllers = {};
|
||||||
|
Map<String, String> errorMessages = {};
|
||||||
|
Map<String, FocusNode> focusNodes = {};
|
||||||
|
Map<String, bool> focusStates = {};
|
||||||
|
|
||||||
|
List<dynamic> countryList = [];
|
||||||
|
String? selectedCountry;
|
||||||
|
String? selectedCountryName;
|
||||||
|
String? selectedCity;
|
||||||
|
String? selectedDuration;
|
||||||
|
String? selectedPerdiemAmount;
|
||||||
|
String? userId;
|
||||||
|
int? hotelsDataId;
|
||||||
|
late String isActive = "1";
|
||||||
|
bool isDisable = false;
|
||||||
|
|
||||||
|
List<String> dataHeader = [
|
||||||
|
"hotel_name",
|
||||||
|
"hotel_chain",
|
||||||
|
"category",
|
||||||
|
"country_code",
|
||||||
|
"city",
|
||||||
|
];
|
||||||
|
|
||||||
|
Map<String, dynamic> hotels_Details() {
|
||||||
|
final data = {
|
||||||
|
"hotel_name": controllers["hotel_name"]?.text,
|
||||||
|
"hotel_chain": controllers["hotel_chain"]?.text,
|
||||||
|
"category": controllers["category"]?.text,
|
||||||
|
"country_code": selectedCountry,
|
||||||
|
"country_name": selectedCountryName,
|
||||||
|
"city": controllers["city"]?.text,
|
||||||
|
"created_by": userId,
|
||||||
|
"is_active": isActive,
|
||||||
|
};
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
apiCountryData = null;
|
||||||
|
apiData = null;
|
||||||
|
for (var field in dataHeader) {
|
||||||
|
controllers[field] = TextEditingController();
|
||||||
|
focusNodes["${field}FocusNode"] = FocusNode();
|
||||||
|
focusStates["${field}Focused"] = false;
|
||||||
|
}
|
||||||
|
for (var key in focusNodes.keys) {
|
||||||
|
_addFocusListener(focusNodes[key]!, (focus) {
|
||||||
|
setState(() {
|
||||||
|
focusStates[key.replaceFirst("FocusNode", "Focused")] = focus;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
fetchCountries();
|
||||||
|
if (widget.hotelsId != null) {
|
||||||
|
print('Editing Hotles ID: ${widget.hotelsId}');
|
||||||
|
updateHotelsDetails();
|
||||||
|
}
|
||||||
|
_clearError();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _clearError() {
|
||||||
|
setState(() {
|
||||||
|
errorMessages.clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
for (var controller in controllers.values) {
|
||||||
|
controller.dispose();
|
||||||
|
}
|
||||||
|
for (var node in focusNodes.values) {
|
||||||
|
node.dispose();
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addFocusListener(FocusNode node, Function(bool) updateState) {
|
||||||
|
node.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
updateState(node.hasFocus);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateHotelsDetails() {
|
||||||
|
print("Update - ${widget.hotelsData}");
|
||||||
|
|
||||||
|
final data = widget.hotelsData;
|
||||||
|
|
||||||
|
if (data == null) return;
|
||||||
|
setState(() {
|
||||||
|
selectedCountry = data['country_code']; // For dropdown
|
||||||
|
selectedCountryName =
|
||||||
|
data['country_name']; // For dropdown label or display
|
||||||
|
controllers['city']?.text = data['city'] ?? '';
|
||||||
|
controllers['hotel_chain']?.text = data['hotel_chain'] ?? '';
|
||||||
|
controllers['category']?.text = data['category'] ?? '';
|
||||||
|
controllers['hotel_name']?.text = data['hotel_name'] ?? '';
|
||||||
|
isActive = data["is_active"];
|
||||||
|
final hotelsId = int.tryParse(data['hotel_id'].toString());
|
||||||
|
hotelsDataId = hotelsId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> fetchCountries() async {
|
||||||
|
try {
|
||||||
|
List<dynamic> countries = await apiService.fetchCountryList(context);
|
||||||
|
setState(() {
|
||||||
|
apiCountryData = countries;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
print('Error fetching country list: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggleStatus() {
|
||||||
|
setState(() {
|
||||||
|
isActive = isActive == "1" ? "0" : "1";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validateData() {
|
||||||
|
errorMessages.clear();
|
||||||
|
|
||||||
|
final data = {
|
||||||
|
"hotel_name": controllers["hotel_name"]?.text,
|
||||||
|
"hotel_chain": controllers["hotel_chain"]?.text,
|
||||||
|
"category": controllers["category"]?.text,
|
||||||
|
"country_code": selectedCountry,
|
||||||
|
"country": selectedCountryName,
|
||||||
|
"city": controllers["city"]?.text,
|
||||||
|
};
|
||||||
|
|
||||||
|
final requiredFields = [
|
||||||
|
"hotel_name",
|
||||||
|
"hotel_chain",
|
||||||
|
"country_code",
|
||||||
|
"city",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Check validation for each field
|
||||||
|
for (String field in requiredFields) {
|
||||||
|
if (data[field] == null || data[field].toString().trim().isEmpty) {
|
||||||
|
errorMessages[field] = "Required";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errorMessages.isEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> handleSubmit() async {
|
||||||
|
userId = await getUserId();
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
isDisable = true;
|
||||||
|
// This triggers UI rebuild with error messages
|
||||||
|
if (validateData()) {
|
||||||
|
postHotelsData();
|
||||||
|
} else {
|
||||||
|
isDisable = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final hotelsData1 = hotels_Details();
|
||||||
|
print("submit data - $hotelsData1");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> postHotelsData({int isActive = 1}) async {
|
||||||
|
final hotelsData = hotels_Details();
|
||||||
|
|
||||||
|
final String apiUrldata;
|
||||||
|
if (hotelsDataId != null) {
|
||||||
|
print("for edit hotel id - $hotelsDataId");
|
||||||
|
|
||||||
|
apiUrldata = '$apiUrl/api/updateHotels/$hotelsDataId';
|
||||||
|
hotelsData["hotel_id"] = hotelsDataId.toString();
|
||||||
|
hotelsData["updated_by"] = userId;
|
||||||
|
(hotelsData.containsKey("created_by"))
|
||||||
|
? hotelsData.remove("created_by")
|
||||||
|
: '';
|
||||||
|
(hotelsData.containsKey("country_name"))
|
||||||
|
? hotelsData.remove("country_name")
|
||||||
|
: '';
|
||||||
|
} else {
|
||||||
|
print("for add Hotel id - null");
|
||||||
|
apiUrldata = '$apiUrl/api/createHotels';
|
||||||
|
print("called apiUrl - $apiUrldata");
|
||||||
|
hotelsData["created_by"] = userId;
|
||||||
|
(hotelsData.containsKey("country_name"))
|
||||||
|
? hotelsData.remove("country_name")
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
final token = await getToken(); // Fetch token
|
||||||
|
|
||||||
|
if (token == null) {
|
||||||
|
throw Exception('Token not found. Please log in.');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final uri = Uri.parse(apiUrldata);
|
||||||
|
final headers = {
|
||||||
|
'Authorization': 'Bearer $token',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'app-signature': 'ts-traveltool-2025-signature-123456',
|
||||||
|
};
|
||||||
|
final body = jsonEncode(hotelsData);
|
||||||
|
|
||||||
|
final response =
|
||||||
|
hotelsDataId != null
|
||||||
|
? await http.put(uri, headers: headers, body: body)
|
||||||
|
: await http.post(uri, headers: headers, body: body);
|
||||||
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||||
|
print("Hotels Details Created successfully!");
|
||||||
|
print("Response: ${response.body}");
|
||||||
|
_clearError();
|
||||||
|
await widget.fetchGetHotels();
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.of(context).pop(); // Close modal only if mounted
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
isDisable = false;
|
||||||
|
});
|
||||||
|
// Do NOT re-enable here if success
|
||||||
|
} else if (response.statusCode == 403) {
|
||||||
|
print("403-FORB");
|
||||||
|
await apiService.logout(context);
|
||||||
|
return null;
|
||||||
|
// throw Exception('Failed to load users');
|
||||||
|
} else if (response.statusCode == 404) {
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
final message = jsonDecode(response.body)['message'] ?? 'Unknown error';
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(message),
|
||||||
|
backgroundColor: Colors.redAccent,
|
||||||
|
behavior: SnackBarBehavior.floating,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
setState(() {
|
||||||
|
isDisable = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
print("Failed to submit plan. Status: ${response.statusCode}");
|
||||||
|
print("Error: ${response.body}");
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
isDisable = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print("Error submitting plan: $e");
|
||||||
|
setState(() {
|
||||||
|
isDisable = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
late Map<String, String> countryMap; // Mapping country_code -> country_name
|
||||||
|
late List<String> countryCodes; // List of country codes
|
||||||
|
|
||||||
|
// countryList = [];
|
||||||
|
countryList = apiCountryData ?? [];
|
||||||
|
|
||||||
|
// Map country codes to country names
|
||||||
|
// countryMap = {
|
||||||
|
// for (var item in countryList)
|
||||||
|
// item['country_code'] as String: item['country_name'] as String,
|
||||||
|
// };
|
||||||
|
|
||||||
|
countryMap = {
|
||||||
|
for (var country in countryList)
|
||||||
|
(country['country_code'] ?? ''):
|
||||||
|
'${country['country_name'] ?? ''} (${country['country_code'] ?? ''})',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract only country codes for processing
|
||||||
|
countryCodes = countryMap.keys.toList();
|
||||||
|
|
||||||
|
selectedCountry ??= null;
|
||||||
|
|
||||||
|
return AlertDialog(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30),
|
||||||
|
// contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 10),
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
// Row 1: Title + Edit + Delete buttons
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
(hotelsDataId != null) ? 'Edit Hotels' : 'Create Hotels',
|
||||||
|
style: GoogleFonts.poppins(fontSize: 15, color: Colors.black),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Divider(thickness: 0.2, color: Colors.blueGrey.shade100),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Hotel Name *",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldForexWrapper(
|
||||||
|
// isFocused: false,
|
||||||
|
isFocused: focusStates["hotel_nameFocused"] ?? false,
|
||||||
|
isDesktop: widget.isDesktop,
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: focusNodes["hotel_nameFocusNode"],
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(
|
||||||
|
RegExp(r'[a-zA-Z0-9 _-]'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
controller: controllers["hotel_name"],
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Hotel Name",
|
||||||
|
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (errorMessages["hotel_name"] != null) ...[
|
||||||
|
SizedBox(height: 5), // Space before error message
|
||||||
|
Text(
|
||||||
|
errorMessages["hotel_name"]!,
|
||||||
|
style: TextStyle(color: Colors.red, fontSize: 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Hotel Chain *",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldForexWrapper(
|
||||||
|
isFocused: focusStates["hotel_chainFocused"] ?? false,
|
||||||
|
isDesktop: widget.isDesktop,
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: focusNodes["hotel_chainFocusNode"],
|
||||||
|
controller: controllers["hotel_chain"],
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(
|
||||||
|
RegExp(r'[a-zA-Z0-9 _-]'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Hotel Chain",
|
||||||
|
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (errorMessages["hotel_chain"] != null) ...[
|
||||||
|
SizedBox(height: 5), // Space before error message
|
||||||
|
Text(
|
||||||
|
errorMessages["hotel_chain"]!,
|
||||||
|
style: TextStyle(color: Colors.red, fontSize: 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Category",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldForexWrapper(
|
||||||
|
// isFocused: false,
|
||||||
|
isFocused: focusStates["categoryFocused"] ?? false,
|
||||||
|
isDesktop: widget.isDesktop,
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: focusNodes["categoryFocusNode"],
|
||||||
|
controller: controllers["category"],
|
||||||
|
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(
|
||||||
|
RegExp(r'[a-z A-Z0-9 ]'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "Category",
|
||||||
|
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Country *",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldForexWrapper(
|
||||||
|
isFocused: false,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 0),
|
||||||
|
isDesktop: widget.isDesktop,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
width: double.infinity,
|
||||||
|
child: Focus(
|
||||||
|
focusNode: focusNodes["country_codeFocusNode"],
|
||||||
|
onFocusChange: (hasFocus) {
|
||||||
|
setState(() {
|
||||||
|
focusStates["country_codeFocused"] = hasFocus;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
// Request focus when user taps
|
||||||
|
focusNodes["country_codeFocusNode"]?.requestFocus();
|
||||||
|
},
|
||||||
|
child: DropdownSearch<String>(
|
||||||
|
selectedItem: countryMap[selectedCountry],
|
||||||
|
popupProps: PopupProps.menu(
|
||||||
|
showSearchBox: true, // Enables search functionality
|
||||||
|
menuProps: const MenuProps(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
constraints: BoxConstraints(maxHeight: 200),
|
||||||
|
itemBuilder: (context, item, isSelected) {
|
||||||
|
print("contryItem - $item");
|
||||||
|
final match = RegExp(
|
||||||
|
r'^(.*)\s\((.*)\)$',
|
||||||
|
).firstMatch(item);
|
||||||
|
final countryName = match?.group(1) ?? '';
|
||||||
|
final countryCode = match?.group(2) ?? '';
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8.0,
|
||||||
|
vertical: 0.02,
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
countryName,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 11.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
countryCode,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 11.5,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
searchFieldProps: TextFieldProps(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "Search ...",
|
||||||
|
hintStyle: GoogleFonts.poppins(fontSize: 11),
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 1,
|
||||||
|
vertical: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
items: countryMap.values.toList(),
|
||||||
|
dropdownDecoratorProps: DropDownDecoratorProps(
|
||||||
|
dropdownSearchDecoration: InputDecoration(
|
||||||
|
// border: InputBorder.none,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color:
|
||||||
|
(focusStates["country_codeFocused"] ??
|
||||||
|
false)
|
||||||
|
? widget.layoutColor!
|
||||||
|
: Colors.white,
|
||||||
|
// width: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color:
|
||||||
|
(focusStates["country_codeFocused"] ??
|
||||||
|
false)
|
||||||
|
? widget.layoutColor!
|
||||||
|
: Colors.white,
|
||||||
|
// : const Color(0xFFD6D5E6),
|
||||||
|
// width: 0.5,
|
||||||
|
// const Color(0xFFD6D5E6),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: widget.layoutColor!,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10.0,
|
||||||
|
vertical: 8.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
dropdownBuilder:
|
||||||
|
(context, selectedItem) => Align(
|
||||||
|
// Center-align selected item
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text(
|
||||||
|
selectedItem ?? "Select ",
|
||||||
|
style: GoogleFonts.poppins(fontSize: 11),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onChanged: (String? newValue) {
|
||||||
|
setState(() {
|
||||||
|
// Find the country_code based on selected country_name
|
||||||
|
selectedCountry =
|
||||||
|
countryMap.entries
|
||||||
|
.firstWhere(
|
||||||
|
(entry) => entry.value == newValue,
|
||||||
|
)
|
||||||
|
.key;
|
||||||
|
final match = RegExp(
|
||||||
|
r'^(.*)\s\((.*)\)$',
|
||||||
|
).firstMatch(newValue!);
|
||||||
|
final countryName =
|
||||||
|
match?.group(1) ??
|
||||||
|
newValue; // --> "Ascension Islands"
|
||||||
|
// final countryCode = match?.group(2) ?? "";
|
||||||
|
selectedCountryName = countryName;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (errorMessages["country_code"] != null) ...[
|
||||||
|
SizedBox(height: 5), // Space before error message
|
||||||
|
Text(
|
||||||
|
errorMessages["country_code"]!,
|
||||||
|
style: TextStyle(color: Colors.red, fontSize: 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"City *",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
CustomTextFieldForexWrapper(
|
||||||
|
isFocused: focusStates["cityFocused"] ?? false,
|
||||||
|
isDesktop: widget.isDesktop,
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
|
child: TextField(
|
||||||
|
focusNode: focusNodes["cityFocusNode"],
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(
|
||||||
|
RegExp(r'[a-zA-Z0-9 _-]'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
controller: controllers["city"],
|
||||||
|
style: const TextStyle(fontSize: 12),
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: "City",
|
||||||
|
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
|
||||||
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
|
border: InputBorder.none,
|
||||||
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (errorMessages["city"] != null) ...[
|
||||||
|
SizedBox(height: 5), // Space before error message
|
||||||
|
Text(
|
||||||
|
errorMessages["city"]!,
|
||||||
|
style: TextStyle(color: Colors.red, fontSize: 12),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 10),
|
||||||
|
// if (isDesktop)SizedBox(width: 8,) else SizedBox(height: 8,),
|
||||||
|
if (hotelsDataId != null)
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Change Status ",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Tooltip(
|
||||||
|
message:
|
||||||
|
isActive == "1" ? "Tap to deactivate" : "Tap to activate",
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: toggleStatus,
|
||||||
|
child: Text(
|
||||||
|
isActive == "1" ? "Active" : "Inactive",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontFamily: "Inter",
|
||||||
|
color: isActive == "1" ? Colors.green : Colors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (hotelsDataId != null) SizedBox(height: 15),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
// SizedBox(
|
||||||
|
// child: ElevatedButton(
|
||||||
|
// onPressed: () {
|
||||||
|
// // You can get text from commentController.text
|
||||||
|
// Navigator.of(context).pop(); // Close the modal
|
||||||
|
// },
|
||||||
|
// style: ElevatedButton.styleFrom(
|
||||||
|
// backgroundColor: widget.layoutColor,
|
||||||
|
// shape: RoundedRectangleBorder(
|
||||||
|
// borderRadius: BorderRadius.circular(8),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// child: Text('Cancel',
|
||||||
|
// style: GoogleFonts.poppins(
|
||||||
|
// fontSize: 13, color: Colors.white)),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// SizedBox(
|
||||||
|
// width: 10,
|
||||||
|
// ),
|
||||||
|
SizedBox(
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed:
|
||||||
|
isDisable
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
setState(() {
|
||||||
|
isDisable = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
await handleSubmit();
|
||||||
|
// You can get text from commentController.text
|
||||||
|
// Navigator.of(context).pop(); // Close the modal
|
||||||
|
|
||||||
|
// Optional: re-enable only on error
|
||||||
|
// setState(() {
|
||||||
|
// isDisable = false;
|
||||||
|
// });
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: widget.layoutColor,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Save',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 11,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
// : SizedBox.shrink(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
1053
lib/Screens/hotels/hotels_list.dart
Normal file
@ -1,4 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:responsive_builder/responsive_builder.dart';
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
|
||||||
@ -6,15 +8,19 @@ import '../../widgets/custom_text_field.dart';
|
|||||||
import '../../widgets/custom_text_itnerary_sub.dart';
|
import '../../widgets/custom_text_itnerary_sub.dart';
|
||||||
|
|
||||||
class BusScreen extends StatefulWidget {
|
class BusScreen extends StatefulWidget {
|
||||||
|
|
||||||
final Map<String, dynamic>? apiData;
|
final Map<String, dynamic>? apiData;
|
||||||
final Function(bool) onClose;
|
final Function(String, bool) onClose;
|
||||||
final Function(Map<String, dynamic>)onSaveBus;
|
final Function(Map<String, dynamic>) onSaveBus;
|
||||||
final Map<String, dynamic>? selectedItem;
|
final Map<String, dynamic>? selectedItem;
|
||||||
final String? loginUser;
|
final String? loginUser;
|
||||||
|
|
||||||
BusScreen({
|
BusScreen({
|
||||||
required this.onClose, this.apiData, required this.onSaveBus, required this.selectedItem, required this.loginUser});
|
required this.onClose,
|
||||||
|
this.apiData,
|
||||||
|
required this.onSaveBus,
|
||||||
|
required this.selectedItem,
|
||||||
|
required this.loginUser,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_BusScreenState createState() => _BusScreenState();
|
_BusScreenState createState() => _BusScreenState();
|
||||||
@ -52,9 +58,8 @@ class _BusScreenState extends State<BusScreen> {
|
|||||||
bool _timeFocus = false;
|
bool _timeFocus = false;
|
||||||
bool _commentsFocus = false;
|
bool _commentsFocus = false;
|
||||||
|
|
||||||
|
Map<String, dynamic> get busData {
|
||||||
Map<String, dynamic> get busData{
|
Map<String, dynamic> data = {
|
||||||
Map<String,dynamic> data = {
|
|
||||||
"from": _fromController.text,
|
"from": _fromController.text,
|
||||||
"to": _toController.text,
|
"to": _toController.text,
|
||||||
"date": _dateController.text,
|
"date": _dateController.text,
|
||||||
@ -64,16 +69,16 @@ class _BusScreenState extends State<BusScreen> {
|
|||||||
"updated_by": widget.loginUser,
|
"updated_by": widget.loginUser,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
if (widget.selectedItem != null) {
|
if (widget.selectedItem != null) {
|
||||||
if (widget.selectedItem?["indx"] != null && widget.selectedItem?["indx"] != 0) {
|
if (widget.selectedItem?["indx"] != null &&
|
||||||
|
widget.selectedItem?["indx"] != 0) {
|
||||||
data["indx"] = widget.selectedItem!["indx"];
|
data["indx"] = widget.selectedItem!["indx"];
|
||||||
} else if (widget.selectedItem?["bus_id"] != null && widget.selectedItem?["bus_id"] != 0) {
|
} else if (widget.selectedItem?["bus_id"] != null &&
|
||||||
|
widget.selectedItem?["bus_id"] != 0) {
|
||||||
data["bus_id"] = widget.selectedItem!["bus_id"];
|
data["bus_id"] = widget.selectedItem!["bus_id"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +114,7 @@ class _BusScreenState extends State<BusScreen> {
|
|||||||
});
|
});
|
||||||
_dateFocusNode.addListener(() {
|
_dateFocusNode.addListener(() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_dateFocus = _fromFocusNode.hasFocus;
|
_dateFocus = _dateFocusNode.hasFocus;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -125,8 +130,6 @@ class _BusScreenState extends State<BusScreen> {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
_buscommentsController = initController("comments");
|
_buscommentsController = initController("comments");
|
||||||
_fromController = initController("from");
|
_fromController = initController("from");
|
||||||
_toController = initController("to");
|
_toController = initController("to");
|
||||||
@ -137,12 +140,14 @@ class _BusScreenState extends State<BusScreen> {
|
|||||||
_toController.addListener(() => _clearError("to"));
|
_toController.addListener(() => _clearError("to"));
|
||||||
_dateController.addListener(() => _clearError("date"));
|
_dateController.addListener(() => _clearError("date"));
|
||||||
_timeController.addListener(() => _clearError("time"));
|
_timeController.addListener(() => _clearError("time"));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_tripTypeFocusNode.dispose();
|
_tripTypeFocusNode.dispose();
|
||||||
|
_dateFocusNode.dispose();
|
||||||
|
_timeFocusNode.dispose();
|
||||||
|
|
||||||
_tripTypeController.dispose();
|
_tripTypeController.dispose();
|
||||||
_hotelNameController.dispose();
|
_hotelNameController.dispose();
|
||||||
_fromController.dispose();
|
_fromController.dispose();
|
||||||
@ -153,7 +158,6 @@ class _BusScreenState extends State<BusScreen> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void _clearError(String field) {
|
void _clearError(String field) {
|
||||||
if (mounted && errorMessages.containsKey(field)) {
|
if (mounted && errorMessages.containsKey(field)) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -161,11 +165,12 @@ class _BusScreenState extends State<BusScreen> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isValidData(Map<String, dynamic> data) {
|
bool isValidData(Map<String, dynamic> data) {
|
||||||
errorMessages.clear(); // Reset errors
|
errorMessages.clear(); // Reset errors
|
||||||
|
|
||||||
// Required fields that must not be empty
|
// Required fields that must not be empty
|
||||||
List<String> requiredFields = ["from", "to","date","time"];
|
List<String> requiredFields = ["from", "to", "date", "time"];
|
||||||
|
|
||||||
// Check validation for each field
|
// Check validation for each field
|
||||||
for (String field in requiredFields) {
|
for (String field in requiredFields) {
|
||||||
@ -177,74 +182,75 @@ class _BusScreenState extends State<BusScreen> {
|
|||||||
return errorMessages.isEmpty; // Valid if there are no errors
|
return errorMessages.isEmpty; // Valid if there are no errors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void handleSave() {
|
||||||
|
print("Handle Save accomadationData $busData");
|
||||||
|
|
||||||
void handleSave(){
|
Map<String, dynamic> data = busData;
|
||||||
|
|
||||||
print( "Handle Save accomadationData $busData");
|
|
||||||
|
|
||||||
Map<String,dynamic> data = busData;
|
|
||||||
|
|
||||||
if (!isValidData(data)) {
|
if (!isValidData(data)) {
|
||||||
print("Validation Failed: Required fields are missing.");
|
print("Validation Failed: Required fields are missing.");
|
||||||
setState(() {});
|
setState(() {});
|
||||||
return; // Stop execution if validation fails
|
return; // Stop execution if validation fails
|
||||||
}else {
|
} else {
|
||||||
widget.onSaveBus(busData);
|
widget.onSaveBus(busData);
|
||||||
}
|
}
|
||||||
|
widget.onClose("Bus", false);
|
||||||
widget.onClose(false);// Close screen after saving
|
// widget.onClose(false); // Close screen after saving
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ResponsiveBuilder(builder: (context, sizingInfo) {
|
return ResponsiveBuilder(
|
||||||
|
builder: (context, sizingInfo) {
|
||||||
bool isMobile = sizingInfo.isMobile;
|
bool isMobile = sizingInfo.isMobile;
|
||||||
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
bool isDesktop =
|
||||||
|
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
color: Color(0xFFF4F4FB),
|
// color: Colors.white,
|
||||||
child: Form(
|
child: Form(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Align(
|
// Align(
|
||||||
alignment: Alignment.centerRight,
|
// alignment: Alignment.centerRight,
|
||||||
child: InkWell(
|
// child: InkWell(
|
||||||
onTap: () {
|
// onTap: () {
|
||||||
widget.onClose(false);
|
// widget.onClose(false);
|
||||||
},
|
// },
|
||||||
child: Icon(
|
// child: Icon(
|
||||||
Icons.close,
|
// Icons.close,
|
||||||
size: 18,
|
// size: 18,
|
||||||
color: Color(0xFF575A74),
|
// color: Color(0xFF575A74),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
Text("Bus Booking List",
|
// Text("Bus Booking List",
|
||||||
style:
|
// style: TextStyle(
|
||||||
TextStyle(fontSize: 18, fontWeight: FontWeight.bold,color: Color(0xFF575A74))),
|
// fontSize: 18,
|
||||||
SizedBox(
|
// fontWeight: FontWeight.bold,
|
||||||
height: 6,
|
// color: Color(0xFF575A74))),
|
||||||
),
|
// SizedBox(
|
||||||
|
// height: 6,
|
||||||
|
// ),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(28.0),
|
padding: const EdgeInsets.only(top: 30.0),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(children: _buildAccomadtionForm(isDesktop)),
|
child: Column(children: _buildAccomadtionForm(isDesktop)),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildAccomadtionForm (bool isDesktop) {
|
List<Widget> _buildAccomadtionForm(bool isDesktop) {
|
||||||
List<Widget> buildResponsiveRow(List<Widget> children) {
|
List<Widget> buildResponsiveRow(List<Widget> children) {
|
||||||
return [
|
return [
|
||||||
isDesktop ? Row(children: children) : Column(children: children),
|
isDesktop ? Row(children: children) : Column(children: children),
|
||||||
@ -254,11 +260,10 @@ class _BusScreenState extends State<BusScreen> {
|
|||||||
|
|
||||||
List<List<Widget>> rowBuilders = [
|
List<List<Widget>> rowBuilders = [
|
||||||
// _builClassType(isDesktop),
|
// _builClassType(isDesktop),
|
||||||
_buildSecondRow(isDesktop)
|
_buildSecondRow(isDesktop),
|
||||||
];
|
];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
// ...buildResponsiveRow(_buildFirstRow(isDesktop)),
|
// ...buildResponsiveRow(_buildFirstRow(isDesktop)),
|
||||||
|
|
||||||
// Iterate over rowBuilders and wrap each in a responsive container
|
// Iterate over rowBuilders and wrap each in a responsive container
|
||||||
@ -267,73 +272,60 @@ class _BusScreenState extends State<BusScreen> {
|
|||||||
...buildResponsiveRow(_buildThirdRow(isDesktop)),
|
...buildResponsiveRow(_buildThirdRow(isDesktop)),
|
||||||
|
|
||||||
// Actions row remains a Row
|
// Actions row remains a Row
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: _handleAction(isDesktop),
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
List<Widget> _buildFirstRow(isDesktop) {
|
List<Widget> _buildFirstRow(isDesktop) {
|
||||||
|
|
||||||
return [
|
return [
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Trip Type",
|
"Trip Type",
|
||||||
style: TextStyle(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w500,
|
||||||
color: Color(0xFF575A74)),
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
|
isDesktop
|
||||||
isDesktop ? Row(children: _buildTripType(isDesktop)
|
? Row(children: _buildTripType(isDesktop))
|
||||||
) :
|
: Column(children: _buildTripType(isDesktop)),
|
||||||
Column(
|
|
||||||
children: _buildTripType(isDesktop)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (isDesktop)
|
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||||||
Spacer()
|
|
||||||
else
|
|
||||||
SizedBox(
|
|
||||||
height: 8,
|
|
||||||
),
|
|
||||||
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildTripType(bool isDesktop) {
|
||||||
|
|
||||||
List<Widget> _buildTripType(bool isDesktop){
|
|
||||||
|
|
||||||
List<dynamic> purposeList = widget.apiData?['flight_trip_type'] ?? [];
|
List<dynamic> purposeList = widget.apiData?['flight_trip_type'] ?? [];
|
||||||
|
|
||||||
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
List<DropdownMenuItem<String>> dropdownItems =
|
||||||
.map((item)=>DropdownMenuItem<String>(
|
purposeList
|
||||||
|
.map(
|
||||||
|
(item) => DropdownMenuItem<String>(
|
||||||
value: item['dropdown_key'],
|
value: item['dropdown_key'],
|
||||||
child: Text(item['dropdown_value']),
|
child: Text(item['dropdown_value']),
|
||||||
)).toList();
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
if (dropdownItems.isEmpty) {
|
if (dropdownItems.isEmpty) {
|
||||||
dropdownItems.add(
|
dropdownItems.add(
|
||||||
DropdownMenuItem<String>(
|
DropdownMenuItem<String>(
|
||||||
value: null,
|
value: null,
|
||||||
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
child: Text(
|
||||||
|
"No options available",
|
||||||
|
style: TextStyle(color: Colors.grey),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default selected value
|
// Default selected value
|
||||||
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
String? selectedPurpose =
|
||||||
|
dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
CustomTextFieldWrapper(
|
CustomTextFieldWrapper(
|
||||||
@ -341,37 +333,37 @@ class _BusScreenState extends State<BusScreen> {
|
|||||||
isDesktop: isDesktop,
|
isDesktop: isDesktop,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 40,
|
height: 40,
|
||||||
|
|
||||||
child: DropdownButtonFormField<String>(
|
child: DropdownButtonFormField<String>(
|
||||||
focusNode: _tripTypeFocusNode, // Assign the correct focus node
|
focusNode: _tripTypeFocusNode, // Assign the correct focus node
|
||||||
value: selectedPurpose,
|
value: selectedPurpose,
|
||||||
style: TextStyle(fontSize: 12),
|
// style: TextStyle(fontSize: 12),
|
||||||
|
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
contentPadding: EdgeInsets.symmetric(
|
contentPadding: EdgeInsets.symmetric(
|
||||||
horizontal: 10), // Proper padding
|
horizontal: 10,
|
||||||
|
), // Proper padding
|
||||||
),
|
),
|
||||||
onChanged: purposeList.isNotEmpty
|
onChanged:
|
||||||
|
purposeList.isNotEmpty
|
||||||
? (newValue) {
|
? (newValue) {
|
||||||
setState(() {
|
setState(() {
|
||||||
selectedPurpose = newValue;
|
selectedPurpose = newValue;
|
||||||
});
|
});
|
||||||
print("Updating form data: Flight -> trip_type -> ${newValue ?? ""}");
|
print(
|
||||||
} : null,
|
"Updating form data: Flight -> trip_type -> ${newValue ?? ""}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
|
||||||
items: dropdownItems,
|
items: dropdownItems,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
List<Widget> _buildSecondRow(bool isDesktop) {
|
List<Widget> _buildSecondRow(bool isDesktop) {
|
||||||
|
|
||||||
|
|
||||||
DateTime? _selectedCheckOutDate;
|
DateTime? _selectedCheckOutDate;
|
||||||
TimeOfDay? _selectedCheckOutTime;
|
TimeOfDay? _selectedCheckOutTime;
|
||||||
|
|
||||||
@ -381,19 +373,20 @@ class _BusScreenState extends State<BusScreen> {
|
|||||||
|
|
||||||
DateTime? pickedDate = await showDatePicker(
|
DateTime? pickedDate = await showDatePicker(
|
||||||
context: context,
|
context: context,
|
||||||
|
initialDate:
|
||||||
|
_selectedCheckOutDate != null &&
|
||||||
initialDate: _selectedCheckOutDate != null && _selectedCheckOutDate!.isAfter(today)
|
_selectedCheckOutDate!.isAfter(today)
|
||||||
? _selectedCheckOutDate!
|
? _selectedCheckOutDate!
|
||||||
: today,
|
: today,
|
||||||
firstDate: today,
|
firstDate: today,
|
||||||
lastDate: DateTime(2100),
|
lastDate: DateTime(2100),
|
||||||
|
initialEntryMode: DatePickerEntryMode.calendarOnly,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
|
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedCheckOutDate = pickedDate;
|
_selectedCheckOutDate = pickedDate;
|
||||||
_dateController.text = DateFormat('yyyy-MM-dd').format(pickedDate);
|
_dateController.text = DateFormat('dd-MM-yyyy').format(pickedDate);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -405,30 +398,64 @@ class _BusScreenState extends State<BusScreen> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (pickedTime != null && pickedTime != _selectedCheckOutTime) {
|
if (pickedTime != null && pickedTime != _selectedCheckOutTime) {
|
||||||
|
// Parse the selected date
|
||||||
|
final dateText = _dateController.text ?? "";
|
||||||
|
final selectedDate = DateFormat(
|
||||||
|
'dd-MM-yyyy',
|
||||||
|
).parse(dateText); // or 'yyyy-MM-dd' depending on your format
|
||||||
|
final now = DateTime.now();
|
||||||
|
|
||||||
|
final selectedDateTime = DateTime(
|
||||||
|
selectedDate.year,
|
||||||
|
selectedDate.month,
|
||||||
|
selectedDate.day,
|
||||||
|
pickedTime.hour,
|
||||||
|
pickedTime.minute,
|
||||||
|
);
|
||||||
|
|
||||||
|
final isToday =
|
||||||
|
selectedDate.year == now.year &&
|
||||||
|
selectedDate.month == now.month &&
|
||||||
|
selectedDate.day == now.day;
|
||||||
|
|
||||||
|
bool isPastTime = selectedDateTime.isBefore(now);
|
||||||
|
|
||||||
|
if (isToday && isPastTime) {
|
||||||
|
setState(() {
|
||||||
|
errorMessages["time"] = "You can't select a past time.";
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedCheckOutTime = pickedTime;
|
_selectedCheckOutTime = pickedTime;
|
||||||
// Formatting time to HH:mm (24-hour format)
|
// Formatting time to HH:mm (24-hour format)
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final formattedTime = DateFormat('HH:mm').format(
|
final formattedTime = DateFormat('HH:mm').format(
|
||||||
DateTime(now.year, now.month, now.day, pickedTime.hour,
|
DateTime(
|
||||||
pickedTime.minute),
|
now.year,
|
||||||
|
now.month,
|
||||||
|
now.day,
|
||||||
|
pickedTime.hour,
|
||||||
|
pickedTime.minute,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
_timeController.text = formattedTime;
|
_timeController.text = formattedTime;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"From",
|
"From *",
|
||||||
style: TextStyle(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w500,
|
||||||
color: Color(0xFF575A74)),
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
CustomTextFieldItnerarySubWrapper(
|
CustomTextFieldItnerarySubWrapper(
|
||||||
@ -440,41 +467,36 @@ class _BusScreenState extends State<BusScreen> {
|
|||||||
focusNode: _fromFocusNode,
|
focusNode: _fromFocusNode,
|
||||||
controller: _fromController,
|
controller: _fromController,
|
||||||
style: const TextStyle(fontSize: 12),
|
style: const TextStyle(fontSize: 12),
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9 _-]')),
|
||||||
|
],
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: "From",
|
labelText: "From",
|
||||||
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (errorMessages["from"] != null) ...[
|
if (errorMessages["from"] != null) ...[
|
||||||
SizedBox(height: 5), // Space before error message
|
SizedBox(height: 5), // Space before error message
|
||||||
Text(
|
Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
|
||||||
"Required",
|
|
||||||
style: TextStyle(color: Colors.red, fontSize: 12),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (isDesktop)
|
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||||||
Spacer()
|
|
||||||
else
|
|
||||||
SizedBox(
|
|
||||||
height: 8,
|
|
||||||
),
|
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"To",
|
"To *",
|
||||||
style: TextStyle(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w500,
|
||||||
color: Color(0xFF575A74)),
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
CustomTextFieldItnerarySubWrapper(
|
CustomTextFieldItnerarySubWrapper(
|
||||||
@ -482,10 +504,12 @@ class _BusScreenState extends State<BusScreen> {
|
|||||||
isDesktop: isDesktop,
|
isDesktop: isDesktop,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 40,
|
height: 40,
|
||||||
|
|
||||||
child: TextField(
|
child: TextField(
|
||||||
focusNode: _toFocusNode,
|
focusNode: _toFocusNode,
|
||||||
controller: _toController,
|
controller: _toController,
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9 _-]')),
|
||||||
|
],
|
||||||
style: const TextStyle(fontSize: 12),
|
style: const TextStyle(fontSize: 12),
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: "To",
|
labelText: "To",
|
||||||
@ -499,28 +523,21 @@ class _BusScreenState extends State<BusScreen> {
|
|||||||
),
|
),
|
||||||
if (errorMessages["to"] != null) ...[
|
if (errorMessages["to"] != null) ...[
|
||||||
SizedBox(height: 5), // Space before error message
|
SizedBox(height: 5), // Space before error message
|
||||||
Text(
|
Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
|
||||||
"Required",
|
|
||||||
style: TextStyle(color: Colors.red, fontSize: 12),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (isDesktop)
|
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||||||
Spacer()
|
|
||||||
else
|
|
||||||
SizedBox(
|
|
||||||
height: 8,
|
|
||||||
),
|
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Date",
|
"Date *",
|
||||||
style: TextStyle(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w500,
|
||||||
color: Color(0xFF575A74)),
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
CustomTextFieldItnerarySubWrapper(
|
CustomTextFieldItnerarySubWrapper(
|
||||||
@ -528,13 +545,18 @@ class _BusScreenState extends State<BusScreen> {
|
|||||||
isDesktop: isDesktop,
|
isDesktop: isDesktop,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 40,
|
height: 40,
|
||||||
|
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () => _selectCheckOutDate(context),
|
// onTap: () => _selectCheckOutDate(context),
|
||||||
|
onTap: () {
|
||||||
|
_dateFocusNode.requestFocus();
|
||||||
|
_selectCheckOutDate(context);
|
||||||
|
},
|
||||||
child: AbsorbPointer(
|
child: AbsorbPointer(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
focusNode: _dateFocusNode,
|
focusNode: _dateFocusNode,
|
||||||
controller: _dateController,
|
controller: _dateController,
|
||||||
|
|
||||||
|
readOnly: true,
|
||||||
style: const TextStyle(fontSize: 12),
|
style: const TextStyle(fontSize: 12),
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: "Select Date",
|
labelText: "Select Date",
|
||||||
@ -542,39 +564,34 @@ class _BusScreenState extends State<BusScreen> {
|
|||||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
suffixIcon: Icon(Icons.calendar_today,
|
suffixIcon: Icon(
|
||||||
size: 16, color: Colors.grey),
|
Icons.calendar_today,
|
||||||
|
size: 16,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (errorMessages["date"] != null) ...[
|
if (errorMessages["date"] != null) ...[
|
||||||
SizedBox(height: 5), // Space before error message
|
SizedBox(height: 5), // Space before error message
|
||||||
Text(
|
Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
|
||||||
"Required",
|
|
||||||
style: TextStyle(color: Colors.red, fontSize: 12),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (isDesktop)
|
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||||||
Spacer()
|
|
||||||
else
|
|
||||||
SizedBox(
|
|
||||||
height: 8,
|
|
||||||
),
|
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Time",
|
"Time *",
|
||||||
style: TextStyle(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w500,
|
||||||
color: Color(0xFF575A74)),
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
CustomTextFieldItnerarySubWrapper(
|
CustomTextFieldItnerarySubWrapper(
|
||||||
@ -583,38 +600,44 @@ class _BusScreenState extends State<BusScreen> {
|
|||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 40,
|
height: 40,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () => _selectCheckOutTime(context),
|
onTap: () {
|
||||||
|
// _selectedCheckOutTime = null; // ✅ Reset time variable
|
||||||
|
_timeController.text = ""; // ✅ Clear text field
|
||||||
|
_timeFocusNode.requestFocus();
|
||||||
|
_selectCheckOutTime(context);
|
||||||
|
},
|
||||||
child: AbsorbPointer(
|
child: AbsorbPointer(
|
||||||
child: TextField(
|
child: TextField(
|
||||||
focusNode: _timeFocusNode,
|
focusNode: _timeFocusNode,
|
||||||
controller: _timeController,
|
controller: _timeController,
|
||||||
style: const TextStyle(fontSize: 12),
|
readOnly: true,
|
||||||
|
style: const TextStyle(fontSize: 12, color: Colors.black),
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
labelText: "Select Time",
|
labelText: "Select Time",
|
||||||
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
suffixIcon:
|
suffixIcon: Icon(
|
||||||
Icon(Icons.access_time, size: 16, color: Colors.grey),
|
Icons.access_time,
|
||||||
|
size: 16,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (errorMessages["time"] != null) ...[
|
if (errorMessages["time"] != null) ...[
|
||||||
SizedBox(height: 5), // Space before error message
|
SizedBox(height: 5), // Space before error message
|
||||||
Text(
|
Text(
|
||||||
"Required",
|
errorMessages["time"]!,
|
||||||
style: TextStyle(color: Colors.red, fontSize: 12),
|
style: TextStyle(color: Colors.red, fontSize: 12),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -625,35 +648,47 @@ class _BusScreenState extends State<BusScreen> {
|
|||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Comments",
|
"Comments",
|
||||||
style: TextStyle(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w500,
|
||||||
color: Color(0xFF575A74)),
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
CustomTextFieldWrapper(
|
CustomTextFieldItnerarySubWrapper(
|
||||||
isFocused: _commentsFocus, // Dropdown doesn't use focus
|
isFocused: _commentsFocus, // Dropdown doesn't use focus
|
||||||
isDesktop: isDesktop,
|
isDesktop: isDesktop,
|
||||||
width: isDesktop
|
width: isDesktop ? MediaQuery.of(context).size.width * 0.35 : null,
|
||||||
? MediaQuery.of(context).size.width * 0.4
|
child: SizedBox(
|
||||||
: MediaQuery.of(context).size.width * 0.66,
|
height: 40,
|
||||||
child: TextField(
|
child: TextField(
|
||||||
focusNode: _commentsFocusNode,
|
focusNode: _commentsFocusNode,
|
||||||
controller: _buscommentsController,
|
controller: _buscommentsController,
|
||||||
maxLines: 6,
|
inputFormatters: [
|
||||||
keyboardType: TextInputType.multiline,
|
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9 _-]')),
|
||||||
|
],
|
||||||
style: TextStyle(fontSize: 12),
|
style: TextStyle(fontSize: 12),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: "Description",
|
labelText: "Comments",
|
||||||
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
contentPadding: EdgeInsets.symmetric(vertical: 4),
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
),
|
||||||
|
if (isDesktop) Spacer(),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: _handleAction(isDesktop),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -662,37 +697,33 @@ class _BusScreenState extends State<BusScreen> {
|
|||||||
// Close Button
|
// Close Button
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
widget.onClose(false);// Close the dialog or screen
|
widget.onClose("Bus", false);
|
||||||
|
// widget.onClose(false); // Close the dialog or screen
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.grey[400], // Light grey color
|
backgroundColor: Colors.grey[400], // Light grey color
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
"Close",
|
"Close",
|
||||||
style: TextStyle(color: Colors.white, fontSize: 14),
|
style: GoogleFonts.poppins(color: Colors.white, fontSize: 12),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 10), // Space between buttons
|
SizedBox(width: 10), // Space between buttons
|
||||||
|
|
||||||
// Save Changes Button
|
// Save Changes Button
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
handleSave();
|
handleSave();
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.blue, // Primary color for save
|
backgroundColor: Color(0xFF114D8B), // Primary color for save
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
"Save Changes",
|
"Save Changes",
|
||||||
style: TextStyle(color: Colors.white, fontSize: 14),
|
style: GoogleFonts.poppins(color: Colors.white, fontSize: 12),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|||||||
@ -1,22 +1,31 @@
|
|||||||
|
import 'package:dropdown_search/dropdown_search.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:responsive_builder/responsive_builder.dart';
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
|
||||||
|
import '../../utils/auth_utils.dart';
|
||||||
import '../../widgets/custom_text_field.dart';
|
import '../../widgets/custom_text_field.dart';
|
||||||
import '../../widgets/custom_text_itnerary_sub.dart';
|
import '../../widgets/custom_text_itnerary_sub.dart';
|
||||||
|
|
||||||
class MiscellaneousScreen extends StatefulWidget {
|
class MiscellaneousScreen extends StatefulWidget {
|
||||||
|
|
||||||
final Map<String, dynamic>? apiData;
|
final Map<String, dynamic>? apiData;
|
||||||
final Function(bool) onClose;
|
final Function(String, bool) onClose;
|
||||||
|
|
||||||
final Function(Map<String, dynamic>) onSaveMiscellaneous;
|
final Function(Map<String, dynamic>) onSaveMiscellaneous;
|
||||||
final Map<String, dynamic>? selectedItem;
|
final Map<String, dynamic>? selectedItem;
|
||||||
final int? selectedIndex;
|
final int? selectedIndex;
|
||||||
final String? loginUser;
|
final String? loginUser;
|
||||||
|
|
||||||
MiscellaneousScreen({
|
MiscellaneousScreen({
|
||||||
required this.onClose, required this.apiData, required this.onSaveMiscellaneous,
|
required this.onClose,
|
||||||
this.selectedItem, this.selectedIndex,required this.loginUser});
|
required this.apiData,
|
||||||
|
required this.onSaveMiscellaneous,
|
||||||
|
this.selectedItem,
|
||||||
|
this.selectedIndex,
|
||||||
|
required this.loginUser,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_MiscellaneousScreenState createState() => _MiscellaneousScreenState();
|
_MiscellaneousScreenState createState() => _MiscellaneousScreenState();
|
||||||
@ -37,10 +46,10 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
|
|||||||
|
|
||||||
late TextEditingController _commentsController = TextEditingController();
|
late TextEditingController _commentsController = TextEditingController();
|
||||||
|
|
||||||
bool _isHotelNameFocused = false;
|
bool _hotelNameFocused = false;
|
||||||
bool _commentsFocus = false;
|
bool _commentsFocus = false;
|
||||||
String? selectedSpecialType;
|
String? selectedSpecialType;
|
||||||
|
Color layoutColor = Colors.grey;
|
||||||
Map<String, String> errorMessages = {};
|
Map<String, String> errorMessages = {};
|
||||||
|
|
||||||
Map<String, dynamic> get miscellaneousData {
|
Map<String, dynamic> get miscellaneousData {
|
||||||
@ -49,14 +58,14 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
|
|||||||
"comments": _commentsController.text,
|
"comments": _commentsController.text,
|
||||||
"created_by": widget.loginUser,
|
"created_by": widget.loginUser,
|
||||||
"updated_by": widget.loginUser,
|
"updated_by": widget.loginUser,
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (widget.selectedItem != null) {
|
if (widget.selectedItem != null) {
|
||||||
if (widget.selectedItem?["indx"] != null && widget.selectedItem?["indx"] != 0) {
|
if (widget.selectedItem?["indx"] != null &&
|
||||||
|
widget.selectedItem?["indx"] != 0) {
|
||||||
data["indx"] = widget.selectedItem!["indx"];
|
data["indx"] = widget.selectedItem!["indx"];
|
||||||
|
} else if (widget.selectedItem?["miscellaneous_id"] != null &&
|
||||||
} else if (widget.selectedItem?["miscellaneous_id"] != null && widget.selectedItem?["miscellaneous_id"] != 0) {
|
widget.selectedItem?["miscellaneous_id"] != 0) {
|
||||||
data["miscellaneous_id"] = widget.selectedItem!["miscellaneous_id"];
|
data["miscellaneous_id"] = widget.selectedItem!["miscellaneous_id"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,19 +73,18 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
if (widget.selectedItem == null) {
|
if (widget.selectedItem == null) {
|
||||||
_commentsController.clear();
|
_commentsController.clear();
|
||||||
}
|
}
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
loadInitialData();
|
||||||
|
});
|
||||||
_hotelNameFocusNode.addListener(() {
|
_hotelNameFocusNode.addListener(() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isHotelNameFocused = _hotelNameFocusNode.hasFocus;
|
_hotelNameFocused = _hotelNameFocusNode.hasFocus;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -86,27 +94,43 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
_commentsController =
|
_commentsController = TextEditingController(
|
||||||
TextEditingController(text: widget.selectedItem?["comments"] ?? "");
|
text: widget.selectedItem?["comments"] ?? "",
|
||||||
|
);
|
||||||
|
|
||||||
// Set the selected value if available
|
// Set the selected value if available
|
||||||
if (widget.selectedItem != null && widget.selectedItem!["special_request"] != null) {
|
if (widget.selectedItem != null &&
|
||||||
|
widget.selectedItem!["special_request"] != null) {
|
||||||
selectedSpecialType = widget.selectedItem!["special_request"].toString();
|
selectedSpecialType = widget.selectedItem!["special_request"].toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_tripTypeFocusNode.dispose();
|
_tripTypeFocusNode.dispose();
|
||||||
|
_hotelNameFocusNode.dispose();
|
||||||
_tripTypeController.dispose();
|
_tripTypeController.dispose();
|
||||||
_commentsController.dispose();
|
_commentsController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void loadInitialData() async {
|
||||||
|
String? layoutString = await getLayoutColor();
|
||||||
|
|
||||||
|
// setState(() {
|
||||||
|
// layoutColor =
|
||||||
|
// layoutString != null
|
||||||
|
// ? Color(int.parse(layoutString))
|
||||||
|
// : Colors.redAccent;
|
||||||
|
// });
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
layoutColor =
|
||||||
|
layoutString != null
|
||||||
|
? Color(int.parse(layoutString))
|
||||||
|
: Colors.redAccent;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
bool isValidData(Map<String, dynamic> data) {
|
bool isValidData(Map<String, dynamic> data) {
|
||||||
errorMessages.clear(); // Reset errors
|
errorMessages.clear(); // Reset errors
|
||||||
@ -114,90 +138,68 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
|
|||||||
// Required fields that must not be empty
|
// Required fields that must not be empty
|
||||||
List<String> requiredFields = ["special_request", "comments"];
|
List<String> requiredFields = ["special_request", "comments"];
|
||||||
|
|
||||||
|
|
||||||
// Check validation for each field
|
// Check validation for each field
|
||||||
for (String field in requiredFields) {
|
for (String field in requiredFields) {
|
||||||
if (data[field] == null || data[field].toString().trim().isEmpty) {
|
if (data[field] == null || data[field].toString().trim().isEmpty) {
|
||||||
errorMessages[field] = "This field is required";
|
errorMessages[field] = "Required";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return errorMessages.isEmpty; // Valid if there are no errors
|
return errorMessages.isEmpty; // Valid if there are no errors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void handleSave() {
|
||||||
|
print("Handle Save miscellaneousData $miscellaneousData");
|
||||||
|
|
||||||
|
Map<String, dynamic> data = miscellaneousData;
|
||||||
void handleSave(){
|
|
||||||
|
|
||||||
print( "Handle Save miscellaneousData $miscellaneousData");
|
|
||||||
|
|
||||||
Map<String,dynamic> data = miscellaneousData;
|
|
||||||
|
|
||||||
if (!isValidData(data)) {
|
if (!isValidData(data)) {
|
||||||
print("Validation Failed: Required fields are missing.");
|
print("Validation Failed: Required fields are missing.");
|
||||||
setState(() {});
|
setState(() {});
|
||||||
return; // Stop execution if validation fails
|
return; // Stop execution if validation fails
|
||||||
}else {
|
} else {
|
||||||
widget.onSaveMiscellaneous(miscellaneousData); // Send object to parent
|
widget.onSaveMiscellaneous(miscellaneousData); // Send object to parent
|
||||||
}
|
}
|
||||||
|
|
||||||
widget.onClose(false);// Close screen after saving
|
widget.onClose("Miscellaneous", false); // Close screen after saving
|
||||||
|
// widget.onClose(false); // Close screen after saving
|
||||||
// Clear only if this is a new entry
|
// Clear only if this is a new entry
|
||||||
// if (widget.selectedItem == null) {
|
// if (widget.selectedItem == null) {
|
||||||
// _commentsController.clear();
|
// _commentsController.clear();
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ResponsiveBuilder(builder: (context, sizingInfo) {
|
return ResponsiveBuilder(
|
||||||
|
builder: (context, sizingInfo) {
|
||||||
bool isMobile = sizingInfo.isMobile;
|
bool isMobile = sizingInfo.isMobile;
|
||||||
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
bool isDesktop =
|
||||||
|
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
color: Color(0xFFF4F4FB),
|
|
||||||
child: Form(
|
child: Form(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Align(
|
|
||||||
alignment: Alignment.centerRight,
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
_commentsController.clear();
|
|
||||||
widget.onClose(false);
|
|
||||||
},
|
|
||||||
child: Icon(
|
|
||||||
Icons.close,
|
|
||||||
size: 18,
|
|
||||||
color: Color(0xFF575A74),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text("Miscellaneous Booking List",
|
|
||||||
style:
|
|
||||||
TextStyle(fontSize: 18, fontWeight: FontWeight.bold,color: Color(0xFF575A74))),
|
|
||||||
SizedBox(
|
|
||||||
height: 6,
|
|
||||||
),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(28.0),
|
padding: const EdgeInsets.only(top: 30.0),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(children: _buildAccomadtionForm(isDesktop)),
|
child: Column(children: _buildAccomadtionForm(isDesktop)),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildAccomadtionForm (bool isDesktop) {
|
List<Widget> _buildAccomadtionForm(bool isDesktop) {
|
||||||
List<Widget> buildResponsiveRow(List<Widget> children) {
|
List<Widget> buildResponsiveRow(List<Widget> children) {
|
||||||
return [
|
return [
|
||||||
isDesktop ? Row(children: children) : Column(children: children),
|
isDesktop ? Row(children: children) : Column(children: children),
|
||||||
@ -205,170 +207,279 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
...buildResponsiveRow(_buildFirstRow(isDesktop)),
|
...buildResponsiveRow(_buildFirstRow(isDesktop)),
|
||||||
|
|
||||||
...buildResponsiveRow(_buildThirdRow(isDesktop)),
|
...buildResponsiveRow(_buildThirdRow(isDesktop)),
|
||||||
|
|
||||||
// Actions row remains a Row
|
// Actions row remains a Row
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: _handleAction(isDesktop),
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
List<Widget> _buildFirstRow(isDesktop) {
|
List<Widget> _buildFirstRow(isDesktop) {
|
||||||
|
|
||||||
return [
|
return [
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Special Type",
|
"Special Type *",
|
||||||
style: TextStyle(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w500,
|
||||||
color: Color(0xFF575A74)),
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
|
isDesktop
|
||||||
isDesktop ? Row(children: _buildTripType(isDesktop)
|
? Row(children: _buildTripType(isDesktop))
|
||||||
) :
|
: Column(children: _buildTripType(isDesktop)),
|
||||||
Column(
|
|
||||||
children: _buildTripType(isDesktop)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (isDesktop)
|
|
||||||
Spacer()
|
|
||||||
else
|
|
||||||
SizedBox(
|
|
||||||
height: 8,
|
|
||||||
),
|
|
||||||
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildTripType(bool isDesktop) {
|
||||||
|
List<dynamic> purposeList =
|
||||||
List<Widget> _buildTripType(bool isDesktop){
|
widget.apiData?['miscellaneous_special_request'] ?? [];
|
||||||
|
|
||||||
List<dynamic> purposeList = widget.apiData?['miscellaneous_special_request'] ?? [];
|
|
||||||
// selectedInsuranceType = purposeList.isNotEmpty ? purposeList.first['dropdown_value'] : null;
|
// selectedInsuranceType = purposeList.isNotEmpty ? purposeList.first['dropdown_value'] : null;
|
||||||
|
|
||||||
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
List<DropdownMenuItem<String>> dropdownItems =
|
||||||
.map((item)=>DropdownMenuItem<String>(
|
purposeList
|
||||||
|
.map(
|
||||||
|
(item) => DropdownMenuItem<String>(
|
||||||
value: item['dropdown_key'],
|
value: item['dropdown_key'],
|
||||||
child: Text(item['dropdown_value']),
|
child: Text(item['dropdown_value']),
|
||||||
)).toList();
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
if (dropdownItems.isEmpty) {
|
if (dropdownItems.isEmpty) {
|
||||||
dropdownItems.add(
|
dropdownItems.add(
|
||||||
DropdownMenuItem<String>(
|
DropdownMenuItem<String>(
|
||||||
value: null,
|
value: null,
|
||||||
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
child: Text(
|
||||||
|
"No options available",
|
||||||
|
style: TextStyle(color: Colors.grey),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default selected value
|
// Default selected value
|
||||||
selectedSpecialType ??= dropdownItems.isNotEmpty ? dropdownItems.first.value : "No options";
|
selectedSpecialType ??=
|
||||||
|
dropdownItems.isNotEmpty ? dropdownItems.first.value : "No options";
|
||||||
|
|
||||||
return [
|
return [
|
||||||
CustomTextFieldWrapper(
|
// CustomTextFieldWrapper(
|
||||||
isFocused: _isHotelNameFocused,
|
// isFocused: _isHotelNameFocused,
|
||||||
|
// isDesktop: isDesktop,
|
||||||
|
// width:
|
||||||
|
// isDesktop
|
||||||
|
// ? MediaQuery.of(context).size.width * 0.34
|
||||||
|
// : MediaQuery.of(context).size.width * 0.66,
|
||||||
|
// child: SizedBox(
|
||||||
|
// height: 40,
|
||||||
|
// child: DropdownButtonFormField<String>(
|
||||||
|
// focusNode: _tripTypeFocusNode, // Assign the correct focus node
|
||||||
|
// value: selectedSpecialType,
|
||||||
|
// style: TextStyle(fontSize: 12),
|
||||||
|
// decoration: InputDecoration(
|
||||||
|
// border: InputBorder.none,
|
||||||
|
// contentPadding: EdgeInsets.symmetric(
|
||||||
|
// horizontal: 10,
|
||||||
|
// ), // Proper padding
|
||||||
|
// ),
|
||||||
|
// onChanged:
|
||||||
|
// purposeList.isNotEmpty
|
||||||
|
// ? (newValue) {
|
||||||
|
// setState(() {
|
||||||
|
// selectedSpecialType = newValue;
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// print(selectedSpecialType);
|
||||||
|
// }
|
||||||
|
// : null,
|
||||||
|
// items: dropdownItems,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
|
isFocused: _hotelNameFocused,
|
||||||
isDesktop: isDesktop,
|
isDesktop: isDesktop,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 0),
|
||||||
|
width:
|
||||||
|
isDesktop
|
||||||
|
? MediaQuery.of(context).size.width * 0.34
|
||||||
|
: null, //MediaQuery.of(context).size.width * 0.66,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 40,
|
height: 40,
|
||||||
|
width: double.infinity,
|
||||||
child: DropdownButtonFormField<String>(
|
child: Focus(
|
||||||
focusNode: _tripTypeFocusNode, // Assign the correct focus node
|
focusNode: _hotelNameFocusNode,
|
||||||
value: selectedSpecialType,
|
onFocusChange: (hasFocus) {
|
||||||
style: TextStyle(fontSize: 12),
|
setState(() {
|
||||||
decoration: InputDecoration(
|
_hotelNameFocused = hasFocus;
|
||||||
border: InputBorder.none,
|
});
|
||||||
contentPadding: EdgeInsets.symmetric(
|
},
|
||||||
horizontal: 10), // Proper padding
|
child: GestureDetector(
|
||||||
|
//
|
||||||
|
onTap: () {
|
||||||
|
// Request focus when user taps
|
||||||
|
_hotelNameFocusNode?.requestFocus();
|
||||||
|
},
|
||||||
|
child: DropdownSearch<Map<String, dynamic>>(
|
||||||
|
// focusNode: _taxiReqFocusNode,
|
||||||
|
items: purposeList.cast<Map<String, dynamic>>(),
|
||||||
|
selectedItem: purposeList.firstWhere(
|
||||||
|
(item) => item['dropdown_key'] == selectedSpecialType,
|
||||||
|
orElse: () => {},
|
||||||
),
|
),
|
||||||
onChanged: purposeList.isNotEmpty
|
itemAsString: (item) => item['dropdown_value'] ?? '',
|
||||||
|
popupProps: PopupProps.menu(
|
||||||
|
showSearchBox: false,
|
||||||
|
fit: FlexFit.loose,
|
||||||
|
menuProps: const MenuProps(backgroundColor: Colors.white),
|
||||||
|
itemBuilder: (context, item, isSelected) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 10,
|
||||||
|
vertical: 5,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
item['dropdown_value'] ?? '',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
dropdownDecoratorProps: DropDownDecoratorProps(
|
||||||
|
dropdownSearchDecoration: InputDecoration(
|
||||||
|
// border: InputBorder.none,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color:
|
||||||
|
(_hotelNameFocused ?? false)
|
||||||
|
? layoutColor!
|
||||||
|
: Colors.white,
|
||||||
|
width: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color:
|
||||||
|
(_hotelNameFocused ?? false)
|
||||||
|
? layoutColor
|
||||||
|
: Colors.white,
|
||||||
|
// : const Color(0xFFD6D5E6),
|
||||||
|
width: 0.5,
|
||||||
|
// const Color(0xFFD6D5E6),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderSide: BorderSide(color: layoutColor, width: 0.5),
|
||||||
|
),
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 10,
|
||||||
|
vertical: 10,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
dropdownBuilder: (context, selectedItem) {
|
||||||
|
if (selectedItem == null || selectedItem.isEmpty) {
|
||||||
|
return Text(
|
||||||
|
"Select ",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
color: Colors.grey,
|
||||||
|
fontSize: 13,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Text(
|
||||||
|
selectedItem['dropdown_value'] ?? '',
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onChanged:
|
||||||
|
purposeList.isNotEmpty
|
||||||
? (newValue) {
|
? (newValue) {
|
||||||
setState(() {
|
setState(() {
|
||||||
selectedSpecialType = newValue;
|
selectedSpecialType = newValue as String?;
|
||||||
});
|
});
|
||||||
|
print("Updating form data: -> ${newValue ?? ""}");
|
||||||
print(selectedSpecialType);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
items: dropdownItems,
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (errorMessages["special_request"] != null) ...[
|
if (errorMessages["special_request"] != null) ...[
|
||||||
SizedBox(height: 5), // Space before error message
|
SizedBox(height: 5), // Space before error message
|
||||||
Text(
|
Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
|
||||||
"Select Type",
|
|
||||||
style: TextStyle(color: Colors.red, fontSize: 12),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
List<Widget> _buildThirdRow(bool isDesktop) {
|
List<Widget> _buildThirdRow(bool isDesktop) {
|
||||||
return [
|
return [
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"Comments",
|
"Comments *",
|
||||||
style: TextStyle(
|
style: GoogleFonts.poppins(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w500,
|
||||||
color: Color(0xFF575A74)),
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
CustomTextFieldWrapper(
|
// CustomTextFieldWrapper
|
||||||
|
CustomTextFieldItnerarySubWrapper(
|
||||||
isFocused: _commentsFocus, // Dropdown doesn't use focus
|
isFocused: _commentsFocus, // Dropdown doesn't use focus
|
||||||
isDesktop: isDesktop,
|
isDesktop: isDesktop,
|
||||||
width: isDesktop
|
width:
|
||||||
? MediaQuery.of(context).size.width * 0.4
|
isDesktop
|
||||||
: MediaQuery.of(context).size.width * 0.66,
|
? MediaQuery.of(context).size.width * 0.34
|
||||||
|
: null, //MediaQuery.of(context).size.width * 0.66,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 40,
|
||||||
child: TextField(
|
child: TextField(
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9 _-]')),
|
||||||
|
],
|
||||||
focusNode: _commentsFocusNode,
|
focusNode: _commentsFocusNode,
|
||||||
controller: _commentsController,
|
controller: _commentsController,
|
||||||
maxLines: 6,
|
|
||||||
keyboardType: TextInputType.multiline,
|
|
||||||
style: TextStyle(fontSize: 12),
|
style: TextStyle(fontSize: 12),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: "Description",
|
labelText: "Comments",
|
||||||
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
contentPadding: EdgeInsets.symmetric(vertical: 4),
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (errorMessages["comments"] != null) ...[
|
if (errorMessages["comments"] != null) ...[
|
||||||
SizedBox(height: 5), // Space before error message
|
SizedBox(height: 5), // Space before error message
|
||||||
Text(
|
Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
|
||||||
"Required",
|
],
|
||||||
style: TextStyle(color: Colors.red, fontSize: 12),
|
],
|
||||||
|
),
|
||||||
|
if (isDesktop) Spacer(),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: _handleAction(isDesktop),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
],
|
|
||||||
)
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -377,39 +488,34 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
|
|||||||
// Close Button
|
// Close Button
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
|
||||||
_commentsController.clear();
|
_commentsController.clear();
|
||||||
widget.onClose(false);// Close the dialog or screen
|
widget.onClose("Miscellaneous", false);
|
||||||
|
// widget.onClose(false); // Close the dialog or screen
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.grey[400], // Light grey color
|
backgroundColor: Colors.grey[400], // Light grey color
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
"Close",
|
"Close",
|
||||||
style: TextStyle(color: Colors.white, fontSize: 14),
|
style: GoogleFonts.poppins(color: Colors.white, fontSize: 12),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 10), // Space between buttons
|
SizedBox(width: 10), // Space between buttons
|
||||||
|
|
||||||
// Save Changes Button
|
// Save Changes Button
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
handleSave();
|
handleSave();
|
||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.blue, // Primary color for save
|
backgroundColor: Color(0xFF114D8B), // Primary color for save
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
"Save Changes",
|
"Save Changes",
|
||||||
style: TextStyle(color: Colors.white, fontSize: 14),
|
style: GoogleFonts.poppins(color: Colors.white, fontSize: 12),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:responsive_builder/responsive_builder.dart';
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
|
|
||||||
@ -11,8 +12,12 @@ class ForexScreen extends StatefulWidget {
|
|||||||
final Function(String tab, String key, String value) updateFormData;
|
final Function(String tab, String key, String value) updateFormData;
|
||||||
final Function(bool) onClose;
|
final Function(bool) onClose;
|
||||||
|
|
||||||
ForexScreen({required this.formData, required this.updateFormData,
|
ForexScreen({
|
||||||
required this.onClose, this.apiData});
|
required this.formData,
|
||||||
|
required this.updateFormData,
|
||||||
|
required this.onClose,
|
||||||
|
this.apiData,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_ForexScreenState createState() => _ForexScreenState();
|
_ForexScreenState createState() => _ForexScreenState();
|
||||||
@ -95,57 +100,55 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_tripTypeController = TextEditingController(
|
||||||
|
text: widget.formData["tripType"] ?? "",
|
||||||
_tripTypeController =
|
);
|
||||||
TextEditingController(text: widget.formData["tripType"] ?? "");
|
_hotelNameController = TextEditingController(
|
||||||
_hotelNameController =
|
text: widget.formData["_hotelName"] ?? "",
|
||||||
TextEditingController(text: widget.formData["_hotelName"] ?? "");
|
);
|
||||||
_fromController =
|
_fromController = TextEditingController(
|
||||||
TextEditingController(text: widget.formData["_from"] ?? "");
|
text: widget.formData["_from"] ?? "",
|
||||||
_toController =
|
);
|
||||||
TextEditingController(text: widget.formData["_Check_In_Time"] ?? "");
|
_toController = TextEditingController(
|
||||||
_dateController =
|
text: widget.formData["_Check_In_Time"] ?? "",
|
||||||
TextEditingController(text: widget.formData["_Check_Out"] ?? "");
|
);
|
||||||
_endDateController =
|
_dateController = TextEditingController(
|
||||||
TextEditingController(text: widget.formData["_Check_In"] ?? "");
|
text: widget.formData["_Check_Out"] ?? "",
|
||||||
_timeController =
|
);
|
||||||
TextEditingController(text: widget.formData["_Check_Out_Time"] ?? "");
|
_endDateController = TextEditingController(
|
||||||
_commentsController =
|
text: widget.formData["_Check_In"] ?? "",
|
||||||
TextEditingController(text: widget.formData["_comments"] ?? "");
|
);
|
||||||
|
_timeController = TextEditingController(
|
||||||
|
text: widget.formData["_Check_Out_Time"] ?? "",
|
||||||
|
);
|
||||||
|
_commentsController = TextEditingController(
|
||||||
|
text: widget.formData["_comments"] ?? "",
|
||||||
|
);
|
||||||
|
|
||||||
// Save data when user types
|
// Save data when user types
|
||||||
_tripTypeController.addListener(() {
|
_tripTypeController.addListener(() {
|
||||||
widget.updateFormData(
|
widget.updateFormData("Bus", "_tripType", _tripTypeController.text);
|
||||||
"Bus", "_tripType", _tripTypeController.text);
|
|
||||||
}); // Save data when user types
|
}); // Save data when user types
|
||||||
_hotelNameController.addListener(() {
|
_hotelNameController.addListener(() {
|
||||||
widget.updateFormData(
|
widget.updateFormData("Bus", "_hotelName", _hotelNameController.text);
|
||||||
"Bus", "_hotelName", _hotelNameController.text);
|
|
||||||
});
|
});
|
||||||
_fromController.addListener(() {
|
_fromController.addListener(() {
|
||||||
widget.updateFormData(
|
widget.updateFormData("Bus", "_from", _fromController.text);
|
||||||
"Bus", "_from", _fromController.text);
|
|
||||||
});
|
});
|
||||||
_toController.addListener(() {
|
_toController.addListener(() {
|
||||||
widget.updateFormData(
|
widget.updateFormData("Bus", "_Check_In_Time", _toController.text);
|
||||||
"Bus", "_Check_In_Time", _toController.text);
|
|
||||||
});
|
});
|
||||||
_dateController.addListener(() {
|
_dateController.addListener(() {
|
||||||
widget.updateFormData(
|
widget.updateFormData("Bus", "_Check_Out", _dateController.text);
|
||||||
"Bus", "_Check_Out", _dateController.text);
|
|
||||||
});
|
});
|
||||||
_endDateController.addListener(() {
|
_endDateController.addListener(() {
|
||||||
widget.updateFormData(
|
widget.updateFormData("Bus", "_Check_In", _endDateController.text);
|
||||||
"Bus", "_Check_In", _endDateController.text);
|
|
||||||
});
|
});
|
||||||
_timeController.addListener(() {
|
_timeController.addListener(() {
|
||||||
widget.updateFormData(
|
widget.updateFormData("Bus", "_Check_Out_Time", _timeController.text);
|
||||||
"Bus", "_Check_Out_Time", _timeController.text);
|
|
||||||
});
|
});
|
||||||
_commentsController.addListener(() {
|
_commentsController.addListener(() {
|
||||||
widget.updateFormData(
|
widget.updateFormData("Bus", "_comments", _commentsController.text);
|
||||||
"Bus", "_comments", _commentsController.text);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,13 +166,13 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ResponsiveBuilder(builder: (context, sizingInfo) {
|
return ResponsiveBuilder(
|
||||||
|
builder: (context, sizingInfo) {
|
||||||
bool isMobile = sizingInfo.isMobile;
|
bool isMobile = sizingInfo.isMobile;
|
||||||
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
bool isDesktop =
|
||||||
|
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
color: Color(0xFFF4F4FB),
|
color: Color(0xFFF4F4FB),
|
||||||
@ -192,27 +195,31 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text("Forex List",
|
Text(
|
||||||
style:
|
"Forex List",
|
||||||
TextStyle(fontSize: 18, fontWeight: FontWeight.bold,color: Color(0xFF575A74))),
|
style: TextStyle(
|
||||||
SizedBox(
|
fontSize: 18,
|
||||||
height: 6,
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 6),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(28.0),
|
padding: const EdgeInsets.all(28.0),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(children: _buildAccomadtionForm(isDesktop)),
|
child: Column(children: _buildAccomadtionForm(isDesktop)),
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildAccomadtionForm (bool isDesktop) {
|
List<Widget> _buildAccomadtionForm(bool isDesktop) {
|
||||||
List<Widget> buildResponsiveRow(List<Widget> children) {
|
List<Widget> buildResponsiveRow(List<Widget> children) {
|
||||||
return [
|
return [
|
||||||
isDesktop ? Row(children: children) : Column(children: children),
|
isDesktop ? Row(children: children) : Column(children: children),
|
||||||
@ -222,14 +229,12 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
|
|
||||||
List<List<Widget>> rowBuilders = [
|
List<List<Widget>> rowBuilders = [
|
||||||
_builClassType(isDesktop),
|
_builClassType(isDesktop),
|
||||||
_buildSecondRow(isDesktop)
|
_buildSecondRow(isDesktop),
|
||||||
];
|
];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
||||||
...buildResponsiveRow(_buildFirstRow(isDesktop)),
|
...buildResponsiveRow(_buildFirstRow(isDesktop)),
|
||||||
|
|
||||||
|
|
||||||
// Iterate over rowBuilders and wrap each in a responsive container
|
// Iterate over rowBuilders and wrap each in a responsive container
|
||||||
...rowBuilders.expand((row) => buildResponsiveRow(row)),
|
...rowBuilders.expand((row) => buildResponsiveRow(row)),
|
||||||
|
|
||||||
@ -247,21 +252,19 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
List<Widget> _buildFirstRow(isDesktop) {
|
List<Widget> _buildFirstRow(isDesktop) {
|
||||||
|
|
||||||
DateTime? _selectedCheckOutDate;
|
DateTime? _selectedCheckOutDate;
|
||||||
DateTime? _selectedEndDate;
|
DateTime? _selectedEndDate;
|
||||||
|
|
||||||
|
|
||||||
Future<void> _selectCheckOutDate(BuildContext context) async {
|
Future<void> _selectCheckOutDate(BuildContext context) async {
|
||||||
DateTime now = DateTime.now();
|
DateTime now = DateTime.now();
|
||||||
DateTime today = DateTime(now.year, now.month, now.day);
|
DateTime today = DateTime(now.year, now.month, now.day);
|
||||||
|
|
||||||
DateTime? pickedDate = await showDatePicker(
|
DateTime? pickedDate = await showDatePicker(
|
||||||
context: context,
|
context: context,
|
||||||
initialDate: _selectedCheckOutDate != null && _selectedCheckOutDate!.isAfter(today)
|
initialDate:
|
||||||
|
_selectedCheckOutDate != null &&
|
||||||
|
_selectedCheckOutDate!.isAfter(today)
|
||||||
? _selectedCheckOutDate!
|
? _selectedCheckOutDate!
|
||||||
: today,
|
: today,
|
||||||
firstDate: today,
|
firstDate: today,
|
||||||
@ -274,16 +277,18 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
_dateController.text = DateFormat('yyyy-MM-dd').format(pickedDate);
|
_dateController.text = DateFormat('yyyy-MM-dd').format(pickedDate);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
Future<void> _selectForexEndDate(BuildContext context) async {
|
Future<void> _selectForexEndDate(BuildContext context) async {
|
||||||
|
|
||||||
DateTime now = DateTime.now();
|
DateTime now = DateTime.now();
|
||||||
DateTime today = DateTime(now.year, now.month, now.day);
|
DateTime today = DateTime(now.year, now.month, now.day);
|
||||||
|
|
||||||
DateTime? pickedDate = await showDatePicker(
|
DateTime? pickedDate = await showDatePicker(
|
||||||
context: context,
|
context: context,
|
||||||
initialDate: _selectedEndDate != null && _selectedEndDate!.isAfter(today)
|
initialDate:
|
||||||
|
_selectedEndDate != null && _selectedEndDate!.isAfter(today)
|
||||||
? _selectedEndDate!
|
? _selectedEndDate!
|
||||||
: today,
|
: today,
|
||||||
firstDate: today,
|
firstDate: today,
|
||||||
@ -298,7 +303,6 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@ -308,7 +312,8 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Color(0xFF575A74)),
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
CustomTextFieldWrapper(
|
CustomTextFieldWrapper(
|
||||||
@ -330,23 +335,20 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
suffixIcon: Icon(Icons.calendar_today,
|
suffixIcon: Icon(
|
||||||
size: 16, color: Colors.grey),
|
Icons.calendar_today,
|
||||||
|
size: 16,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (isDesktop)
|
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||||||
Spacer()
|
|
||||||
else
|
|
||||||
SizedBox(
|
|
||||||
height: 8,
|
|
||||||
),
|
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -355,7 +357,8 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Color(0xFF575A74)),
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
CustomTextFieldWrapper(
|
CustomTextFieldWrapper(
|
||||||
@ -377,46 +380,50 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
suffixIcon: Icon(Icons.calendar_today,
|
suffixIcon: Icon(
|
||||||
size: 16, color: Colors.grey),
|
Icons.calendar_today,
|
||||||
|
size: 16,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildTripType(bool isDesktop) {
|
||||||
|
|
||||||
List<Widget> _buildTripType(bool isDesktop){
|
|
||||||
|
|
||||||
List<dynamic> purposeList = widget.apiData?['flight_trip_type'] ?? [];
|
List<dynamic> purposeList = widget.apiData?['flight_trip_type'] ?? [];
|
||||||
|
|
||||||
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
List<DropdownMenuItem<String>> dropdownItems =
|
||||||
.map((item)=>DropdownMenuItem<String>(
|
purposeList
|
||||||
|
.map(
|
||||||
|
(item) => DropdownMenuItem<String>(
|
||||||
value: item['dropdown_value'],
|
value: item['dropdown_value'],
|
||||||
child: Text(item['dropdown_value']),
|
child: Text(item['dropdown_value']),
|
||||||
)).toList();
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
if (dropdownItems.isEmpty) {
|
if (dropdownItems.isEmpty) {
|
||||||
dropdownItems.add(
|
dropdownItems.add(
|
||||||
DropdownMenuItem<String>(
|
DropdownMenuItem<String>(
|
||||||
value: null,
|
value: null,
|
||||||
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
child: Text(
|
||||||
|
"No options available",
|
||||||
|
style: TextStyle(color: Colors.grey),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default selected value
|
// Default selected value
|
||||||
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
String? selectedPurpose =
|
||||||
|
dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
CustomTextFieldWrapper(
|
CustomTextFieldWrapper(
|
||||||
@ -432,51 +439,62 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
contentPadding: EdgeInsets.symmetric(
|
contentPadding: EdgeInsets.symmetric(
|
||||||
horizontal: 10), // Proper padding
|
horizontal: 10,
|
||||||
|
), // Proper padding
|
||||||
),
|
),
|
||||||
onChanged: purposeList.isNotEmpty
|
onChanged:
|
||||||
|
purposeList.isNotEmpty
|
||||||
? (newValue) {
|
? (newValue) {
|
||||||
setState(() {
|
setState(() {
|
||||||
selectedPurpose = newValue;
|
selectedPurpose = newValue;
|
||||||
});
|
});
|
||||||
print("Updating form data: Flight -> trip_type -> ${newValue ?? ""}");
|
print(
|
||||||
|
"Updating form data: Flight -> trip_type -> ${newValue ?? ""}",
|
||||||
|
);
|
||||||
|
|
||||||
|
widget.updateFormData(
|
||||||
widget.updateFormData("Flight", "trip_type", newValue ?? "");
|
"Flight",
|
||||||
|
"trip_type",
|
||||||
|
newValue ?? "",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
|
|
||||||
items: dropdownItems,
|
items: dropdownItems,
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Widget> _builClassType(bool isDesktop) {
|
||||||
List<Widget> _builClassType(bool isDesktop){
|
|
||||||
|
|
||||||
List<dynamic> purposeList = widget.apiData?['flight_class'] ?? [];
|
List<dynamic> purposeList = widget.apiData?['flight_class'] ?? [];
|
||||||
|
|
||||||
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
List<DropdownMenuItem<String>> dropdownItems =
|
||||||
.map((item)=>DropdownMenuItem<String>(
|
purposeList
|
||||||
|
.map(
|
||||||
|
(item) => DropdownMenuItem<String>(
|
||||||
value: item['dropdown_value'],
|
value: item['dropdown_value'],
|
||||||
child: Text(item['dropdown_value']),
|
child: Text(item['dropdown_value']),
|
||||||
)).toList();
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
if (dropdownItems.isEmpty) {
|
if (dropdownItems.isEmpty) {
|
||||||
dropdownItems.add(
|
dropdownItems.add(
|
||||||
DropdownMenuItem<String>(
|
DropdownMenuItem<String>(
|
||||||
value: null,
|
value: null,
|
||||||
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
child: Text(
|
||||||
|
"No options available",
|
||||||
|
style: TextStyle(color: Colors.grey),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default selected value
|
// Default selected value
|
||||||
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
String? selectedPurpose =
|
||||||
|
dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
Column(
|
Column(
|
||||||
@ -487,7 +505,8 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Color(0xFF575A74)),
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
CustomTextFieldWrapper(
|
CustomTextFieldWrapper(
|
||||||
@ -504,9 +523,11 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
contentPadding: EdgeInsets.symmetric(
|
contentPadding: EdgeInsets.symmetric(
|
||||||
horizontal: 10), // Proper padding
|
horizontal: 10,
|
||||||
|
), // Proper padding
|
||||||
),
|
),
|
||||||
onChanged: purposeList.isNotEmpty
|
onChanged:
|
||||||
|
purposeList.isNotEmpty
|
||||||
? (newValue) {
|
? (newValue) {
|
||||||
setState(() {
|
setState(() {
|
||||||
selectedPurpose = newValue;
|
selectedPurpose = newValue;
|
||||||
@ -535,10 +556,8 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
List<Widget> _buildSecondRow(bool isDesktop) {
|
List<Widget> _buildSecondRow(bool isDesktop) {
|
||||||
return [
|
return [
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -547,15 +566,20 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Color(0xFF575A74)),
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
CustomTextFieldItnerarySubWrapper(
|
CustomTextFieldItnerarySubWrapper(
|
||||||
isFocused: _fromFocus,
|
isFocused: _fromFocus,
|
||||||
|
|
||||||
isDesktop: isDesktop,
|
isDesktop: isDesktop,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 40,
|
height: 40,
|
||||||
child: TextField(
|
child: TextField(
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9_-]')),
|
||||||
|
],
|
||||||
focusNode: _fromFocusNode,
|
focusNode: _fromFocusNode,
|
||||||
controller: _fromController,
|
controller: _fromController,
|
||||||
style: const TextStyle(fontSize: 12),
|
style: const TextStyle(fontSize: 12),
|
||||||
@ -565,19 +589,13 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (isDesktop)
|
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||||||
Spacer()
|
|
||||||
else
|
|
||||||
SizedBox(
|
|
||||||
height: 8,
|
|
||||||
),
|
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -586,7 +604,8 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Color(0xFF575A74)),
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
CustomTextFieldItnerarySubWrapper(
|
CustomTextFieldItnerarySubWrapper(
|
||||||
@ -611,12 +630,7 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (isDesktop)
|
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||||||
Spacer()
|
|
||||||
else
|
|
||||||
SizedBox(
|
|
||||||
height: 8,
|
|
||||||
),
|
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -625,7 +639,8 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Color(0xFF575A74)),
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
CustomTextFieldItnerarySubWrapper(
|
CustomTextFieldItnerarySubWrapper(
|
||||||
@ -643,19 +658,13 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (isDesktop)
|
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||||||
Spacer()
|
|
||||||
else
|
|
||||||
SizedBox(
|
|
||||||
height: 8,
|
|
||||||
),
|
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -664,13 +673,14 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Color(0xFF575A74)),
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
CustomTextFieldItnerarySubWrapper(
|
CustomTextFieldItnerarySubWrapper(
|
||||||
isFocused: _toFocus,
|
isFocused: _toFocus,
|
||||||
isDesktop: isDesktop,
|
isDesktop: isDesktop,
|
||||||
color:Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 40,
|
height: 40,
|
||||||
|
|
||||||
@ -680,7 +690,11 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
"Rs ",
|
"Rs ",
|
||||||
// focusNode: _toFocusNode,
|
// focusNode: _toFocusNode,
|
||||||
// controller: _toController,
|
// controller: _toController,
|
||||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600,color: Color(0xFF575A74)),
|
style: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
// decoration: const InputDecoration(
|
// decoration: const InputDecoration(
|
||||||
// labelText: "To",
|
// labelText: "To",
|
||||||
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
@ -694,34 +708,38 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildCardDetailsRow(bool isDesktop) {
|
List<Widget> _buildCardDetailsRow(bool isDesktop) {
|
||||||
|
|
||||||
List<dynamic> purposeList = widget.apiData?['flight_class'] ?? [];
|
List<dynamic> purposeList = widget.apiData?['flight_class'] ?? [];
|
||||||
|
|
||||||
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
List<DropdownMenuItem<String>> dropdownItems =
|
||||||
.map((item)=>DropdownMenuItem<String>(
|
purposeList
|
||||||
|
.map(
|
||||||
|
(item) => DropdownMenuItem<String>(
|
||||||
value: item['dropdown_value'],
|
value: item['dropdown_value'],
|
||||||
child: Text(item['dropdown_value']),
|
child: Text(item['dropdown_value']),
|
||||||
)).toList();
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
if (dropdownItems.isEmpty) {
|
if (dropdownItems.isEmpty) {
|
||||||
dropdownItems.add(
|
dropdownItems.add(
|
||||||
DropdownMenuItem<String>(
|
DropdownMenuItem<String>(
|
||||||
value: null,
|
value: null,
|
||||||
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
child: Text(
|
||||||
|
"No options available",
|
||||||
|
style: TextStyle(color: Colors.grey),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default selected value
|
// Default selected value
|
||||||
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
String? selectedPurpose =
|
||||||
|
dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||||
return [
|
return [
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -730,7 +748,8 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Color(0xFF575A74)),
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
CustomTextFieldItnerarySubWrapper(
|
CustomTextFieldItnerarySubWrapper(
|
||||||
@ -748,7 +767,6 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -756,12 +774,7 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
if (isDesktop)
|
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||||||
Spacer()
|
|
||||||
else
|
|
||||||
SizedBox(
|
|
||||||
height: 8,
|
|
||||||
),
|
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -770,7 +783,8 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Color(0xFF575A74)),
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
CustomTextFieldItnerarySubWrapper(
|
CustomTextFieldItnerarySubWrapper(
|
||||||
@ -787,9 +801,11 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
contentPadding: EdgeInsets.symmetric(
|
contentPadding: EdgeInsets.symmetric(
|
||||||
horizontal: 10), // Proper padding
|
horizontal: 10,
|
||||||
|
), // Proper padding
|
||||||
),
|
),
|
||||||
onChanged: purposeList.isNotEmpty
|
onChanged:
|
||||||
|
purposeList.isNotEmpty
|
||||||
? (newValue) {
|
? (newValue) {
|
||||||
setState(() {
|
setState(() {
|
||||||
selectedPurpose = newValue;
|
selectedPurpose = newValue;
|
||||||
@ -802,12 +818,7 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (isDesktop)
|
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||||||
Spacer()
|
|
||||||
else
|
|
||||||
SizedBox(
|
|
||||||
height: 8,
|
|
||||||
),
|
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -816,7 +827,8 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Color(0xFF575A74)),
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
CustomTextFieldItnerarySubWrapper(
|
CustomTextFieldItnerarySubWrapper(
|
||||||
@ -841,12 +853,7 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (isDesktop)
|
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||||||
Spacer()
|
|
||||||
else
|
|
||||||
SizedBox(
|
|
||||||
height: 8,
|
|
||||||
),
|
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -855,7 +862,8 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Color(0xFF575A74)),
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
CustomTextFieldItnerarySubWrapper(
|
CustomTextFieldItnerarySubWrapper(
|
||||||
@ -873,16 +881,12 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||||
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -896,13 +900,15 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Color(0xFF575A74)),
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 5),
|
SizedBox(height: 5),
|
||||||
CustomTextFieldWrapper(
|
CustomTextFieldWrapper(
|
||||||
isFocused: _commentsFocus, // Dropdown doesn't use focus
|
isFocused: _commentsFocus, // Dropdown doesn't use focus
|
||||||
isDesktop: isDesktop,
|
isDesktop: isDesktop,
|
||||||
width: isDesktop
|
width:
|
||||||
|
isDesktop
|
||||||
? MediaQuery.of(context).size.width * 0.4
|
? MediaQuery.of(context).size.width * 0.4
|
||||||
: MediaQuery.of(context).size.width * 0.66,
|
: MediaQuery.of(context).size.width * 0.66,
|
||||||
child: TextField(
|
child: TextField(
|
||||||
@ -921,7 +927,7 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -944,10 +950,14 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
"Check If You Don't Have a forex Account",
|
"Check If You Don't Have a forex Account",
|
||||||
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74)),
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Color(0xFF575A74),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -960,9 +970,7 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.grey[400], // Light grey color
|
backgroundColor: Colors.grey[400], // Light grey color
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
@ -971,7 +979,6 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 10), // Space between buttons
|
SizedBox(width: 10), // Space between buttons
|
||||||
|
|
||||||
// Save Changes Button
|
// Save Changes Button
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
@ -979,9 +986,7 @@ class _ForexScreenState extends State<ForexScreen> {
|
|||||||
},
|
},
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Colors.blue, // Primary color for save
|
backgroundColor: Colors.blue, // Primary color for save
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|||||||
@ -1,93 +1,713 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
class AccomodationListWidget extends StatelessWidget {
|
import '../../utils/auth_utils.dart';
|
||||||
|
|
||||||
final List<Map<String,dynamic>> accommodationList;
|
class AccomodationListWidget extends StatefulWidget {
|
||||||
|
final List<Map<String, dynamic>> accommodationList;
|
||||||
final Function(bool, Map<String, dynamic>, String) onOpen;
|
final Function(bool, Map<String, dynamic>, String) onOpen;
|
||||||
final Function(Map<String, dynamic>) onDeleteAccommodation;
|
final Function(Map<String, dynamic>) onDeleteAccommodation;
|
||||||
|
final Function(String, bool) onAddNew;
|
||||||
|
final bool isViewMode;
|
||||||
|
|
||||||
const AccomodationListWidget({super.key, required this.accommodationList, required this.onOpen, required this.onDeleteAccommodation});
|
const AccomodationListWidget({
|
||||||
|
super.key,
|
||||||
|
required this.accommodationList,
|
||||||
|
required this.onOpen,
|
||||||
|
required this.onDeleteAccommodation,
|
||||||
|
required this.onAddNew,
|
||||||
|
required this.isViewMode,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AccomodationListWidget> createState() => _AccomodationListWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AccomodationListWidgetState extends State<AccomodationListWidget> {
|
||||||
|
Color? layoutColor;
|
||||||
|
Color? secondColor;
|
||||||
|
Color? thridColor;
|
||||||
|
|
||||||
|
bool orgHasService = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
loadInitialData();
|
||||||
|
getOrgServices();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadInitialData() async {
|
||||||
|
String? layoutString = await getLayoutColor();
|
||||||
|
String? secondString = await getSecondaryColor();
|
||||||
|
String? thridString = await getTernaryColor();
|
||||||
|
|
||||||
|
// String? bodyStringColor = await getBodyColor();
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
layoutColor =
|
||||||
|
layoutString != null
|
||||||
|
? Color(int.parse(layoutString))
|
||||||
|
: Colors.redAccent;
|
||||||
|
|
||||||
|
secondColor =
|
||||||
|
layoutString != null
|
||||||
|
? Color(int.parse(secondString!))
|
||||||
|
: Colors.orange;
|
||||||
|
|
||||||
|
thridColor =
|
||||||
|
layoutString != null
|
||||||
|
? Color(int.parse(thridString!))
|
||||||
|
: Colors.orangeAccent;
|
||||||
|
// bodyColor =
|
||||||
|
// bodyStringColor != null
|
||||||
|
// ? Color(int.parse(bodyStringColor))
|
||||||
|
// : Colors.white;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void getOrgServices() async {
|
||||||
|
final services = await getOrgServicesName();
|
||||||
|
print("getOrgServices storage: $services");
|
||||||
|
|
||||||
|
// just names
|
||||||
|
final names = services.map((s) => s['name']).toList();
|
||||||
|
print("Names only: $names");
|
||||||
|
final hasFlight = names.contains("Accomodation");
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
orgHasService = hasFlight;
|
||||||
|
});
|
||||||
|
|
||||||
|
print("orgHasService: $orgHasService");
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return
|
final isDesktop = MediaQuery.of(context).size.width > 1024;
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
return Padding(
|
||||||
|
padding:
|
||||||
|
isDesktop ? const EdgeInsets.all(16.0) : const EdgeInsets.all(5.0),
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.only(top: 16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
|
// Header with title and button
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
if ((!isDesktop) && widget.accommodationList.isNotEmpty)
|
||||||
Text(
|
Text(
|
||||||
"Accomodation Booking List",
|
" ",
|
||||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
style: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (widget.accommodationList.isNotEmpty && orgHasService)
|
||||||
|
MouseRegion(
|
||||||
|
cursor:
|
||||||
|
widget.isViewMode
|
||||||
|
? SystemMouseCursors.forbidden
|
||||||
|
: SystemMouseCursors.click,
|
||||||
|
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap:
|
||||||
|
widget.isViewMode
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
print("New data");
|
||||||
|
widget.onAddNew("Accomodation", true);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize:
|
||||||
|
MainAxisSize.min, // Ensures content fits nicely
|
||||||
|
children: [
|
||||||
|
// Text(
|
||||||
|
// "Add New",
|
||||||
|
// style: TextStyle(fontSize: 13),
|
||||||
|
// ),
|
||||||
|
// SizedBox(width: 8), // spacing between icon and text
|
||||||
|
Icon(
|
||||||
|
Icons.add_circle_sharp,
|
||||||
|
size: 30,
|
||||||
|
color: Color(0xFF114D8B),
|
||||||
|
),
|
||||||
|
// Container(
|
||||||
|
// decoration: BoxDecoration(
|
||||||
|
// shape: BoxShape.circle,
|
||||||
|
// border: Border.all(
|
||||||
|
// color: Color(0xFF114D8B), // Outline color
|
||||||
|
// width: 2, // Outline thickness
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// height: 30,
|
||||||
|
// width: 30,
|
||||||
|
// child: Center(
|
||||||
|
// child: Icon(
|
||||||
|
// Icons.add,
|
||||||
|
// size: 20,
|
||||||
|
// color: Colors.black,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// onPressed: isViewMode
|
||||||
|
// ? null
|
||||||
|
// : () {
|
||||||
|
// print("New data");
|
||||||
|
//
|
||||||
|
// },
|
||||||
|
// child: Row(
|
||||||
|
// mainAxisSize: MainAxisSize.min,
|
||||||
|
// children: [
|
||||||
|
// Icon(
|
||||||
|
// Icons.add_circle_sharp,
|
||||||
|
// size: 30,
|
||||||
|
// color: Color(0xFF114D8B),
|
||||||
|
// )
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
),
|
||||||
|
// MouseRegion(
|
||||||
|
// cursor: isViewMode
|
||||||
|
// ? SystemMouseCursors.forbidden
|
||||||
|
// : SystemMouseCursors.click,
|
||||||
|
// child: ElevatedButton(
|
||||||
|
// style: ElevatedButton.styleFrom(
|
||||||
|
// backgroundColor: Color(0xFF114D8B),
|
||||||
|
// foregroundColor: Colors.white,
|
||||||
|
// disabledBackgroundColor: Color(0xFF114D8B),
|
||||||
|
// disabledForegroundColor: Colors.white,
|
||||||
|
// shape: RoundedRectangleBorder(
|
||||||
|
// borderRadius: BorderRadius.circular(8),
|
||||||
|
// side: BorderSide(color: Color(0xFF114D8B), width: 2),
|
||||||
|
// ),
|
||||||
|
// padding:
|
||||||
|
// EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
// ),
|
||||||
|
// onPressed: isViewMode
|
||||||
|
// ? null
|
||||||
|
// : () {
|
||||||
|
// print("New data");
|
||||||
|
// onAddNew("Accomodation", true);
|
||||||
|
// },
|
||||||
|
// child: Row(
|
||||||
|
// mainAxisSize:
|
||||||
|
// MainAxisSize.min, // Ensures content fits nicely
|
||||||
|
// children: [
|
||||||
|
// // spacing between icon and text
|
||||||
|
// Icon(
|
||||||
|
// Icons.add_circle_sharp,
|
||||||
|
// size: 30,
|
||||||
|
// color: Color(0xFF114D8B),
|
||||||
|
// )
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
SingleChildScrollView(
|
|
||||||
scrollDirection: Axis.horizontal,
|
// Scroll behavior based on device
|
||||||
child: SizedBox(
|
_buildData(context, isDesktop),
|
||||||
width: MediaQuery.of(context).size.width ,
|
|
||||||
child: DataTable(
|
|
||||||
border: TableBorder(
|
|
||||||
bottom: BorderSide(color: Colors.black12),
|
|
||||||
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
|
|
||||||
),
|
|
||||||
columns: const [
|
|
||||||
DataColumn(label: Text('#')),
|
|
||||||
DataColumn(label: Text('Destination City')),
|
|
||||||
DataColumn(label: Text('Hotel Name')),
|
|
||||||
DataColumn(label: Text('CheckIn Date')),
|
|
||||||
DataColumn(label: Text('CheckOut Date')),
|
|
||||||
DataColumn(label: Text('Actions')),
|
|
||||||
],
|
],
|
||||||
rows: _buildDataRows(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<DataRow> _buildDataRows() {
|
Widget _buildData(BuildContext context, bool isDesktop) {
|
||||||
|
List<Map<String, dynamic>> filteredList =
|
||||||
List<Map<String, dynamic>> filteredList = accommodationList
|
widget.accommodationList
|
||||||
.where((item) => item["is_active"] == "1")
|
.where((item) => item["is_active"] == "1")
|
||||||
.toList();
|
.toList();
|
||||||
print("filteredList- $filteredList");
|
print("filteredList- $filteredList");
|
||||||
|
|
||||||
return filteredList.asMap() .entries.map((entry) {
|
// String formatDate(String dateString) {
|
||||||
|
// try {
|
||||||
|
// DateTime date = DateTime.parse(dateString);
|
||||||
|
// return DateFormat('d MMM yy').format(date); // Example: 4 Apr 25
|
||||||
|
// } catch (e) {
|
||||||
|
// return "Invalid Date";
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
final Map<String, dynamic> item = entry.value;
|
String formatDate(String dateString) {
|
||||||
|
try {
|
||||||
|
DateTime date = DateFormat("dd-MM-yyyy").parse(dateString);
|
||||||
|
return DateFormat('dd MMM yy').format(date); // Example: 23 May 25
|
||||||
|
} catch (e) {
|
||||||
|
return "Invalid Date";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return DataRow(cells: [
|
String formatTime(String timeString) {
|
||||||
|
try {
|
||||||
|
final parts = timeString.split(':');
|
||||||
|
int hour = int.parse(parts[0]);
|
||||||
|
int minute = int.parse(parts[1]);
|
||||||
|
|
||||||
DataCell(Text(item["indx"]?.toString() ?? "N/A")), // Index column
|
final now = DateTime.now();
|
||||||
DataCell(Text(item["destination_city"]!)),
|
|
||||||
DataCell(Text(item["hotel_name"]!)),
|
DateTime dateTime;
|
||||||
DataCell(Text(item["checkin_date"]!)),
|
|
||||||
DataCell(Text(item["checkout_date"]!)),
|
if (hour == 24 && minute == 0) {
|
||||||
DataCell(Row(
|
// 24:00 is treated as 00:00 on the next day
|
||||||
|
dateTime = DateTime(
|
||||||
|
now.year,
|
||||||
|
now.month,
|
||||||
|
now.day,
|
||||||
|
).add(const Duration(days: 1));
|
||||||
|
} else {
|
||||||
|
if (hour < 0 || hour > 23 || minute < 0 || minute > 59) {
|
||||||
|
throw FormatException("Invalid hour or minute");
|
||||||
|
}
|
||||||
|
dateTime = DateTime(now.year, now.month, now.day, hour, minute);
|
||||||
|
}
|
||||||
|
|
||||||
|
return DateFormat('HH:mm').format(dateTime); // 24-hour format
|
||||||
|
} catch (e) {
|
||||||
|
return "Invalid Time";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String formatTime(String timeString) {
|
||||||
|
// try {
|
||||||
|
// final parts = timeString.split(':');
|
||||||
|
// final now = DateTime.now();
|
||||||
|
// final dateTime = DateTime(
|
||||||
|
// now.year,
|
||||||
|
// now.month,
|
||||||
|
// now.day,
|
||||||
|
// int.parse(parts[0]),
|
||||||
|
// int.parse(parts[1]),
|
||||||
|
// );
|
||||||
|
// return DateFormat.jm().format(dateTime); // e.g., 12:16 PM or 12 AM
|
||||||
|
// } catch (e) {
|
||||||
|
// return "Invalid Time";
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: filteredList.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = filteredList[index];
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
// color: Colors.orange.shade50,
|
||||||
|
color: isDesktop ? Colors.white : thridColor,
|
||||||
|
// color: Colors.white,
|
||||||
|
boxShadow:
|
||||||
|
isDesktop
|
||||||
|
? [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black12,
|
||||||
|
blurRadius: 3,
|
||||||
|
offset: Offset(0, 1),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: [],
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(
|
||||||
|
color: Colors.white70, // Change color to match your theme
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: isDesktop ? EdgeInsets.all(12) : EdgeInsets.all(8),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
Row(
|
||||||
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
|
// mainAxisAlignment: MainAxisAlignment.end,
|
||||||
onPressed: () {
|
children: [
|
||||||
// View action
|
Expanded(
|
||||||
},
|
flex: 1,
|
||||||
|
child: Text(
|
||||||
|
item["hotel_name"]!,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight:
|
||||||
|
isDesktop ? FontWeight.w800 : FontWeight.w600,
|
||||||
|
color: !isDesktop ? Colors.black : Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
if (orgHasService) ...[
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => widget.onOpen(true, item, "Accomodation"),
|
||||||
|
child: Tooltip(
|
||||||
|
message: 'Edit Accomodation Details',
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/IconsImg/edit.png',
|
||||||
|
width: 20,
|
||||||
|
height: 15,
|
||||||
|
color:
|
||||||
|
isDesktop ? Color(0xFF114D8B) : Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => widget.onDeleteAccommodation(item),
|
||||||
|
child: Tooltip(
|
||||||
|
message: 'Delete Accommodation Details',
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/IconsImg/delete.png',
|
||||||
|
width: 20,
|
||||||
|
height: 15,
|
||||||
|
color: Colors.red,
|
||||||
),
|
),
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.edit, color: Colors.green),
|
|
||||||
onPressed: () {
|
|
||||||
onOpen(true, item, "Accomodation");
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.delete, color: Colors.red),
|
|
||||||
onPressed: () {
|
|
||||||
onDeleteAccommodation(item);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)),
|
],
|
||||||
]);
|
),
|
||||||
}).toList();
|
// Divider(color: Colors.blueGrey.shade50),
|
||||||
|
SizedBox(height: 4),
|
||||||
|
isDesktop
|
||||||
|
? Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Text(
|
||||||
|
" DestinationCity",
|
||||||
|
style: GoogleFonts.poppins(fontSize: 11),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Text(
|
||||||
|
" Check (in) ",
|
||||||
|
style: GoogleFonts.poppins(fontSize: 11),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Text(
|
||||||
|
" Check (out) ",
|
||||||
|
style: GoogleFonts.poppins(fontSize: 11),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Text(
|
||||||
|
" Comments",
|
||||||
|
style: GoogleFonts.poppins(fontSize: 11),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: SizedBox.shrink(),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
isDesktop
|
||||||
|
? Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Text(
|
||||||
|
item["destination_city"],
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Text(
|
||||||
|
"${formatDate(item["checkin_date"]!)} ${formatTime(item["checkin_time"]!)}",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Text(
|
||||||
|
"${formatDate(item["checkout_date"]!)} ${formatTime(item["checkout_time"]!)}",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: _buildComments(
|
||||||
|
"Comments:",
|
||||||
|
item["comments"] ?? "N/A",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: Container(
|
||||||
|
color: Colors.white,
|
||||||
|
padding: EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildRow(
|
||||||
|
"Destination City",
|
||||||
|
item["destination_city"],
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
_buildRow(
|
||||||
|
"Check (in)",
|
||||||
|
"${formatDate(item["checkin_date"]!)} ${formatTime(item["checkin_time"]!)}",
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
_buildRow(
|
||||||
|
"Check (out)",
|
||||||
|
"${formatDate(item["checkout_date"]!)} ${formatTime(item["checkout_time"]!)}",
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
_buildRow("Comments:", item["comments"] ?? "N/A"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Column(
|
||||||
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
// children: [
|
||||||
|
// Wrap(
|
||||||
|
// spacing: 20, // horizontal space between items
|
||||||
|
// runSpacing: 10, // vertical space between rows
|
||||||
|
// children: [
|
||||||
|
// _buildInfoColumn("City", item["destination_city"]),
|
||||||
|
// _buildInfoColumn("Check (In)", "${formatDate(item["checkin_date"]!)} ${formatTime(item["checkin_time"]!)}"),
|
||||||
|
// _buildInfoColumn("Check (Out)", "${formatDate(item["checkout_date"]!)} ${formatTime(item["checkout_time"]!)}"),
|
||||||
|
// // City - long name handled with flexible space
|
||||||
|
// // Expanded(
|
||||||
|
// // flex: 3,
|
||||||
|
// // child: _buildInfoColumn("City", item["destination_city"]),
|
||||||
|
// // ),
|
||||||
|
//
|
||||||
|
// // SizedBox(width: 8),
|
||||||
|
//
|
||||||
|
// // Check In and Check Out stacked right side
|
||||||
|
// // Expanded(
|
||||||
|
// // flex: 4,
|
||||||
|
// // child: Column(
|
||||||
|
// // crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
// // children: [
|
||||||
|
// // _buildInfoColumn(
|
||||||
|
// // "Check In",
|
||||||
|
// // "${formatDate(item["checkin_date"]!)} ${formatTime(item["checkin_time"]!)}",
|
||||||
|
// // ),
|
||||||
|
// // SizedBox(height: 6),
|
||||||
|
// // _buildInfoColumn(
|
||||||
|
// // "Check Out",
|
||||||
|
// // "${formatDate(item["checkout_date"]!)} ${formatTime(item["checkout_time"]!)}",
|
||||||
|
// // ),
|
||||||
|
// // ],
|
||||||
|
// // ),
|
||||||
|
// // ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// const SizedBox(height: 10),
|
||||||
|
// const Text(
|
||||||
|
// "Comment",
|
||||||
|
// style: TextStyle(fontWeight: FontWeight.w500),
|
||||||
|
// ),
|
||||||
|
// const SizedBox(height: 5),
|
||||||
|
// if (item["comments"] != null && item["comments"].toString().trim().isNotEmpty)
|
||||||
|
// Container(
|
||||||
|
// width: double.infinity,
|
||||||
|
// padding: const EdgeInsets.all(8),
|
||||||
|
// decoration: BoxDecoration(
|
||||||
|
// border: Border.all(color: Color(0xFF7098DD), width: 1),
|
||||||
|
// borderRadius: BorderRadius.circular(6),
|
||||||
|
// ),
|
||||||
|
// child: Text(
|
||||||
|
// item["comments"],
|
||||||
|
// style: const TextStyle(fontSize: 12),
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
// else
|
||||||
|
// const Text( "N/A", style: TextStyle(fontSize: 12), ),
|
||||||
|
// ],
|
||||||
|
// )
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildComments(String title, String value) {
|
||||||
|
// Define character limits based on title
|
||||||
|
Map<String, int> limits = {
|
||||||
|
// "Special Request:": 30,
|
||||||
|
"Comments:": 20,
|
||||||
|
// Add more keys and limits if needed
|
||||||
|
};
|
||||||
|
|
||||||
|
int limit = limits[title] ?? 50; // Default limit if title not found
|
||||||
|
bool exceedsLimit = value.length > limit;
|
||||||
|
|
||||||
|
String wrapText(String text, int maxLineLength) {
|
||||||
|
final pattern = RegExp('.{1,$maxLineLength}(\\s+|\$)');
|
||||||
|
return pattern.allMatches(text).map((m) => m.group(0)!).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child:
|
||||||
|
exceedsLimit
|
||||||
|
? Tooltip(
|
||||||
|
message: wrapText(value, 50),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade200, // Black background
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
textStyle: TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
), // Tooltip text color
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
preferBelow: false,
|
||||||
|
child: Text(
|
||||||
|
value.substring(0, limit) + "...",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Text(
|
||||||
|
value,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper widget
|
||||||
|
Widget _buildInfoColumn(String label, String value) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black87),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildRow(String title, String value) {
|
||||||
|
// Define character limits based on title
|
||||||
|
Map<String, int> limits = {
|
||||||
|
// "Special Request:": 30,
|
||||||
|
"Comments:": 10,
|
||||||
|
// Add more keys and limits if needed
|
||||||
|
};
|
||||||
|
|
||||||
|
int limit = limits[title] ?? 50; // Default limit if title not found
|
||||||
|
bool exceedsLimit = value.length > limit;
|
||||||
|
|
||||||
|
String wrapText(String text, int maxLineLength) {
|
||||||
|
final pattern = RegExp('.{1,$maxLineLength}(\\s+|\$)');
|
||||||
|
return pattern.allMatches(text).map((m) => m.group(0)!).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
// color: Colors.yellowAccent.shade100,
|
||||||
|
width: 100,
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 5),
|
||||||
|
// Expanded(child: Text(value, style: TextStyle(fontSize: 12))),
|
||||||
|
Expanded(
|
||||||
|
child:
|
||||||
|
exceedsLimit
|
||||||
|
? Tooltip(
|
||||||
|
message: wrapText(value, 50),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade200, // Black background
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
textStyle: TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
), // Tooltip text color
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
preferBelow: false,
|
||||||
|
child: Text(
|
||||||
|
value.substring(0, limit) + "...",
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Text(value, style: TextStyle(fontSize: 12)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDateTimeRow(String title, String date, String time) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 100,
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 5),
|
||||||
|
Expanded(
|
||||||
|
child: Text("$date, $time", style: GoogleFonts.poppins(fontSize: 12)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,111 +1,667 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
import 'package:responsive_builder/responsive_builder.dart';
|
import 'package:responsive_builder/responsive_builder.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
import '../../config/apiUrl.dart';
|
import '../../config/apiUrl.dart';
|
||||||
import '../../data/models/plan.dart';
|
import '../../data/models/plan.dart';
|
||||||
|
import '../../utils/auth_utils.dart';
|
||||||
|
|
||||||
|
class BusListWidget extends StatefulWidget {
|
||||||
class BusListWidget extends StatelessWidget{
|
|
||||||
final List<Map<String, dynamic>> busList;
|
final List<Map<String, dynamic>> busList;
|
||||||
final Function(bool, Map<String, dynamic>, String) onOpen;
|
final Function(bool, Map<String, dynamic>, String) onOpen;
|
||||||
final Function(Map<String,dynamic>) onDeleteBus;
|
final Function(Map<String, dynamic>) onDeleteBus;
|
||||||
const BusListWidget({super.key, required this.busList, required this.onOpen, required this.onDeleteBus});
|
|
||||||
|
|
||||||
|
final Function(String, bool) onAddNew;
|
||||||
|
final bool isViewMode;
|
||||||
|
|
||||||
|
const BusListWidget({
|
||||||
|
super.key,
|
||||||
|
required this.busList,
|
||||||
|
required this.onOpen,
|
||||||
|
required this.onDeleteBus,
|
||||||
|
required this.onAddNew,
|
||||||
|
required this.isViewMode,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BusListWidget> createState() => _BusListWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BusListWidgetState extends State<BusListWidget> {
|
||||||
|
Color? layoutColor;
|
||||||
|
Color? secondColor;
|
||||||
|
Color? thridColor;
|
||||||
|
bool orgHasService = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
loadInitialData();
|
||||||
|
getOrgServices();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadInitialData() async {
|
||||||
|
String? layoutString = await getLayoutColor();
|
||||||
|
String? secondString = await getSecondaryColor();
|
||||||
|
String? thridString = await getTernaryColor();
|
||||||
|
|
||||||
|
// String? bodyStringColor = await getBodyColor();
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
layoutColor =
|
||||||
|
layoutString != null
|
||||||
|
? Color(int.parse(layoutString))
|
||||||
|
: Colors.redAccent;
|
||||||
|
|
||||||
|
secondColor =
|
||||||
|
layoutString != null
|
||||||
|
? Color(int.parse(secondString!))
|
||||||
|
: Colors.orange;
|
||||||
|
|
||||||
|
thridColor =
|
||||||
|
layoutString != null
|
||||||
|
? Color(int.parse(thridString!))
|
||||||
|
: Colors.orangeAccent;
|
||||||
|
// bodyColor =
|
||||||
|
// bodyStringColor != null
|
||||||
|
// ? Color(int.parse(bodyStringColor))
|
||||||
|
// : Colors.white;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void getOrgServices() async {
|
||||||
|
final services = await getOrgServicesName();
|
||||||
|
print("getOrgServices storage: $services");
|
||||||
|
|
||||||
|
// just names
|
||||||
|
final names = services.map((s) => s['name']).toList();
|
||||||
|
print("Names only: $names");
|
||||||
|
final hasFlight = names.contains("Bus");
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
orgHasService = hasFlight;
|
||||||
|
});
|
||||||
|
|
||||||
|
print("orgHasService: $orgHasService");
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final isDesktop = MediaQuery.of(context).size.width > 1024;
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding:
|
||||||
|
isDesktop ? const EdgeInsets.all(16.0) : const EdgeInsets.all(5.0),
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.only(top: 16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
|
// Header with title and button
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
if ((!isDesktop) && widget.busList.isNotEmpty)
|
||||||
Text(
|
Text(
|
||||||
"Bus Booking List",
|
" ",
|
||||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
style: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (widget.busList.isNotEmpty && orgHasService)
|
||||||
|
MouseRegion(
|
||||||
|
cursor:
|
||||||
|
widget.isViewMode
|
||||||
|
? SystemMouseCursors.forbidden
|
||||||
|
: SystemMouseCursors.click,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap:
|
||||||
|
widget.isViewMode
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
print("New data");
|
||||||
|
widget.onAddNew("Bus", true);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize:
|
||||||
|
MainAxisSize.min, // Ensures content fits nicely
|
||||||
|
children: [
|
||||||
|
// Text(
|
||||||
|
// "Add New",
|
||||||
|
// style: TextStyle(fontSize: 13),
|
||||||
|
// ),
|
||||||
|
// SizedBox(width: 8), // spacing between icon and text
|
||||||
|
Icon(
|
||||||
|
Icons.add_circle_sharp,
|
||||||
|
size: 30,
|
||||||
|
color: Color(0xFF114D8B),
|
||||||
|
),
|
||||||
|
// Container(
|
||||||
|
// decoration: BoxDecoration(
|
||||||
|
// shape: BoxShape.circle,
|
||||||
|
// border: Border.all(
|
||||||
|
// color: Color(0xFF114D8B), // Outline color
|
||||||
|
// width: 2, // Outline thickness
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// height: 30,
|
||||||
|
// width: 30,
|
||||||
|
// child: Center(
|
||||||
|
// child: Icon(
|
||||||
|
// Icons.add,
|
||||||
|
// size: 20,
|
||||||
|
// color: Colors.black,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// child: ElevatedButton(
|
||||||
|
// style: ElevatedButton.styleFrom(
|
||||||
|
// backgroundColor: Color(0xFF114D8B),
|
||||||
|
// foregroundColor: Colors.white,
|
||||||
|
// disabledBackgroundColor: Color(0xFF114D8B),
|
||||||
|
// disabledForegroundColor: Colors.white,
|
||||||
|
// shape: RoundedRectangleBorder(
|
||||||
|
// borderRadius: BorderRadius.circular(8),
|
||||||
|
// side: BorderSide(color: Color(0xFF114D8B), width: 2),
|
||||||
|
// ),
|
||||||
|
// padding:
|
||||||
|
// EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||||
|
// ),
|
||||||
|
// onPressed: isViewMode
|
||||||
|
// ? null
|
||||||
|
// : () {
|
||||||
|
// print("New data");
|
||||||
|
// onAddNew("Bus", true);
|
||||||
|
// },
|
||||||
|
// child: Row(
|
||||||
|
// mainAxisSize:
|
||||||
|
// MainAxisSize.min, // Ensures content fits nicely
|
||||||
|
// children: [
|
||||||
|
// Icon(
|
||||||
|
// Icons.add_circle_sharp,
|
||||||
|
// size: 30,
|
||||||
|
// color: Color(0xFF114D8B),
|
||||||
|
// )
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Container(
|
|
||||||
// color: Colors.blueGrey,
|
|
||||||
width: double.infinity,
|
|
||||||
child: Center(
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
child: Expanded(
|
|
||||||
child: SizedBox(
|
|
||||||
width: MediaQuery.of(context).size.width ,
|
|
||||||
child: DataTable(
|
|
||||||
border: TableBorder(
|
|
||||||
bottom: BorderSide(color: Colors.black12),
|
|
||||||
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
|
|
||||||
),
|
|
||||||
columns: const [
|
|
||||||
|
|
||||||
// DataColumn(label: Text('#')),
|
// Scroll behavior based on device
|
||||||
DataColumn(label: Text('From')),
|
_buildData(context, isDesktop),
|
||||||
DataColumn(label: Text('To')),
|
|
||||||
DataColumn(label: Text('Date')),
|
|
||||||
DataColumn(label: Text('Time')),
|
|
||||||
|
|
||||||
DataColumn(label: Text('Actions')),
|
|
||||||
],
|
],
|
||||||
rows: _buildDataRows(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<DataRow> _buildDataRows() {
|
Widget _buildData(BuildContext context, bool isDesktop) {
|
||||||
|
List<Map<String, dynamic>> filteredList =
|
||||||
List<Map<String, dynamic>> filteredList = busList
|
widget.busList.where((item) => item["is_active"] == "1").toList();
|
||||||
.where((item) => item["is_active"] == "1")
|
|
||||||
.toList();
|
|
||||||
print("filteredList- $filteredList");
|
print("filteredList- $filteredList");
|
||||||
|
|
||||||
|
// String formatDate(String dateString) {
|
||||||
|
// try {
|
||||||
|
// DateTime date = DateTime.parse(dateString);
|
||||||
|
// return DateFormat('d MMM yy').format(date); // Example: 4 Apr 25
|
||||||
|
// } catch (e) {
|
||||||
|
// return "Invalid Date";
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// String formatTime(String timeString) {
|
||||||
|
// try {
|
||||||
|
// final parts = timeString.split(':');
|
||||||
|
// final now = DateTime.now();
|
||||||
|
// final dateTime = DateTime(
|
||||||
|
// now.year,
|
||||||
|
// now.month,
|
||||||
|
// now.day,
|
||||||
|
// int.parse(parts[0]),
|
||||||
|
// int.parse(parts[1]),
|
||||||
|
// );
|
||||||
|
// return DateFormat.jm().format(dateTime); // e.g., 12:16 PM or 12 AM
|
||||||
|
// } catch (e) {
|
||||||
|
// return "Invalid Time";
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
return filteredList.asMap().entries.map((entry) {
|
String formatDate(String dateString) {
|
||||||
|
try {
|
||||||
|
DateTime date = DateFormat("dd-MM-yyyy").parse(dateString);
|
||||||
|
return DateFormat('dd MMM yy').format(date); // Example: 23 May 25
|
||||||
|
} catch (e) {
|
||||||
|
return "Invalid Date";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final Map<String, dynamic> item = entry.value;
|
String formatTime(String timeString) {
|
||||||
|
try {
|
||||||
|
final parts = timeString.split(':');
|
||||||
|
int hour = int.parse(parts[0]);
|
||||||
|
int minute = int.parse(parts[1]);
|
||||||
|
|
||||||
return DataRow(cells: [
|
final now = DateTime.now();
|
||||||
// DataCell(Text(item["indx"]?.toString() ?? "N/A")), // Index column
|
|
||||||
DataCell(Text(item["from"]!)),
|
DateTime dateTime;
|
||||||
DataCell(Text(item["to"]!)),
|
|
||||||
DataCell(Text(item["date"]!)),
|
if (hour == 24 && minute == 0) {
|
||||||
DataCell(Text(item["time"]!)),
|
// 24:00 is treated as 00:00 on the next day
|
||||||
DataCell(Row(
|
dateTime = DateTime(
|
||||||
|
now.year,
|
||||||
|
now.month,
|
||||||
|
now.day,
|
||||||
|
).add(const Duration(days: 1));
|
||||||
|
} else {
|
||||||
|
if (hour < 0 || hour > 23 || minute < 0 || minute > 59) {
|
||||||
|
throw FormatException("Invalid hour or minute");
|
||||||
|
}
|
||||||
|
dateTime = DateTime(now.year, now.month, now.day, hour, minute);
|
||||||
|
}
|
||||||
|
|
||||||
|
return DateFormat('HH:mm').format(dateTime); // 24-hour format
|
||||||
|
} catch (e) {
|
||||||
|
return "Invalid Time";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: filteredList.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = filteredList[index];
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
// color: Colors.orange.shade50,
|
||||||
|
color: isDesktop ? Colors.white : thridColor,
|
||||||
|
boxShadow:
|
||||||
|
isDesktop
|
||||||
|
? [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black12,
|
||||||
|
blurRadius: 3,
|
||||||
|
offset: Offset(0, 1),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: [],
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(
|
||||||
|
color: Colors.white70, // Change color to match your theme
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: isDesktop ? EdgeInsets.all(12) : EdgeInsets.all(8),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
Row(
|
||||||
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
onPressed: () {
|
children: [
|
||||||
// View action
|
Text(
|
||||||
},
|
"${item["from"]} - ${item["to"]}",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
color:
|
||||||
|
!isDesktop ? Color(0xFF114D8B) : Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
|
||||||
|
if (widget.busList.isNotEmpty && orgHasService) ...[
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => widget.onOpen(true, item, "Bus"),
|
||||||
|
child: Tooltip(
|
||||||
|
message: 'Edit Bus Details',
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/IconsImg/edit.png',
|
||||||
|
width: 20,
|
||||||
|
height: 15,
|
||||||
|
// color: Color(0xFF114D8B),
|
||||||
|
color:
|
||||||
|
!isDesktop ? Colors.black : Color(0xFF575A74),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => widget.onDeleteBus(item),
|
||||||
|
child: Tooltip(
|
||||||
|
message: 'Delete Bus Details',
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/IconsImg/delete.png',
|
||||||
|
width: 20,
|
||||||
|
height: 15,
|
||||||
|
color: Colors.red,
|
||||||
),
|
),
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.edit, color: Colors.green),
|
|
||||||
onPressed: () {
|
|
||||||
onOpen(true, item, "Bus");
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.delete, color: Colors.red),
|
|
||||||
onPressed: () {
|
|
||||||
onDeleteBus(item);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)),
|
],
|
||||||
]);
|
),
|
||||||
}).toList();
|
// Divider(color: Colors.blueGrey.shade50),
|
||||||
|
SizedBox(height: 4),
|
||||||
|
isDesktop
|
||||||
|
? Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: Text(
|
||||||
|
"Planned Trips",
|
||||||
|
style: GoogleFonts.poppins(fontSize: 11),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: Text(
|
||||||
|
" Date",
|
||||||
|
style: GoogleFonts.poppins(fontSize: 11),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: Text(
|
||||||
|
" Comments",
|
||||||
|
style: GoogleFonts.poppins(fontSize: 11),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: SizedBox.shrink(),
|
||||||
|
SizedBox(height: 8),
|
||||||
|
isDesktop
|
||||||
|
? Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: Text(
|
||||||
|
"${item["from"]} - ${item["to"]} ",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: Text(
|
||||||
|
"${formatDate(item["date"]!)} ${formatTime(item["time"]!)}",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: _buildComments(
|
||||||
|
"Comments:",
|
||||||
|
item["comments"] ?? "N/A",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: Container(
|
||||||
|
color: Colors.white,
|
||||||
|
padding: const EdgeInsets.all(4.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildRow(
|
||||||
|
"PlannedTrips",
|
||||||
|
'${item["from"]!} - ${item["to"]!}',
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
// _buildRow("To", item["to"]!),
|
||||||
|
// SizedBox(width: 10),
|
||||||
|
_buildDateTimeRow(
|
||||||
|
"Date",
|
||||||
|
formatDate(item["date"]!),
|
||||||
|
formatTime(item["time"]!),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5),
|
||||||
|
_buildRow("Comments", item["comments"] ?? "N/A"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Column(
|
||||||
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
// children: [
|
||||||
|
// // Top Row (Country, Currency, Total)
|
||||||
|
// Wrap(
|
||||||
|
// spacing: 20, // horizontal space between items
|
||||||
|
// runSpacing: 10, // vertical space between rows
|
||||||
|
// // Row(
|
||||||
|
// // mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
// children: [
|
||||||
|
// _infoBlock("From", item["from"] ?? "N/A"),
|
||||||
|
// _infoBlock("To", item["to"]! ?? "N/A"),
|
||||||
|
// _infoBlock(
|
||||||
|
// "Date",
|
||||||
|
// (item["date"] != null && item["time"] != null)
|
||||||
|
// ? "${formatDate(item["date"]!)} ${formatTime(item["time"]!)}"
|
||||||
|
// : "N/A",
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// const SizedBox(height: 16),
|
||||||
|
//
|
||||||
|
// // Comment label
|
||||||
|
// Text(
|
||||||
|
// "Comment",
|
||||||
|
// style: GoogleFonts.poppins(
|
||||||
|
// fontSize: 14,
|
||||||
|
// fontWeight: FontWeight.w600,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// const SizedBox(height: 8),
|
||||||
|
// if (item["comments"] != null &&
|
||||||
|
// item["comments"].toString().trim().isNotEmpty)
|
||||||
|
// Container(
|
||||||
|
// width: double.infinity,
|
||||||
|
// padding: const EdgeInsets.all(12),
|
||||||
|
// decoration: BoxDecoration(
|
||||||
|
// border: Border.all(
|
||||||
|
// color: const Color(0xFF7098DD),
|
||||||
|
// width: 1,
|
||||||
|
// ),
|
||||||
|
// borderRadius: BorderRadius.circular(6),
|
||||||
|
// ),
|
||||||
|
// child: Text(
|
||||||
|
// item["comments"] ?? "N/A",
|
||||||
|
// style: GoogleFonts.poppins(
|
||||||
|
// fontSize: 12,
|
||||||
|
// color: Colors.black87,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
// else
|
||||||
|
// const Text("N/A", style: TextStyle(fontSize: 12)),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _infoBlock(String label, String value) {
|
||||||
|
return Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 12),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black87),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildComments(String title, String value) {
|
||||||
|
// Define character limits based on title
|
||||||
|
Map<String, int> limits = {
|
||||||
|
// "Special Request:": 30,
|
||||||
|
"Comments:": 30,
|
||||||
|
// Add more keys and limits if needed
|
||||||
|
};
|
||||||
|
|
||||||
|
int limit = limits[title] ?? 50; // Default limit if title not found
|
||||||
|
bool exceedsLimit = value.length > limit;
|
||||||
|
|
||||||
|
String wrapText(String text, int maxLineLength) {
|
||||||
|
final pattern = RegExp('.{1,$maxLineLength}(\\s+|\$)');
|
||||||
|
return pattern.allMatches(text).map((m) => m.group(0)!).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child:
|
||||||
|
exceedsLimit
|
||||||
|
? Tooltip(
|
||||||
|
message: wrapText(value, 50),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade200, // Black background
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
textStyle: TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
), // Tooltip text color
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
preferBelow: false,
|
||||||
|
child: Text(
|
||||||
|
value.substring(0, limit) + "...",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Text(
|
||||||
|
value,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildRow(String title, String value) {
|
||||||
|
// Define character limits based on title
|
||||||
|
Map<String, int> limits = {
|
||||||
|
// "Special Request:": 30,
|
||||||
|
"Comments:": 10,
|
||||||
|
// Add more keys and limits if needed
|
||||||
|
};
|
||||||
|
|
||||||
|
int limit = limits[title] ?? 50; // Default limit if title not found
|
||||||
|
bool exceedsLimit = value.length > limit;
|
||||||
|
|
||||||
|
String wrapText(String text, int maxLineLength) {
|
||||||
|
final pattern = RegExp('.{1,$maxLineLength}(\\s+|\$)');
|
||||||
|
return pattern.allMatches(text).map((m) => m.group(0)!).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 100,
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child:
|
||||||
|
exceedsLimit
|
||||||
|
? Tooltip(
|
||||||
|
message: wrapText(value, 50),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade200, // Black background
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
textStyle: TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
), // Tooltip text color
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
preferBelow: false,
|
||||||
|
child: Text(
|
||||||
|
value.substring(0, limit) + "...",
|
||||||
|
style: TextStyle(fontSize: 12),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Text(value, style: GoogleFonts.poppins(fontSize: 12)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDateTimeRow(String title, String date, String time) {
|
||||||
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 100,
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Text("$date, $time", style: GoogleFonts.poppins(fontSize: 12)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,94 +1,914 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
class FlightListWidget extends StatelessWidget {
|
import '../../services/apiService.dart';
|
||||||
final List<Map<String,dynamic>> flightList;
|
import '../../utils/auth_utils.dart';
|
||||||
final Function( bool,Map<String,dynamic>, String) onOpen;
|
|
||||||
final Function(Map<String,dynamic>) onDeleteFlight;
|
|
||||||
|
|
||||||
const FlightListWidget({super.key, required this.flightList, required this.onOpen, required this.onDeleteFlight});
|
class FlightListWidget extends StatefulWidget {
|
||||||
|
final bool hasAction;
|
||||||
|
final String? tripType;
|
||||||
|
final List<Map<String, dynamic>> flightList;
|
||||||
|
final Function(bool, Map<String, dynamic>, String) onOpen;
|
||||||
|
final Function(Map<String, dynamic>) onDeleteFlight;
|
||||||
|
final Map<String, dynamic>? apiData;
|
||||||
|
final Function(String, bool) onAddNew;
|
||||||
|
final bool isViewMode;
|
||||||
|
|
||||||
|
const FlightListWidget({
|
||||||
|
Key? key,
|
||||||
|
required this.flightList,
|
||||||
|
required this.onOpen,
|
||||||
|
required this.onDeleteFlight,
|
||||||
|
required this.onAddNew,
|
||||||
|
required this.apiData,
|
||||||
|
required this.isViewMode,
|
||||||
|
required this.hasAction,
|
||||||
|
this.tripType,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_FlightListWidgetState createState() => _FlightListWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FlightListWidgetState extends State<FlightListWidget> {
|
||||||
|
ApiService apiService = ApiService();
|
||||||
|
Color? layoutColor;
|
||||||
|
Color? secondColor;
|
||||||
|
Color? thridColor;
|
||||||
|
|
||||||
|
bool orgHasService = false;
|
||||||
|
|
||||||
|
// late Map<String, String> countryMap;
|
||||||
|
Map<String, String> countryMap = {};
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
loadCountryList(); // Call your method here
|
||||||
|
loadInitialData();
|
||||||
|
getOrgServices();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadInitialData() async {
|
||||||
|
String? layoutString = await getLayoutColor();
|
||||||
|
String? secondString = await getSecondaryColor();
|
||||||
|
String? thridString = await getTernaryColor();
|
||||||
|
|
||||||
|
// String? bodyStringColor = await getBodyColor();
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
layoutColor =
|
||||||
|
layoutString != null
|
||||||
|
? Color(int.parse(layoutString))
|
||||||
|
: Colors.redAccent;
|
||||||
|
|
||||||
|
secondColor =
|
||||||
|
layoutString != null
|
||||||
|
? Color(int.parse(secondString!))
|
||||||
|
: Colors.orange;
|
||||||
|
|
||||||
|
thridColor =
|
||||||
|
layoutString != null
|
||||||
|
? Color(int.parse(thridString!))
|
||||||
|
: Colors.orangeAccent;
|
||||||
|
// bodyColor =
|
||||||
|
// bodyStringColor != null
|
||||||
|
// ? Color(int.parse(bodyStringColor))
|
||||||
|
// : Colors.white;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void getOrgServices() async {
|
||||||
|
final services = await getOrgServicesName();
|
||||||
|
print("getOrgServices storage: $services");
|
||||||
|
|
||||||
|
// just names
|
||||||
|
final names = services.map((s) => s['name']).toList();
|
||||||
|
print("Names only: $names");
|
||||||
|
final hasFlight = names.contains("Flight");
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
orgHasService = hasFlight;
|
||||||
|
});
|
||||||
|
|
||||||
|
print("orgHasService: $orgHasService");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadCountryList() async {
|
||||||
|
final result = await apiService.fetchFlightsCountryList(
|
||||||
|
context,
|
||||||
|
widget.tripType,
|
||||||
|
);
|
||||||
|
|
||||||
|
print("ResultCountry : $result");
|
||||||
|
|
||||||
|
// Create a map: Country_Code -> "City, Airport"
|
||||||
|
Map<String, String> tempCountryMap = {};
|
||||||
|
|
||||||
|
for (var country in result) {
|
||||||
|
// String city = country['City'] ?? '';
|
||||||
|
// String airport = country['Airport'] ?? '';
|
||||||
|
// String displayName = '${country['City']} - ${country['Airport']}';
|
||||||
|
// // String displayName = '${country['City']} | ${country['Airport']}';
|
||||||
|
|
||||||
|
final code = country['Code'] ?? '';
|
||||||
|
final city = country['City'] ?? '';
|
||||||
|
final airport = country['Airport'] ?? '';
|
||||||
|
final displayName = '$city ($code)\n$airport';
|
||||||
|
|
||||||
|
tempCountryMap[country['Code']] = displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
countryMap = tempCountryMap; // Update the map
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final isDesktop = MediaQuery.of(context).size.width > 1024;
|
||||||
|
|
||||||
|
ApiService apiService = ApiService();
|
||||||
|
|
||||||
|
late Map<String, String> countryMap;
|
||||||
|
late List<String> countryCodes;
|
||||||
|
|
||||||
|
void checkClass() {
|
||||||
|
if (widget.hasAction) {
|
||||||
|
if (widget.tripType?.isNotEmpty == true) {
|
||||||
|
print("Teppp - $widget.tripType");
|
||||||
|
widget.onAddNew("Flight", true);
|
||||||
|
} else {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('Select Trip Type'),
|
||||||
|
content: const Text(
|
||||||
|
'Please select a trip type before adding a flight.',
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('OK'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("Teppp No - $widget.tripType");
|
||||||
|
widget.onAddNew("Flight", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// return Padding(
|
||||||
|
// padding: const EdgeInsets.all(16.0),
|
||||||
|
// child: Container(
|
||||||
|
// margin: const EdgeInsets.only(top: 16.0),
|
||||||
|
// child: Column(
|
||||||
|
// crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
// children: [
|
||||||
|
// // Header with title and button
|
||||||
|
// Row(
|
||||||
|
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
// children: [
|
||||||
|
// Text(
|
||||||
|
// "Flight",
|
||||||
|
// style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
// ),
|
||||||
|
// widget.flightList.isNotEmpty
|
||||||
|
// ? MouseRegion(
|
||||||
|
// cursor:
|
||||||
|
// widget.isViewMode
|
||||||
|
// ? SystemMouseCursors.forbidden
|
||||||
|
// : SystemMouseCursors.click,
|
||||||
|
// child: GestureDetector(
|
||||||
|
// onTap:
|
||||||
|
// widget.isViewMode
|
||||||
|
// ? null
|
||||||
|
// : () {
|
||||||
|
// widget.onAddNew("Flight", true);
|
||||||
|
// // checkClass();
|
||||||
|
// print("New data");
|
||||||
|
// },
|
||||||
|
// child: Row(
|
||||||
|
// mainAxisSize:
|
||||||
|
// MainAxisSize.min, // Ensures content fits nicely
|
||||||
|
// children: [
|
||||||
|
// // Text(
|
||||||
|
// // "Add New",
|
||||||
|
// // style: TextStyle(fontSize: 13),
|
||||||
|
// // ),
|
||||||
|
// // SizedBox(width: 8), // spacing between icon and text
|
||||||
|
// Icon(
|
||||||
|
// Icons.add_circle_sharp,
|
||||||
|
// size: 30,
|
||||||
|
// color: Color(0xFF114D8B),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
// : SizedBox.shrink(),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// const SizedBox(height: 16),
|
||||||
|
//
|
||||||
|
// // Scroll behavior based on device
|
||||||
|
// _buildData(context, isDesktop),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding:
|
||||||
|
isDesktop ? const EdgeInsets.all(16.0) : const EdgeInsets.all(5.0),
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.only(top: 16.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
|
// Row: Title (mobile only) + Add Button (conditional)
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
// Title (optional on desktop or mobile)
|
||||||
|
if ((!isDesktop) && widget.flightList.isNotEmpty)
|
||||||
Text(
|
Text(
|
||||||
"Flight Booking List",
|
" ",
|
||||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
style: const TextStyle(
|
||||||
),
|
fontSize: 18,
|
||||||
const SizedBox(height: 16),
|
fontWeight: FontWeight.bold,
|
||||||
|
|
||||||
SingleChildScrollView(
|
|
||||||
scrollDirection: Axis.horizontal,
|
|
||||||
child: SizedBox(
|
|
||||||
// width: 1000,
|
|
||||||
width: MediaQuery.of(context).size.width ,
|
|
||||||
child: DataTable(
|
|
||||||
border: TableBorder(
|
|
||||||
bottom: BorderSide(color: Colors.black12),
|
|
||||||
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
|
|
||||||
),
|
|
||||||
columns: const [
|
|
||||||
// DataColumn(label: Text('#')),
|
|
||||||
DataColumn(label: Text('Trip Type')),
|
|
||||||
DataColumn(label: Text('From')),
|
|
||||||
DataColumn(label: Text('To')),
|
|
||||||
DataColumn(label: Text('Actions')),
|
|
||||||
],
|
|
||||||
rows: _buildDataRows(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// Right side: either "Add" icon or nothing
|
||||||
|
if (widget.flightList.isNotEmpty && orgHasService)
|
||||||
|
MouseRegion(
|
||||||
|
cursor:
|
||||||
|
widget.isViewMode
|
||||||
|
? SystemMouseCursors.forbidden
|
||||||
|
: SystemMouseCursors.click,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap:
|
||||||
|
widget.isViewMode
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
widget.onAddNew("Flight", true);
|
||||||
|
print("New data");
|
||||||
|
},
|
||||||
|
child: Icon(
|
||||||
|
Icons.add_circle_sharp,
|
||||||
|
size: 30,
|
||||||
|
color: const Color(0xFF114D8B),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else if (!isDesktop)
|
||||||
|
SizedBox.shrink(),
|
||||||
|
// Centered "No Data Found" message for mobile and empty list
|
||||||
|
// Expanded(
|
||||||
|
// child: Center(
|
||||||
|
// child: Text(
|
||||||
|
// "No Data Found",
|
||||||
|
// textAlign: TextAlign.center,
|
||||||
|
// style: GoogleFonts.poppins(
|
||||||
|
// fontSize: 18,
|
||||||
|
// fontWeight: FontWeight.w500,
|
||||||
|
// color: Colors.grey,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
isDesktop ? SizedBox(height: 16) : SizedBox(height: 5),
|
||||||
|
|
||||||
|
// Actual flight data UI
|
||||||
|
_buildData(context, isDesktop),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<DataRow> _buildDataRows() {
|
Widget _buildData(BuildContext context, bool isDesktop) {
|
||||||
|
List<Map<String, dynamic>> filteredList =
|
||||||
List<Map<String, dynamic>> filteredList = flightList
|
widget.flightList.where((item) => item["is_active"] == "1").toList();
|
||||||
.where((item) => item["is_active"] == "1")
|
|
||||||
.toList();
|
|
||||||
print("filteredList- $filteredList");
|
print("filteredList- $filteredList");
|
||||||
|
|
||||||
|
List<dynamic> visatypeList = widget.apiData?['flight_class'] ?? [];
|
||||||
|
|
||||||
return filteredList.asMap().entries.map((entry) {
|
String getRequestForClass(String? specialRequestKey) {
|
||||||
Map<String,dynamic> item = entry.value;
|
if (specialRequestKey == null) return "N/A";
|
||||||
print("Trip Type: ${item["trip_type"]}");
|
|
||||||
|
|
||||||
|
return visatypeList
|
||||||
|
.firstWhere(
|
||||||
|
(element) =>
|
||||||
|
element["dropdown_key"].toString() == specialRequestKey,
|
||||||
|
orElse: () => {"dropdown_value": "N/A"},
|
||||||
|
)["dropdown_value"]
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
return DataRow(cells: [
|
// String formatDate(String dateString) {
|
||||||
// DataCell(Text(item["indx"]?.toString() ?? "N/A")),
|
// try {
|
||||||
DataCell(Text(item["trip_type"]?.toString() ?? "N/A")),
|
// DateTime date = DateTime.parse(dateString);
|
||||||
DataCell(Text(item["trips"].isNotEmpty ? item["trips"][0]["from_place"]?.toString() ?? "N/A" : "N/A")),
|
// return DateFormat('d MMM yy').format(date); // Example: 4 Apr 25
|
||||||
DataCell(Text(item["trips"].isNotEmpty ? item["trips"][0]["to_place"]?.toString() ?? "N/A" : "N/A")),
|
// } catch (e) {
|
||||||
|
// return "Invalid Date";
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// String formatTime(String timeString) {
|
||||||
|
// try {
|
||||||
|
// final parts = timeString.split(':');
|
||||||
|
// final now = DateTime.now();
|
||||||
|
// final dateTime = DateTime(
|
||||||
|
// now.year,
|
||||||
|
// now.month,
|
||||||
|
// now.day,
|
||||||
|
// int.parse(parts[0]),
|
||||||
|
// int.parse(parts[1]),
|
||||||
|
// );
|
||||||
|
// return DateFormat.jm().format(dateTime); // e.g., 12:16 PM or 12 AM
|
||||||
|
// } catch (e) {
|
||||||
|
// return "Invalid Time";
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
DataCell(Row(
|
String formatDate(String dateString) {
|
||||||
|
try {
|
||||||
|
DateTime date = DateFormat("dd-MM-yyyy").parse(dateString);
|
||||||
|
return DateFormat('dd MMM yy').format(date); // Example: 23 May 25
|
||||||
|
} catch (e) {
|
||||||
|
return "Invalid Date";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String formatTime(String timeString) {
|
||||||
|
try {
|
||||||
|
final parts = timeString.split(':');
|
||||||
|
int hour = int.parse(parts[0]);
|
||||||
|
int minute = int.parse(parts[1]);
|
||||||
|
|
||||||
|
final now = DateTime.now();
|
||||||
|
|
||||||
|
DateTime dateTime;
|
||||||
|
|
||||||
|
if (hour == 24 && minute == 0) {
|
||||||
|
// 24:00 is treated as 00:00 on the next day
|
||||||
|
dateTime = DateTime(
|
||||||
|
now.year,
|
||||||
|
now.month,
|
||||||
|
now.day,
|
||||||
|
).add(const Duration(days: 1));
|
||||||
|
} else {
|
||||||
|
if (hour < 0 || hour > 23 || minute < 0 || minute > 59) {
|
||||||
|
throw FormatException("Invalid hour or minute");
|
||||||
|
}
|
||||||
|
dateTime = DateTime(now.year, now.month, now.day, hour, minute);
|
||||||
|
}
|
||||||
|
|
||||||
|
return DateFormat('HH:mm').format(dateTime); // 24-hour format
|
||||||
|
} catch (e) {
|
||||||
|
return "Invalid Time";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: filteredList.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = filteredList[index];
|
||||||
|
String? tripTypeName = '';
|
||||||
|
switch (item["trip_type"]?.toString()) {
|
||||||
|
case "Roundtrip":
|
||||||
|
tripTypeName = "Round Trip";
|
||||||
|
break;
|
||||||
|
case "Multitrip":
|
||||||
|
tripTypeName = "Multi-Trip";
|
||||||
|
break;
|
||||||
|
case "Oneway":
|
||||||
|
tripTypeName = "One-Way";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
tripTypeName = item["trip_type"]?.toString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return Container(
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
// color: isDesktop ? Colors.white : Color(0xFFFFF0F5),
|
||||||
|
color: isDesktop ? Colors.white : thridColor,
|
||||||
|
// color: Colors.orange.shade50,
|
||||||
|
// color: Colors.white,
|
||||||
|
boxShadow:
|
||||||
|
isDesktop
|
||||||
|
? [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black12,
|
||||||
|
blurRadius: 2,
|
||||||
|
offset: Offset(0, 2), // down
|
||||||
|
),
|
||||||
|
// Top shadow
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black12,
|
||||||
|
blurRadius: 0.5,
|
||||||
|
offset: Offset(0, -1), // up
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: [],
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(
|
||||||
|
color: Colors.white70, // Change color to match your theme
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: isDesktop ? EdgeInsets.all(12) : EdgeInsets.all(5),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
Padding(
|
||||||
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
|
padding: const EdgeInsets.symmetric(horizontal: 2),
|
||||||
onPressed: () {
|
child: Row(
|
||||||
// View action
|
children: [
|
||||||
},
|
Text(
|
||||||
|
tripTypeName ?? "N/A",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight:
|
||||||
|
isDesktop ? FontWeight.w800 : FontWeight.w600,
|
||||||
|
color: !isDesktop ? Colors.black : Color(0xFF575A74),
|
||||||
|
|
||||||
|
// Color(0xFF114D8B)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
|
||||||
|
if (orgHasService) ...[
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => widget.onOpen(true, item, "Flight"),
|
||||||
|
child: Tooltip(
|
||||||
|
message: 'Edit Flight Details',
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/IconsImg/edit.png',
|
||||||
|
width: 20,
|
||||||
|
height: 15,
|
||||||
|
color:
|
||||||
|
isDesktop
|
||||||
|
? Color(0xFF114D8B)
|
||||||
|
: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => widget.onDeleteFlight(item),
|
||||||
|
child: Tooltip(
|
||||||
|
message: 'Delete Flight Details',
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/IconsImg/delete.png',
|
||||||
|
width: 20,
|
||||||
|
height: 15,
|
||||||
|
color: Colors.red,
|
||||||
),
|
),
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.edit, color: Colors.green),
|
|
||||||
onPressed: () {
|
|
||||||
onOpen(true, item, "Flight");
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.delete, color: Colors.red),
|
|
||||||
onPressed: () {
|
|
||||||
onDeleteFlight(item);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)),
|
],
|
||||||
]);
|
),
|
||||||
}).toList();
|
),
|
||||||
|
// Divider(color: Colors.blueGrey.shade50),
|
||||||
|
SizedBox(height: 4),
|
||||||
|
if (isDesktop)
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Text(
|
||||||
|
"Class",
|
||||||
|
style: GoogleFonts.poppins(fontSize: 11),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 5,
|
||||||
|
child: Text(
|
||||||
|
"Sector",
|
||||||
|
style: GoogleFonts.poppins(fontSize: 11),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Text(
|
||||||
|
"Date",
|
||||||
|
style: GoogleFonts.poppins(fontSize: 11),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Text(
|
||||||
|
"Time",
|
||||||
|
style: GoogleFonts.poppins(fontSize: 11),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: 4),
|
||||||
|
|
||||||
|
// Trip Rows
|
||||||
|
if (isDesktop)
|
||||||
|
if (item["trips"] != null && item["trips"].isNotEmpty)
|
||||||
|
...item["trips"].map<Widget>((trip) {
|
||||||
|
print("werewrewrTrip - $trip");
|
||||||
|
|
||||||
|
String fromPlaceCountry =
|
||||||
|
countryMap[trip["from_place"]?.toString()] ??
|
||||||
|
"Unknown Country";
|
||||||
|
|
||||||
|
final parts = fromPlaceCountry.split('\n');
|
||||||
|
final cityAndCode = parts[0];
|
||||||
|
final fromairport = parts.length > 1 ? parts[1] : '';
|
||||||
|
|
||||||
|
// Extract city and code from "City (CODE)"
|
||||||
|
final cityMatch = RegExp(
|
||||||
|
r'^(.*)\s+\(([^)]+)\)$',
|
||||||
|
).firstMatch(cityAndCode);
|
||||||
|
final fromcity = cityMatch?.group(1) ?? '';
|
||||||
|
final fromcode = cityMatch?.group(2) ?? '';
|
||||||
|
|
||||||
|
String toPlaceCountry =
|
||||||
|
countryMap[trip["to_place"]?.toString()] ??
|
||||||
|
"Unknown Country";
|
||||||
|
|
||||||
|
final toparts = toPlaceCountry.split('\n');
|
||||||
|
final tocityAndCode = toparts[0];
|
||||||
|
final toairport = toparts.length > 1 ? toparts[1] : '';
|
||||||
|
|
||||||
|
// Extract city and code from "City (CODE)"
|
||||||
|
final tocityMatch = RegExp(
|
||||||
|
r'^(.*)\s+\(([^)]+)\)$',
|
||||||
|
).firstMatch(tocityAndCode);
|
||||||
|
final tocity = tocityMatch?.group(1) ?? '';
|
||||||
|
final tocode = tocityMatch?.group(2) ?? '';
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Text(
|
||||||
|
getRequestForClass(trip["class"].toString()) ??
|
||||||
|
"N/A",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 4,
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"$fromcity ($fromcode)",
|
||||||
|
// "${trip["from_place"]?.toString()} - ${trip["to_place"]?.toString()}",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"$fromairport",
|
||||||
|
// "${trip["from_place"]?.toString()} - ${trip["to_place"]?.toString()}",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 10),
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/images/IconsImg/Frame.png',
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 1,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"$tocity ($tocode)",
|
||||||
|
// "${trip["from_place"]?.toString()} - ${trip["to_place"]?.toString()}",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"$toairport",
|
||||||
|
// "${trip["from_place"]?.toString()} - ${trip["to_place"]?.toString()}",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Spacer(),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Text(
|
||||||
|
formatDate(trip["date"] ?? ""),
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: Text(
|
||||||
|
formatTime(trip["time"] ?? ""),
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
|
||||||
|
if (!isDesktop &&
|
||||||
|
item["trips"] != null &&
|
||||||
|
item["trips"].isNotEmpty)
|
||||||
|
...item["trips"].map<Widget>((trip) {
|
||||||
|
String fromPlaceCountry =
|
||||||
|
countryMap[trip["from_place"]?.toString()] ??
|
||||||
|
"Unknown Country";
|
||||||
|
|
||||||
|
final parts = fromPlaceCountry.split('\n');
|
||||||
|
final cityAndCode = parts[0];
|
||||||
|
final fromairport = parts.length > 1 ? parts[1] : '';
|
||||||
|
|
||||||
|
// Extract city and code from "City (CODE)"
|
||||||
|
final cityMatch = RegExp(
|
||||||
|
r'^(.*)\s+\(([^)]+)\)$',
|
||||||
|
).firstMatch(cityAndCode);
|
||||||
|
final fromcity = cityMatch?.group(1) ?? '';
|
||||||
|
final fromcode = cityMatch?.group(2) ?? '';
|
||||||
|
|
||||||
|
String toPlaceCountry =
|
||||||
|
countryMap[trip["to_place"]?.toString()] ??
|
||||||
|
"Unknown Country";
|
||||||
|
|
||||||
|
final toparts = toPlaceCountry.split('\n');
|
||||||
|
final tocityAndCode = toparts[0];
|
||||||
|
final toairport = toparts.length > 1 ? toparts[1] : '';
|
||||||
|
|
||||||
|
// Extract city and code from "City (CODE)"
|
||||||
|
final tocityMatch = RegExp(
|
||||||
|
r'^(.*)\s+\(([^)]+)\)$',
|
||||||
|
).firstMatch(tocityAndCode);
|
||||||
|
final tocity = tocityMatch?.group(1) ?? '';
|
||||||
|
final tocode = tocityMatch?.group(2) ?? '';
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 6.0),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: Colors.grey.shade300),
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
color: Colors.grey.shade50,
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildKeyValueRow(
|
||||||
|
"Class",
|
||||||
|
getRequestForClass(trip["class"].toString()) ??
|
||||||
|
"N/A",
|
||||||
|
),
|
||||||
|
_buildKeyValueRow(
|
||||||
|
"From",
|
||||||
|
"$fromcity ($fromcode), $fromairport ",
|
||||||
|
),
|
||||||
|
_buildKeyValueRow(
|
||||||
|
"To",
|
||||||
|
"$tocity ($tocode), $toairport",
|
||||||
|
),
|
||||||
|
|
||||||
|
_buildKeyValueRow(
|
||||||
|
"Date",
|
||||||
|
formatDate(trip["date"] ?? ""),
|
||||||
|
),
|
||||||
|
_buildKeyValueRow(
|
||||||
|
"Time",
|
||||||
|
formatTime(trip["time"] ?? ""),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// return Padding(
|
||||||
|
// padding: const EdgeInsets.symmetric(vertical: 6.0),
|
||||||
|
// child: Container(
|
||||||
|
// // decoration: BoxDecoration(
|
||||||
|
// // border: Border.all(color: Colors.grey.shade300),
|
||||||
|
// // borderRadius: BorderRadius.circular(6),
|
||||||
|
// // color: Colors.grey.shade50,
|
||||||
|
// // ),
|
||||||
|
// // padding: const EdgeInsets.all(12),
|
||||||
|
// child: Row(
|
||||||
|
// children: [
|
||||||
|
// /// LEFT SIDE: Class and Airport Info
|
||||||
|
// Expanded(
|
||||||
|
// flex: 2,
|
||||||
|
// child: Row(
|
||||||
|
// children: [
|
||||||
|
// Text(
|
||||||
|
// '${getRequestForClass(trip["class"].toString()) ?? "N/A"} (',
|
||||||
|
// style: const TextStyle(
|
||||||
|
// fontWeight: FontWeight.bold,
|
||||||
|
// fontSize: 14,
|
||||||
|
// color: Colors.black87,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// Text(
|
||||||
|
// // fromairport,
|
||||||
|
// fromcode,
|
||||||
|
// style: const TextStyle(
|
||||||
|
// fontWeight: FontWeight.bold,
|
||||||
|
// fontSize: 14,
|
||||||
|
// color: Colors.black87,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// const Padding(
|
||||||
|
// padding: EdgeInsets.symmetric(
|
||||||
|
// horizontal: 6,
|
||||||
|
// ),
|
||||||
|
// child: Image(
|
||||||
|
// image: AssetImage(
|
||||||
|
// 'assets/images/IconsImg/Frame.png',
|
||||||
|
// ),
|
||||||
|
// width: 18,
|
||||||
|
// height: 18,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// Text(
|
||||||
|
// '$tocode)',
|
||||||
|
// // '$toairport)',
|
||||||
|
// style: const TextStyle(
|
||||||
|
// fontWeight: FontWeight.bold,
|
||||||
|
// fontSize: 14,
|
||||||
|
// color: Colors.black87,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
//
|
||||||
|
// /// RIGHT SIDE: Date and Time
|
||||||
|
// Expanded(
|
||||||
|
// flex: 1,
|
||||||
|
// child: Text(
|
||||||
|
// '${formatDate(trip["date"] ?? "")} ${formatTime(trip["time"] ?? "")}',
|
||||||
|
// textAlign: TextAlign.right,
|
||||||
|
// style: const TextStyle(
|
||||||
|
// fontSize: 13,
|
||||||
|
// color: Colors.black87,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
|
||||||
|
// return Padding(
|
||||||
|
// padding: const EdgeInsets.symmetric(vertical: 6.0),
|
||||||
|
// child: Container(
|
||||||
|
// child: Column(
|
||||||
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
// children: [
|
||||||
|
// Wrap(
|
||||||
|
// crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
|
// spacing: 4,
|
||||||
|
// runSpacing: 4,
|
||||||
|
// children: [
|
||||||
|
// Text(
|
||||||
|
// '${getRequestForClass(trip["class"].toString()) ?? "N/A"} (',
|
||||||
|
// style: const TextStyle(
|
||||||
|
// fontWeight: FontWeight.bold,
|
||||||
|
// fontSize: 14,
|
||||||
|
// color: Colors.black87,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// Text(
|
||||||
|
// fromcode,
|
||||||
|
// style: const TextStyle(
|
||||||
|
// fontWeight: FontWeight.bold,
|
||||||
|
// fontSize: 14,
|
||||||
|
// color: Colors.black87,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// const Padding(
|
||||||
|
// padding: EdgeInsets.symmetric(horizontal: 4),
|
||||||
|
// child: Image(
|
||||||
|
// image: AssetImage('assets/images/IconsImg/Frame.png'),
|
||||||
|
// width: 18,
|
||||||
|
// height: 18,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// Text(
|
||||||
|
// '$tocode)',
|
||||||
|
// style: const TextStyle(
|
||||||
|
// fontWeight: FontWeight.bold,
|
||||||
|
// fontSize: 14,
|
||||||
|
// color: Colors.black87,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// const SizedBox(height: 6),
|
||||||
|
// Align(
|
||||||
|
// alignment: Alignment.centerRight,
|
||||||
|
// child: Text(
|
||||||
|
// '${formatDate(trip["date"] ?? "")} ${formatTime(trip["time"] ?? "")}',
|
||||||
|
// textAlign: TextAlign.right,
|
||||||
|
// style: const TextStyle(
|
||||||
|
// fontSize: 13,
|
||||||
|
// color: Colors.black87,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
}).toList(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildKeyValueRow(String key, String value) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 4.0),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 70, // adjust width as needed
|
||||||
|
child: Text(
|
||||||
|
"$key",
|
||||||
|
style: GoogleFonts.poppins(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(value, style: GoogleFonts.poppins(fontSize: 12)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||