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">
|
||||
|
||||
|
||||
<application
|
||||
android:label="frontend"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
@ -25,11 +28,22 @@
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</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.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
|
||||
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
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
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
@ -15,4 +16,7 @@
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
<paths>
|
||||
<cache-path name="cache" path="." />
|
||||
</paths>
|
||||
</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.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
</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;
|
||||
buildSettings = {
|
||||
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_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
@ -484,7 +484,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
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_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
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" : "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"
|
||||
}
|
||||
}
|
||||
{"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"}}
|
||||
|
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,34 +4,31 @@ import 'package:http/http.dart' as http;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
|
||||
class LoginPage1 extends StatefulWidget {
|
||||
const LoginPage1({super.key});
|
||||
|
||||
@override
|
||||
_LoginPageState createState() => _LoginPageState();
|
||||
}
|
||||
}
|
||||
|
||||
class _LoginPageState extends State<LoginPage1> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: LoginWidget(),
|
||||
body: LoginWidget(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// StatefulWidget for Body Content
|
||||
class LoginWidget extends StatefulWidget{
|
||||
class LoginWidget extends StatefulWidget {
|
||||
const LoginWidget({super.key});
|
||||
|
||||
@override
|
||||
_LoginWidgetState createState() => _LoginWidgetState();
|
||||
}
|
||||
|
||||
class _LoginWidgetState extends State<LoginWidget>{
|
||||
|
||||
class _LoginWidgetState extends State<LoginWidget> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final TextEditingController _emailController = TextEditingController();
|
||||
final TextEditingController _passwordController = TextEditingController();
|
||||
@ -47,7 +44,8 @@ class _LoginWidgetState extends State<LoginWidget>{
|
||||
Uri.parse(url),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'},
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: jsonEncode({
|
||||
'email': _emailController.text.trim(),
|
||||
'password': _passwordController.text.trim(),
|
||||
@ -61,11 +59,12 @@ class _LoginWidgetState extends State<LoginWidget>{
|
||||
backgroundColor: Colors.green, // Set background to green
|
||||
),
|
||||
);
|
||||
context.go('/home'); // Login Success
|
||||
}
|
||||
else {
|
||||
context.go('/listPlan'); // Login Success
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text("Login Failed: ${jsonDecode(response.body)['message']}")),
|
||||
SnackBar(
|
||||
content: Text(
|
||||
"Login Failed: ${jsonDecode(response.body)['message']}")),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
@ -76,16 +75,13 @@ class _LoginWidgetState extends State<LoginWidget>{
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context){
|
||||
Widget build(BuildContext context) {
|
||||
double myHeight = MediaQuery.of(context).size.height;
|
||||
double myWidth = MediaQuery.of(context).size.width;
|
||||
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
@ -94,19 +90,17 @@ class _LoginWidgetState extends State<LoginWidget>{
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
|
||||
child: Center(
|
||||
child: Text(
|
||||
"Hello! , Welcome Back",
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.blueAccent
|
||||
),
|
||||
color: Colors.blueAccent),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: myHeight/10),
|
||||
SizedBox(height: myHeight / 10),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: const Text(
|
||||
@ -114,33 +108,32 @@ class _LoginWidgetState extends State<LoginWidget>{
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.blueAccent
|
||||
),
|
||||
color: Colors.blueAccent),
|
||||
),
|
||||
),
|
||||
SizedBox(height:8),
|
||||
SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: _emailController,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Enter the email',
|
||||
labelStyle: TextStyle(color: Colors.grey,),
|
||||
labelStyle: TextStyle(
|
||||
color: Colors.grey,
|
||||
),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||
filled: true,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12), // Rounded corners// Remove the default border
|
||||
borderSide: BorderSide.none
|
||||
),
|
||||
borderRadius: BorderRadius.circular(
|
||||
12), // Rounded corners// Remove the default border
|
||||
borderSide: BorderSide.none),
|
||||
),
|
||||
validator: (value){
|
||||
if(value == null || value.isEmpty){
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Required Email';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
|
||||
|
||||
const SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
@ -149,18 +142,19 @@ class _LoginWidgetState extends State<LoginWidget>{
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.blueAccent
|
||||
),
|
||||
color: Colors.blueAccent),
|
||||
),
|
||||
),
|
||||
SizedBox(height:8),
|
||||
SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller:_passwordController,
|
||||
controller: _passwordController,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
obscureText: _obscureText,
|
||||
decoration: InputDecoration(
|
||||
labelText: "Enter the password",
|
||||
labelStyle: TextStyle(color: Colors.grey,),
|
||||
labelStyle: TextStyle(
|
||||
color: Colors.grey,
|
||||
),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
@ -176,11 +170,9 @@ class _LoginWidgetState extends State<LoginWidget>{
|
||||
filled: true,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none
|
||||
)
|
||||
),
|
||||
validator: (value){
|
||||
if(value == null || value.isEmpty){
|
||||
borderSide: BorderSide.none)),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Required Password';
|
||||
}
|
||||
return null;
|
||||
@ -189,21 +181,18 @@ class _LoginWidgetState extends State<LoginWidget>{
|
||||
const SizedBox(height: 20),
|
||||
Center(
|
||||
child: ElevatedButton(
|
||||
onPressed: (){
|
||||
onPressed: () {
|
||||
_login(context);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blueAccent, // Change button color
|
||||
foregroundColor: Colors.white, // Text color
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24,vertical: 12),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24, vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return ResponsiveBuilder(
|
||||
builder: (context, sizingInfo) {
|
||||
return ResponsiveBuilder(builder: (context, sizingInfo) {
|
||||
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||
|
||||
return Scaffold(
|
||||
appBar: isDesktop ? null : const CustomAppBar(title:'Home'),
|
||||
appBar: CustomAppBar(isDesktop: isDesktop),
|
||||
drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
|
||||
body:
|
||||
Row(
|
||||
body: Row(
|
||||
children: [
|
||||
if(isDesktop)
|
||||
CustomDrawer(isDesktop: true),
|
||||
if (isDesktop) CustomDrawer(isDesktop: true),
|
||||
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/services.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:intl/intl.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';
|
||||
|
||||
class BusScreen extends StatefulWidget {
|
||||
|
||||
final Map<String, dynamic>? apiData;
|
||||
final Function(bool) onClose;
|
||||
final Function(Map<String, dynamic>)onSaveBus;
|
||||
final Map<String, dynamic>? selectedItem;
|
||||
final String? loginUser;
|
||||
final Function(String, bool) onClose;
|
||||
final Function(Map<String, dynamic>) onSaveBus;
|
||||
final Map<String, dynamic>? selectedItem;
|
||||
final String? loginUser;
|
||||
|
||||
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
|
||||
_BusScreenState createState() => _BusScreenState();
|
||||
@ -52,27 +58,26 @@ class _BusScreenState extends State<BusScreen> {
|
||||
bool _timeFocus = false;
|
||||
bool _commentsFocus = false;
|
||||
|
||||
|
||||
Map<String, dynamic> get busData{
|
||||
Map<String,dynamic> data = {
|
||||
"from": _fromController.text,
|
||||
"to": _toController.text,
|
||||
"date": _dateController.text,
|
||||
"time": _timeController.text,
|
||||
"comments": _buscommentsController.text,
|
||||
"created_by": widget.loginUser,
|
||||
"updated_by": widget.loginUser,
|
||||
Map<String, dynamic> get busData {
|
||||
Map<String, dynamic> data = {
|
||||
"from": _fromController.text,
|
||||
"to": _toController.text,
|
||||
"date": _dateController.text,
|
||||
"time": _timeController.text,
|
||||
"comments": _buscommentsController.text,
|
||||
"created_by": widget.loginUser,
|
||||
"updated_by": widget.loginUser,
|
||||
};
|
||||
|
||||
|
||||
if (widget.selectedItem != null) {
|
||||
if (widget.selectedItem?["indx"] != null && widget.selectedItem?["indx"] != 0) {
|
||||
data["indx"] = widget.selectedItem!["indx"];
|
||||
} else if (widget.selectedItem?["bus_id"] != null && widget.selectedItem?["bus_id"] != 0) {
|
||||
data["bus_id"] = widget.selectedItem!["bus_id"];
|
||||
}
|
||||
}
|
||||
|
||||
if (widget.selectedItem != null) {
|
||||
if (widget.selectedItem?["indx"] != null &&
|
||||
widget.selectedItem?["indx"] != 0) {
|
||||
data["indx"] = widget.selectedItem!["indx"];
|
||||
} else if (widget.selectedItem?["bus_id"] != null &&
|
||||
widget.selectedItem?["bus_id"] != 0) {
|
||||
data["bus_id"] = widget.selectedItem!["bus_id"];
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
@ -91,41 +96,39 @@ class _BusScreenState extends State<BusScreen> {
|
||||
});
|
||||
});
|
||||
|
||||
_hotelNameFocusNode.addListener(() {
|
||||
setState(() {
|
||||
_isHotelNameFocused = _hotelNameFocusNode.hasFocus;
|
||||
});
|
||||
_hotelNameFocusNode.addListener(() {
|
||||
setState(() {
|
||||
_isHotelNameFocused = _hotelNameFocusNode.hasFocus;
|
||||
});
|
||||
_fromFocusNode.addListener(() {
|
||||
setState(() {
|
||||
_fromFocus = _fromFocusNode.hasFocus;
|
||||
});
|
||||
});
|
||||
_fromFocusNode.addListener(() {
|
||||
setState(() {
|
||||
_fromFocus = _fromFocusNode.hasFocus;
|
||||
});
|
||||
});
|
||||
|
||||
_toFocusNode.addListener(() {
|
||||
setState(() {
|
||||
_toFocus = _toFocusNode.hasFocus;
|
||||
});
|
||||
_toFocusNode.addListener(() {
|
||||
setState(() {
|
||||
_toFocus = _toFocusNode.hasFocus;
|
||||
});
|
||||
_dateFocusNode.addListener(() {
|
||||
setState(() {
|
||||
_dateFocus = _fromFocusNode.hasFocus;
|
||||
});
|
||||
});
|
||||
_dateFocusNode.addListener(() {
|
||||
setState(() {
|
||||
_dateFocus = _dateFocusNode.hasFocus;
|
||||
});
|
||||
});
|
||||
|
||||
_timeFocusNode.addListener(() {
|
||||
setState(() {
|
||||
_timeFocus = _timeFocusNode.hasFocus;
|
||||
});
|
||||
_timeFocusNode.addListener(() {
|
||||
setState(() {
|
||||
_timeFocus = _timeFocusNode.hasFocus;
|
||||
});
|
||||
});
|
||||
|
||||
_commentsFocusNode.addListener(() {
|
||||
setState(() {
|
||||
_commentsFocus = _commentsFocusNode.hasFocus;
|
||||
});
|
||||
_commentsFocusNode.addListener(() {
|
||||
setState(() {
|
||||
_commentsFocus = _commentsFocusNode.hasFocus;
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
_buscommentsController = initController("comments");
|
||||
_fromController = initController("from");
|
||||
@ -137,12 +140,14 @@ class _BusScreenState extends State<BusScreen> {
|
||||
_toController.addListener(() => _clearError("to"));
|
||||
_dateController.addListener(() => _clearError("date"));
|
||||
_timeController.addListener(() => _clearError("time"));
|
||||
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tripTypeFocusNode.dispose();
|
||||
_dateFocusNode.dispose();
|
||||
_timeFocusNode.dispose();
|
||||
|
||||
_tripTypeController.dispose();
|
||||
_hotelNameController.dispose();
|
||||
_fromController.dispose();
|
||||
@ -153,7 +158,6 @@ class _BusScreenState extends State<BusScreen> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
void _clearError(String field) {
|
||||
if (mounted && errorMessages.containsKey(field)) {
|
||||
setState(() {
|
||||
@ -161,11 +165,12 @@ class _BusScreenState extends State<BusScreen> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
bool isValidData(Map<String, dynamic> data) {
|
||||
errorMessages.clear(); // Reset errors
|
||||
|
||||
// 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
|
||||
for (String field in requiredFields) {
|
||||
@ -177,74 +182,75 @@ class _BusScreenState extends State<BusScreen> {
|
||||
return errorMessages.isEmpty; // Valid if there are no errors
|
||||
}
|
||||
|
||||
void handleSave() {
|
||||
print("Handle Save accomadationData $busData");
|
||||
|
||||
void handleSave(){
|
||||
|
||||
print( "Handle Save accomadationData $busData");
|
||||
|
||||
Map<String,dynamic> data = busData;
|
||||
Map<String, dynamic> data = busData;
|
||||
|
||||
if (!isValidData(data)) {
|
||||
print("Validation Failed: Required fields are missing.");
|
||||
setState(() {});
|
||||
return; // Stop execution if validation fails
|
||||
}else {
|
||||
} else {
|
||||
widget.onSaveBus(busData);
|
||||
}
|
||||
|
||||
widget.onClose(false);// Close screen after saving
|
||||
widget.onClose("Bus", false);
|
||||
// widget.onClose(false); // Close screen after saving
|
||||
}
|
||||
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ResponsiveBuilder(builder: (context, sizingInfo) {
|
||||
bool isMobile = sizingInfo.isMobile;
|
||||
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||
return ResponsiveBuilder(
|
||||
builder: (context, sizingInfo) {
|
||||
bool isMobile = sizingInfo.isMobile;
|
||||
bool isDesktop =
|
||||
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||
|
||||
return Container(
|
||||
color: Color(0xFFF4F4FB),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
widget.onClose(false);
|
||||
},
|
||||
child: Icon(
|
||||
Icons.close,
|
||||
size: 18,
|
||||
color: Color(0xFF575A74),
|
||||
return Container(
|
||||
// color: Colors.white,
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
// Align(
|
||||
// alignment: Alignment.centerRight,
|
||||
// child: InkWell(
|
||||
// onTap: () {
|
||||
// widget.onClose(false);
|
||||
// },
|
||||
// child: Icon(
|
||||
// Icons.close,
|
||||
// size: 18,
|
||||
// color: Color(0xFF575A74),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// Text("Bus Booking List",
|
||||
// style: TextStyle(
|
||||
// fontSize: 18,
|
||||
// fontWeight: FontWeight.bold,
|
||||
// color: Color(0xFF575A74))),
|
||||
// SizedBox(
|
||||
// height: 6,
|
||||
// ),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 30.0),
|
||||
child: Center(
|
||||
child: Column(children: _buildAccomadtionForm(isDesktop)),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text("Bus Booking List",
|
||||
style:
|
||||
TextStyle(fontSize: 18, fontWeight: FontWeight.bold,color: Color(0xFF575A74))),
|
||||
SizedBox(
|
||||
height: 6,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(28.0),
|
||||
child: Center(
|
||||
child: Column(children: _buildAccomadtionForm(isDesktop)),
|
||||
),
|
||||
)
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildAccomadtionForm (bool isDesktop) {
|
||||
List<Widget> _buildAccomadtionForm(bool isDesktop) {
|
||||
List<Widget> buildResponsiveRow(List<Widget> children) {
|
||||
return [
|
||||
isDesktop ? Row(children: children) : Column(children: children),
|
||||
@ -254,11 +260,10 @@ class _BusScreenState extends State<BusScreen> {
|
||||
|
||||
List<List<Widget>> rowBuilders = [
|
||||
// _builClassType(isDesktop),
|
||||
_buildSecondRow(isDesktop)
|
||||
_buildSecondRow(isDesktop),
|
||||
];
|
||||
|
||||
return [
|
||||
|
||||
// ...buildResponsiveRow(_buildFirstRow(isDesktop)),
|
||||
|
||||
// Iterate over rowBuilders and wrap each in a responsive container
|
||||
@ -267,73 +272,60 @@ class _BusScreenState extends State<BusScreen> {
|
||||
...buildResponsiveRow(_buildThirdRow(isDesktop)),
|
||||
|
||||
// Actions row remains a Row
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: _handleAction(isDesktop),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
||||
List<Widget> _buildFirstRow(isDesktop) {
|
||||
|
||||
return [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Trip Type",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74)),
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF575A74),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
|
||||
isDesktop ? Row(children: _buildTripType(isDesktop)
|
||||
) :
|
||||
Column(
|
||||
children: _buildTripType(isDesktop)
|
||||
)
|
||||
|
||||
|
||||
isDesktop
|
||||
? Row(children: _buildTripType(isDesktop))
|
||||
: Column(children: _buildTripType(isDesktop)),
|
||||
],
|
||||
),
|
||||
if (isDesktop)
|
||||
Spacer()
|
||||
else
|
||||
SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
|
||||
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
||||
List<Widget> _buildTripType(bool isDesktop){
|
||||
|
||||
List<Widget> _buildTripType(bool isDesktop) {
|
||||
List<dynamic> purposeList = widget.apiData?['flight_trip_type'] ?? [];
|
||||
|
||||
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
||||
.map((item)=>DropdownMenuItem<String>(
|
||||
value: item['dropdown_key'],
|
||||
child: Text(item['dropdown_value']),
|
||||
)).toList();
|
||||
List<DropdownMenuItem<String>> dropdownItems =
|
||||
purposeList
|
||||
.map(
|
||||
(item) => DropdownMenuItem<String>(
|
||||
value: item['dropdown_key'],
|
||||
child: Text(item['dropdown_value']),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
if (dropdownItems.isEmpty) {
|
||||
dropdownItems.add(
|
||||
DropdownMenuItem<String>(
|
||||
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
|
||||
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||
|
||||
String? selectedPurpose =
|
||||
dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||
|
||||
return [
|
||||
CustomTextFieldWrapper(
|
||||
@ -341,37 +333,37 @@ class _BusScreenState extends State<BusScreen> {
|
||||
isDesktop: isDesktop,
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
|
||||
child: DropdownButtonFormField<String>(
|
||||
focusNode: _tripTypeFocusNode, // Assign the correct focus node
|
||||
value: selectedPurpose,
|
||||
style: TextStyle(fontSize: 12),
|
||||
// style: TextStyle(fontSize: 12),
|
||||
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: 10), // Proper padding
|
||||
horizontal: 10,
|
||||
), // Proper padding
|
||||
),
|
||||
onChanged: purposeList.isNotEmpty
|
||||
? (newValue) {
|
||||
setState(() {
|
||||
selectedPurpose = newValue;
|
||||
});
|
||||
print("Updating form data: Flight -> trip_type -> ${newValue ?? ""}");
|
||||
} : null,
|
||||
onChanged:
|
||||
purposeList.isNotEmpty
|
||||
? (newValue) {
|
||||
setState(() {
|
||||
selectedPurpose = newValue;
|
||||
});
|
||||
print(
|
||||
"Updating form data: Flight -> trip_type -> ${newValue ?? ""}",
|
||||
);
|
||||
}
|
||||
: null,
|
||||
|
||||
items: dropdownItems,
|
||||
),
|
||||
|
||||
|
||||
),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
List<Widget> _buildSecondRow(bool isDesktop) {
|
||||
|
||||
|
||||
DateTime? _selectedCheckOutDate;
|
||||
TimeOfDay? _selectedCheckOutTime;
|
||||
|
||||
@ -381,19 +373,20 @@ class _BusScreenState extends State<BusScreen> {
|
||||
|
||||
DateTime? pickedDate = await showDatePicker(
|
||||
context: context,
|
||||
|
||||
|
||||
initialDate: _selectedCheckOutDate != null && _selectedCheckOutDate!.isAfter(today)
|
||||
? _selectedCheckOutDate!
|
||||
: today,
|
||||
initialDate:
|
||||
_selectedCheckOutDate != null &&
|
||||
_selectedCheckOutDate!.isAfter(today)
|
||||
? _selectedCheckOutDate!
|
||||
: today,
|
||||
firstDate: today,
|
||||
lastDate: DateTime(2100),
|
||||
initialEntryMode: DatePickerEntryMode.calendarOnly,
|
||||
);
|
||||
|
||||
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
|
||||
setState(() {
|
||||
_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) {
|
||||
setState(() {
|
||||
_selectedCheckOutTime = pickedTime;
|
||||
// Formatting time to HH:mm (24-hour format)
|
||||
final now = DateTime.now();
|
||||
final formattedTime = DateFormat('HH:mm').format(
|
||||
DateTime(now.year, now.month, now.day, pickedTime.hour,
|
||||
pickedTime.minute),
|
||||
);
|
||||
_timeController.text = formattedTime;
|
||||
});
|
||||
// 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(() {
|
||||
_selectedCheckOutTime = pickedTime;
|
||||
// Formatting time to HH:mm (24-hour format)
|
||||
final now = DateTime.now();
|
||||
final formattedTime = DateFormat('HH:mm').format(
|
||||
DateTime(
|
||||
now.year,
|
||||
now.month,
|
||||
now.day,
|
||||
pickedTime.hour,
|
||||
pickedTime.minute,
|
||||
),
|
||||
);
|
||||
_timeController.text = formattedTime;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"From",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74)),
|
||||
"From *",
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF575A74),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
CustomTextFieldItnerarySubWrapper(
|
||||
@ -436,45 +463,40 @@ class _BusScreenState extends State<BusScreen> {
|
||||
isDesktop: isDesktop,
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
child: TextField(
|
||||
child: TextField(
|
||||
focusNode: _fromFocusNode,
|
||||
controller: _fromController,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9 _-]')),
|
||||
],
|
||||
decoration: const InputDecoration(
|
||||
labelText: "From",
|
||||
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (errorMessages["from"] != null) ...[
|
||||
SizedBox(height: 5), // Space before error message
|
||||
Text(
|
||||
"Required",
|
||||
style: TextStyle(color: Colors.red, fontSize: 12),
|
||||
),
|
||||
Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
|
||||
],
|
||||
],
|
||||
),
|
||||
if (isDesktop)
|
||||
Spacer()
|
||||
else
|
||||
SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"To",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74)),
|
||||
"To *",
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF575A74),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
CustomTextFieldItnerarySubWrapper(
|
||||
@ -482,10 +504,12 @@ class _BusScreenState extends State<BusScreen> {
|
||||
isDesktop: isDesktop,
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
|
||||
child: TextField(
|
||||
focusNode: _toFocusNode,
|
||||
controller: _toController,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9 _-]')),
|
||||
],
|
||||
style: const TextStyle(fontSize: 12),
|
||||
decoration: const InputDecoration(
|
||||
labelText: "To",
|
||||
@ -499,28 +523,21 @@ class _BusScreenState extends State<BusScreen> {
|
||||
),
|
||||
if (errorMessages["to"] != null) ...[
|
||||
SizedBox(height: 5), // Space before error message
|
||||
Text(
|
||||
"Required",
|
||||
style: TextStyle(color: Colors.red, fontSize: 12),
|
||||
),
|
||||
Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
|
||||
],
|
||||
],
|
||||
),
|
||||
if (isDesktop)
|
||||
Spacer()
|
||||
else
|
||||
SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Date",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74)),
|
||||
"Date *",
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF575A74),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
CustomTextFieldItnerarySubWrapper(
|
||||
@ -528,13 +545,18 @@ class _BusScreenState extends State<BusScreen> {
|
||||
isDesktop: isDesktop,
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
|
||||
child: GestureDetector(
|
||||
onTap: () => _selectCheckOutDate(context),
|
||||
// onTap: () => _selectCheckOutDate(context),
|
||||
onTap: () {
|
||||
_dateFocusNode.requestFocus();
|
||||
_selectCheckOutDate(context);
|
||||
},
|
||||
child: AbsorbPointer(
|
||||
child: TextField(
|
||||
focusNode: _dateFocusNode,
|
||||
controller: _dateController,
|
||||
|
||||
readOnly: true,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Select Date",
|
||||
@ -542,39 +564,34 @@ class _BusScreenState extends State<BusScreen> {
|
||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||
suffixIcon: Icon(Icons.calendar_today,
|
||||
size: 16, color: Colors.grey),
|
||||
suffixIcon: Icon(
|
||||
Icons.calendar_today,
|
||||
size: 16,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
),
|
||||
),
|
||||
if (errorMessages["date"] != null) ...[
|
||||
SizedBox(height: 5), // Space before error message
|
||||
Text(
|
||||
"Required",
|
||||
style: TextStyle(color: Colors.red, fontSize: 12),
|
||||
),
|
||||
Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
|
||||
],
|
||||
],
|
||||
),
|
||||
if (isDesktop)
|
||||
Spacer()
|
||||
else
|
||||
SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Time",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74)),
|
||||
"Time *",
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF575A74),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
CustomTextFieldItnerarySubWrapper(
|
||||
@ -583,38 +600,44 @@ class _BusScreenState extends State<BusScreen> {
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
child: GestureDetector(
|
||||
onTap: () => _selectCheckOutTime(context),
|
||||
onTap: () {
|
||||
// _selectedCheckOutTime = null; // ✅ Reset time variable
|
||||
_timeController.text = ""; // ✅ Clear text field
|
||||
_timeFocusNode.requestFocus();
|
||||
_selectCheckOutTime(context);
|
||||
},
|
||||
child: AbsorbPointer(
|
||||
child: TextField(
|
||||
focusNode: _timeFocusNode,
|
||||
controller: _timeController,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
readOnly: true,
|
||||
style: const TextStyle(fontSize: 12, color: Colors.black),
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Select Time",
|
||||
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||
suffixIcon:
|
||||
Icon(Icons.access_time, size: 16, color: Colors.grey),
|
||||
suffixIcon: Icon(
|
||||
Icons.access_time,
|
||||
size: 16,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
),
|
||||
),
|
||||
if (errorMessages["time"] != null) ...[
|
||||
SizedBox(height: 5), // Space before error message
|
||||
Text(
|
||||
"Required",
|
||||
errorMessages["time"]!,
|
||||
style: TextStyle(color: Colors.red, fontSize: 12),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
@ -625,35 +648,47 @@ class _BusScreenState extends State<BusScreen> {
|
||||
children: [
|
||||
Text(
|
||||
"Comments",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74)),
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF575A74),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
CustomTextFieldWrapper(
|
||||
CustomTextFieldItnerarySubWrapper(
|
||||
isFocused: _commentsFocus, // Dropdown doesn't use focus
|
||||
isDesktop: isDesktop,
|
||||
width: isDesktop
|
||||
? MediaQuery.of(context).size.width * 0.4
|
||||
: MediaQuery.of(context).size.width * 0.66,
|
||||
child: TextField(
|
||||
focusNode: _commentsFocusNode,
|
||||
controller: _buscommentsController,
|
||||
maxLines: 6,
|
||||
keyboardType: TextInputType.multiline,
|
||||
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: 4),
|
||||
width: isDesktop ? MediaQuery.of(context).size.width * 0.35 : null,
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
child: TextField(
|
||||
focusNode: _commentsFocusNode,
|
||||
controller: _buscommentsController,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9 _-]')),
|
||||
],
|
||||
style: TextStyle(fontSize: 12),
|
||||
decoration: InputDecoration(
|
||||
labelText: "Comments",
|
||||
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
if (isDesktop) Spacer(),
|
||||
Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: _handleAction(isDesktop),
|
||||
),
|
||||
],
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@ -662,39 +697,35 @@ class _BusScreenState extends State<BusScreen> {
|
||||
// Close Button
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
widget.onClose(false);// Close the dialog or screen
|
||||
widget.onClose("Bus", false);
|
||||
// widget.onClose(false); // Close the dialog or screen
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.grey[400], // Light grey color
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
),
|
||||
child: Text(
|
||||
"Close",
|
||||
style: TextStyle(color: Colors.white, fontSize: 14),
|
||||
style: GoogleFonts.poppins(color: Colors.white, fontSize: 12),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10), // Space between buttons
|
||||
|
||||
// Save Changes Button
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
handleSave();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue, // Primary color for save
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
backgroundColor: Color(0xFF114D8B), // Primary color for save
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
),
|
||||
child: Text(
|
||||
"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/services.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:responsive_builder/responsive_builder.dart';
|
||||
|
||||
import '../../utils/auth_utils.dart';
|
||||
import '../../widgets/custom_text_field.dart';
|
||||
import '../../widgets/custom_text_itnerary_sub.dart';
|
||||
|
||||
class MiscellaneousScreen extends StatefulWidget {
|
||||
|
||||
final Map<String, dynamic>? apiData;
|
||||
final Function(bool) onClose;
|
||||
final Function(String, bool) onClose;
|
||||
|
||||
final Function(Map<String, dynamic>) onSaveMiscellaneous;
|
||||
final Map<String, dynamic>? selectedItem;
|
||||
final int? selectedIndex;
|
||||
final String? loginUser;
|
||||
|
||||
MiscellaneousScreen({
|
||||
required this.onClose, required this.apiData, required this.onSaveMiscellaneous,
|
||||
this.selectedItem, this.selectedIndex,required this.loginUser});
|
||||
required this.onClose,
|
||||
required this.apiData,
|
||||
required this.onSaveMiscellaneous,
|
||||
this.selectedItem,
|
||||
this.selectedIndex,
|
||||
required this.loginUser,
|
||||
});
|
||||
|
||||
@override
|
||||
_MiscellaneousScreenState createState() => _MiscellaneousScreenState();
|
||||
@ -37,10 +46,10 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
|
||||
|
||||
late TextEditingController _commentsController = TextEditingController();
|
||||
|
||||
bool _isHotelNameFocused = false;
|
||||
bool _hotelNameFocused = false;
|
||||
bool _commentsFocus = false;
|
||||
String? selectedSpecialType;
|
||||
|
||||
Color layoutColor = Colors.grey;
|
||||
Map<String, String> errorMessages = {};
|
||||
|
||||
Map<String, dynamic> get miscellaneousData {
|
||||
@ -49,14 +58,14 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
|
||||
"comments": _commentsController.text,
|
||||
"created_by": widget.loginUser,
|
||||
"updated_by": widget.loginUser,
|
||||
|
||||
};
|
||||
|
||||
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"];
|
||||
|
||||
} else if (widget.selectedItem?["miscellaneous_id"] != null && widget.selectedItem?["miscellaneous_id"] != 0) {
|
||||
} else if (widget.selectedItem?["miscellaneous_id"] != null &&
|
||||
widget.selectedItem?["miscellaneous_id"] != 0) {
|
||||
data["miscellaneous_id"] = widget.selectedItem!["miscellaneous_id"];
|
||||
}
|
||||
}
|
||||
@ -64,19 +73,18 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.selectedItem == null) {
|
||||
_commentsController.clear();
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
loadInitialData();
|
||||
});
|
||||
_hotelNameFocusNode.addListener(() {
|
||||
setState(() {
|
||||
_isHotelNameFocused = _hotelNameFocusNode.hasFocus;
|
||||
_hotelNameFocused = _hotelNameFocusNode.hasFocus;
|
||||
});
|
||||
});
|
||||
|
||||
@ -86,27 +94,43 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
|
||||
});
|
||||
});
|
||||
|
||||
_commentsController =
|
||||
TextEditingController(text: widget.selectedItem?["comments"] ?? "");
|
||||
_commentsController = TextEditingController(
|
||||
text: widget.selectedItem?["comments"] ?? "",
|
||||
);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_tripTypeFocusNode.dispose();
|
||||
_hotelNameFocusNode.dispose();
|
||||
_tripTypeController.dispose();
|
||||
_commentsController.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) {
|
||||
errorMessages.clear(); // Reset errors
|
||||
@ -114,90 +138,68 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
|
||||
// Required fields that must not be empty
|
||||
List<String> requiredFields = ["special_request", "comments"];
|
||||
|
||||
|
||||
// Check validation for each field
|
||||
for (String field in requiredFields) {
|
||||
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
|
||||
}
|
||||
|
||||
void handleSave() {
|
||||
print("Handle Save miscellaneousData $miscellaneousData");
|
||||
|
||||
|
||||
void handleSave(){
|
||||
|
||||
print( "Handle Save miscellaneousData $miscellaneousData");
|
||||
|
||||
Map<String,dynamic> data = miscellaneousData;
|
||||
Map<String, dynamic> data = miscellaneousData;
|
||||
|
||||
if (!isValidData(data)) {
|
||||
print("Validation Failed: Required fields are missing.");
|
||||
setState(() {});
|
||||
return; // Stop execution if validation fails
|
||||
}else {
|
||||
} else {
|
||||
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
|
||||
// if (widget.selectedItem == null) {
|
||||
// _commentsController.clear();
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ResponsiveBuilder(builder: (context, sizingInfo) {
|
||||
bool isMobile = sizingInfo.isMobile;
|
||||
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||
return ResponsiveBuilder(
|
||||
builder: (context, sizingInfo) {
|
||||
bool isMobile = sizingInfo.isMobile;
|
||||
bool isDesktop =
|
||||
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||
|
||||
return Container(
|
||||
color: Color(0xFFF4F4FB),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
_commentsController.clear();
|
||||
widget.onClose(false);
|
||||
},
|
||||
child: Icon(
|
||||
Icons.close,
|
||||
size: 18,
|
||||
color: Color(0xFF575A74),
|
||||
return Container(
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 30.0),
|
||||
child: Center(
|
||||
child: Column(children: _buildAccomadtionForm(isDesktop)),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text("Miscellaneous Booking List",
|
||||
style:
|
||||
TextStyle(fontSize: 18, fontWeight: FontWeight.bold,color: Color(0xFF575A74))),
|
||||
SizedBox(
|
||||
height: 6,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(28.0),
|
||||
child: Center(
|
||||
child: Column(children: _buildAccomadtionForm(isDesktop)),
|
||||
),
|
||||
)
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildAccomadtionForm (bool isDesktop) {
|
||||
List<Widget> _buildAccomadtionForm(bool isDesktop) {
|
||||
List<Widget> buildResponsiveRow(List<Widget> children) {
|
||||
return [
|
||||
isDesktop ? Row(children: children) : Column(children: children),
|
||||
@ -205,170 +207,279 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
return [
|
||||
|
||||
...buildResponsiveRow(_buildFirstRow(isDesktop)),
|
||||
|
||||
...buildResponsiveRow(_buildThirdRow(isDesktop)),
|
||||
|
||||
// Actions row remains a Row
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: _handleAction(isDesktop),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
||||
List<Widget> _buildFirstRow(isDesktop) {
|
||||
|
||||
return [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Special Type",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74)),
|
||||
"Special Type *",
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF575A74),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
|
||||
isDesktop ? Row(children: _buildTripType(isDesktop)
|
||||
) :
|
||||
Column(
|
||||
children: _buildTripType(isDesktop)
|
||||
)
|
||||
|
||||
|
||||
isDesktop
|
||||
? Row(children: _buildTripType(isDesktop))
|
||||
: Column(children: _buildTripType(isDesktop)),
|
||||
],
|
||||
),
|
||||
if (isDesktop)
|
||||
Spacer()
|
||||
else
|
||||
SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
||||
List<Widget> _buildTripType(bool isDesktop){
|
||||
|
||||
List<dynamic> purposeList = widget.apiData?['miscellaneous_special_request'] ?? [];
|
||||
List<Widget> _buildTripType(bool isDesktop) {
|
||||
List<dynamic> purposeList =
|
||||
widget.apiData?['miscellaneous_special_request'] ?? [];
|
||||
// selectedInsuranceType = purposeList.isNotEmpty ? purposeList.first['dropdown_value'] : null;
|
||||
|
||||
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
||||
.map((item)=>DropdownMenuItem<String>(
|
||||
value: item['dropdown_key'],
|
||||
child: Text(item['dropdown_value']),
|
||||
)).toList();
|
||||
List<DropdownMenuItem<String>> dropdownItems =
|
||||
purposeList
|
||||
.map(
|
||||
(item) => DropdownMenuItem<String>(
|
||||
value: item['dropdown_key'],
|
||||
child: Text(item['dropdown_value']),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
if (dropdownItems.isEmpty) {
|
||||
dropdownItems.add(
|
||||
DropdownMenuItem<String>(
|
||||
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
|
||||
selectedSpecialType ??= dropdownItems.isNotEmpty ? dropdownItems.first.value : "No options";
|
||||
|
||||
selectedSpecialType ??=
|
||||
dropdownItems.isNotEmpty ? dropdownItems.first.value : "No options";
|
||||
|
||||
return [
|
||||
CustomTextFieldWrapper(
|
||||
isFocused: _isHotelNameFocused,
|
||||
// CustomTextFieldWrapper(
|
||||
// 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,
|
||||
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(
|
||||
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) {
|
||||
width: double.infinity,
|
||||
child: Focus(
|
||||
focusNode: _hotelNameFocusNode,
|
||||
onFocusChange: (hasFocus) {
|
||||
setState(() {
|
||||
selectedSpecialType = newValue;
|
||||
_hotelNameFocused = hasFocus;
|
||||
});
|
||||
|
||||
print(selectedSpecialType);
|
||||
|
||||
}
|
||||
: null,
|
||||
items: dropdownItems,
|
||||
},
|
||||
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: () => {},
|
||||
),
|
||||
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) {
|
||||
setState(() {
|
||||
selectedSpecialType = newValue as String?;
|
||||
});
|
||||
print("Updating form data: -> ${newValue ?? ""}");
|
||||
}
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
),
|
||||
),
|
||||
if (errorMessages["special_request"] != null) ...[
|
||||
SizedBox(height: 5), // Space before error message
|
||||
Text(
|
||||
"Select Type",
|
||||
style: TextStyle(color: Colors.red, fontSize: 12),
|
||||
),
|
||||
Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
||||
List<Widget> _buildThirdRow(bool isDesktop) {
|
||||
return [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Comments",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74)),
|
||||
"Comments *",
|
||||
style: GoogleFonts.poppins(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Color(0xFF575A74),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
CustomTextFieldWrapper(
|
||||
// CustomTextFieldWrapper
|
||||
CustomTextFieldItnerarySubWrapper(
|
||||
isFocused: _commentsFocus, // Dropdown doesn't use focus
|
||||
isDesktop: isDesktop,
|
||||
width: isDesktop
|
||||
? MediaQuery.of(context).size.width * 0.4
|
||||
: MediaQuery.of(context).size.width * 0.66,
|
||||
child: TextField(
|
||||
focusNode: _commentsFocusNode,
|
||||
controller: _commentsController,
|
||||
maxLines: 6,
|
||||
keyboardType: TextInputType.multiline,
|
||||
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: 4),
|
||||
width:
|
||||
isDesktop
|
||||
? MediaQuery.of(context).size.width * 0.34
|
||||
: null, //MediaQuery.of(context).size.width * 0.66,
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
child: TextField(
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9 _-]')),
|
||||
],
|
||||
focusNode: _commentsFocusNode,
|
||||
controller: _commentsController,
|
||||
style: TextStyle(fontSize: 12),
|
||||
decoration: InputDecoration(
|
||||
labelText: "Comments",
|
||||
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (errorMessages["comments"] != null) ...[
|
||||
SizedBox(height: 5), // Space before error message
|
||||
Text(
|
||||
"Required",
|
||||
style: TextStyle(color: Colors.red, fontSize: 12),
|
||||
),
|
||||
Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
|
||||
],
|
||||
],
|
||||
)
|
||||
),
|
||||
if (isDesktop) Spacer(),
|
||||
SizedBox(height: 5),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: _handleAction(isDesktop),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@ -377,41 +488,36 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
|
||||
// Close Button
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
|
||||
_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(
|
||||
backgroundColor: Colors.grey[400], // Light grey color
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
),
|
||||
child: Text(
|
||||
"Close",
|
||||
style: TextStyle(color: Colors.white, fontSize: 14),
|
||||
style: GoogleFonts.poppins(color: Colors.white, fontSize: 12),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10), // Space between buttons
|
||||
|
||||
// Save Changes Button
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
handleSave();
|
||||
handleSave();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue, // Primary color for save
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
backgroundColor: Color(0xFF114D8B), // Primary color for save
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
),
|
||||
child: Text(
|
||||
"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/services.dart';
|
||||
import 'package:intl/intl.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(bool) onClose;
|
||||
|
||||
ForexScreen({required this.formData, required this.updateFormData,
|
||||
required this.onClose, this.apiData});
|
||||
ForexScreen({
|
||||
required this.formData,
|
||||
required this.updateFormData,
|
||||
required this.onClose,
|
||||
this.apiData,
|
||||
});
|
||||
|
||||
@override
|
||||
_ForexScreenState createState() => _ForexScreenState();
|
||||
@ -95,57 +100,55 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
_tripTypeController =
|
||||
TextEditingController(text: widget.formData["tripType"] ?? "");
|
||||
_hotelNameController =
|
||||
TextEditingController(text: widget.formData["_hotelName"] ?? "");
|
||||
_fromController =
|
||||
TextEditingController(text: widget.formData["_from"] ?? "");
|
||||
_toController =
|
||||
TextEditingController(text: widget.formData["_Check_In_Time"] ?? "");
|
||||
_dateController =
|
||||
TextEditingController(text: widget.formData["_Check_Out"] ?? "");
|
||||
_endDateController =
|
||||
TextEditingController(text: widget.formData["_Check_In"] ?? "");
|
||||
_timeController =
|
||||
TextEditingController(text: widget.formData["_Check_Out_Time"] ?? "");
|
||||
_commentsController =
|
||||
TextEditingController(text: widget.formData["_comments"] ?? "");
|
||||
_tripTypeController = TextEditingController(
|
||||
text: widget.formData["tripType"] ?? "",
|
||||
);
|
||||
_hotelNameController = TextEditingController(
|
||||
text: widget.formData["_hotelName"] ?? "",
|
||||
);
|
||||
_fromController = TextEditingController(
|
||||
text: widget.formData["_from"] ?? "",
|
||||
);
|
||||
_toController = TextEditingController(
|
||||
text: widget.formData["_Check_In_Time"] ?? "",
|
||||
);
|
||||
_dateController = TextEditingController(
|
||||
text: widget.formData["_Check_Out"] ?? "",
|
||||
);
|
||||
_endDateController = TextEditingController(
|
||||
text: widget.formData["_Check_In"] ?? "",
|
||||
);
|
||||
_timeController = TextEditingController(
|
||||
text: widget.formData["_Check_Out_Time"] ?? "",
|
||||
);
|
||||
_commentsController = TextEditingController(
|
||||
text: widget.formData["_comments"] ?? "",
|
||||
);
|
||||
|
||||
// Save data when user types
|
||||
_tripTypeController.addListener(() {
|
||||
widget.updateFormData(
|
||||
"Bus", "_tripType", _tripTypeController.text);
|
||||
widget.updateFormData("Bus", "_tripType", _tripTypeController.text);
|
||||
}); // Save data when user types
|
||||
_hotelNameController.addListener(() {
|
||||
widget.updateFormData(
|
||||
"Bus", "_hotelName", _hotelNameController.text);
|
||||
widget.updateFormData("Bus", "_hotelName", _hotelNameController.text);
|
||||
});
|
||||
_fromController.addListener(() {
|
||||
widget.updateFormData(
|
||||
"Bus", "_from", _fromController.text);
|
||||
widget.updateFormData("Bus", "_from", _fromController.text);
|
||||
});
|
||||
_toController.addListener(() {
|
||||
widget.updateFormData(
|
||||
"Bus", "_Check_In_Time", _toController.text);
|
||||
widget.updateFormData("Bus", "_Check_In_Time", _toController.text);
|
||||
});
|
||||
_dateController.addListener(() {
|
||||
widget.updateFormData(
|
||||
"Bus", "_Check_Out", _dateController.text);
|
||||
widget.updateFormData("Bus", "_Check_Out", _dateController.text);
|
||||
});
|
||||
_endDateController.addListener(() {
|
||||
widget.updateFormData(
|
||||
"Bus", "_Check_In", _endDateController.text);
|
||||
widget.updateFormData("Bus", "_Check_In", _endDateController.text);
|
||||
});
|
||||
_timeController.addListener(() {
|
||||
widget.updateFormData(
|
||||
"Bus", "_Check_Out_Time", _timeController.text);
|
||||
widget.updateFormData("Bus", "_Check_Out_Time", _timeController.text);
|
||||
});
|
||||
_commentsController.addListener(() {
|
||||
widget.updateFormData(
|
||||
"Bus", "_comments", _commentsController.text);
|
||||
widget.updateFormData("Bus", "_comments", _commentsController.text);
|
||||
});
|
||||
}
|
||||
|
||||
@ -163,56 +166,60 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ResponsiveBuilder(builder: (context, sizingInfo) {
|
||||
bool isMobile = sizingInfo.isMobile;
|
||||
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||
return ResponsiveBuilder(
|
||||
builder: (context, sizingInfo) {
|
||||
bool isMobile = sizingInfo.isMobile;
|
||||
bool isDesktop =
|
||||
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
|
||||
|
||||
return Container(
|
||||
color: Color(0xFFF4F4FB),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
widget.onClose(false);
|
||||
},
|
||||
child: Icon(
|
||||
Icons.close,
|
||||
size: 18,
|
||||
return Container(
|
||||
color: Color(0xFFF4F4FB),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
widget.onClose(false);
|
||||
},
|
||||
child: Icon(
|
||||
Icons.close,
|
||||
size: 18,
|
||||
color: Color(0xFF575A74),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
"Forex List",
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF575A74),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text("Forex List",
|
||||
style:
|
||||
TextStyle(fontSize: 18, fontWeight: FontWeight.bold,color: Color(0xFF575A74))),
|
||||
SizedBox(
|
||||
height: 6,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(28.0),
|
||||
child: Center(
|
||||
child: Column(children: _buildAccomadtionForm(isDesktop)),
|
||||
SizedBox(height: 6),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(28.0),
|
||||
child: Center(
|
||||
child: Column(children: _buildAccomadtionForm(isDesktop)),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildAccomadtionForm (bool isDesktop) {
|
||||
List<Widget> _buildAccomadtionForm(bool isDesktop) {
|
||||
List<Widget> buildResponsiveRow(List<Widget> children) {
|
||||
return [
|
||||
isDesktop ? Row(children: children) : Column(children: children),
|
||||
@ -222,14 +229,12 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
|
||||
List<List<Widget>> rowBuilders = [
|
||||
_builClassType(isDesktop),
|
||||
_buildSecondRow(isDesktop)
|
||||
_buildSecondRow(isDesktop),
|
||||
];
|
||||
|
||||
return [
|
||||
|
||||
...buildResponsiveRow(_buildFirstRow(isDesktop)),
|
||||
|
||||
|
||||
// Iterate over rowBuilders and wrap each in a responsive container
|
||||
...rowBuilders.expand((row) => buildResponsiveRow(row)),
|
||||
|
||||
@ -247,23 +252,21 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
||||
List<Widget> _buildFirstRow(isDesktop) {
|
||||
|
||||
DateTime? _selectedCheckOutDate;
|
||||
DateTime? _selectedEndDate;
|
||||
|
||||
|
||||
Future<void> _selectCheckOutDate(BuildContext context) async {
|
||||
DateTime now = DateTime.now();
|
||||
DateTime today = DateTime(now.year, now.month, now.day);
|
||||
|
||||
DateTime? pickedDate = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: _selectedCheckOutDate != null && _selectedCheckOutDate!.isAfter(today)
|
||||
? _selectedCheckOutDate!
|
||||
: today,
|
||||
initialDate:
|
||||
_selectedCheckOutDate != null &&
|
||||
_selectedCheckOutDate!.isAfter(today)
|
||||
? _selectedCheckOutDate!
|
||||
: today,
|
||||
firstDate: today,
|
||||
lastDate: DateTime(2100),
|
||||
);
|
||||
@ -274,18 +277,20 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
_dateController.text = DateFormat('yyyy-MM-dd').format(pickedDate);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
Future<void> _selectForexEndDate(BuildContext context) async {
|
||||
|
||||
DateTime now = DateTime.now();
|
||||
DateTime today = DateTime(now.year, now.month, now.day);
|
||||
|
||||
DateTime? pickedDate = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: _selectedEndDate != null && _selectedEndDate!.isAfter(today)
|
||||
? _selectedEndDate!
|
||||
: today,
|
||||
initialDate:
|
||||
_selectedEndDate != null && _selectedEndDate!.isAfter(today)
|
||||
? _selectedEndDate!
|
||||
: today,
|
||||
firstDate: today,
|
||||
lastDate: DateTime(2100),
|
||||
);
|
||||
@ -298,7 +303,6 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -306,9 +310,10 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
Text(
|
||||
"Forex Start Date",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74)),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
CustomTextFieldWrapper(
|
||||
@ -330,32 +335,30 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||
suffixIcon: Icon(Icons.calendar_today,
|
||||
size: 16, color: Colors.grey),
|
||||
suffixIcon: Icon(
|
||||
Icons.calendar_today,
|
||||
size: 16,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (isDesktop)
|
||||
Spacer()
|
||||
else
|
||||
SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Forex End Date",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74)),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
CustomTextFieldWrapper(
|
||||
@ -377,46 +380,50 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||
suffixIcon: Icon(Icons.calendar_today,
|
||||
size: 16, color: Colors.grey),
|
||||
suffixIcon: Icon(
|
||||
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<DropdownMenuItem<String>> dropdownItems = purposeList
|
||||
.map((item)=>DropdownMenuItem<String>(
|
||||
value: item['dropdown_value'],
|
||||
child: Text(item['dropdown_value']),
|
||||
)).toList();
|
||||
List<DropdownMenuItem<String>> dropdownItems =
|
||||
purposeList
|
||||
.map(
|
||||
(item) => DropdownMenuItem<String>(
|
||||
value: item['dropdown_value'],
|
||||
child: Text(item['dropdown_value']),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
if (dropdownItems.isEmpty) {
|
||||
dropdownItems.add(
|
||||
DropdownMenuItem<String>(
|
||||
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
|
||||
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||
|
||||
String? selectedPurpose =
|
||||
dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||
|
||||
return [
|
||||
CustomTextFieldWrapper(
|
||||
@ -432,51 +439,62 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: 10), // Proper padding
|
||||
horizontal: 10,
|
||||
), // Proper padding
|
||||
),
|
||||
onChanged: purposeList.isNotEmpty
|
||||
? (newValue) {
|
||||
setState(() {
|
||||
selectedPurpose = newValue;
|
||||
});
|
||||
print("Updating form data: Flight -> trip_type -> ${newValue ?? ""}");
|
||||
onChanged:
|
||||
purposeList.isNotEmpty
|
||||
? (newValue) {
|
||||
setState(() {
|
||||
selectedPurpose = newValue;
|
||||
});
|
||||
print(
|
||||
"Updating form data: Flight -> trip_type -> ${newValue ?? ""}",
|
||||
);
|
||||
|
||||
|
||||
widget.updateFormData("Flight", "trip_type", newValue ?? "");
|
||||
}
|
||||
: null,
|
||||
widget.updateFormData(
|
||||
"Flight",
|
||||
"trip_type",
|
||||
newValue ?? "",
|
||||
);
|
||||
}
|
||||
: null,
|
||||
|
||||
items: dropdownItems,
|
||||
),
|
||||
|
||||
|
||||
),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
List<Widget> _builClassType(bool isDesktop){
|
||||
|
||||
List<Widget> _builClassType(bool isDesktop) {
|
||||
List<dynamic> purposeList = widget.apiData?['flight_class'] ?? [];
|
||||
|
||||
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
||||
.map((item)=>DropdownMenuItem<String>(
|
||||
value: item['dropdown_value'],
|
||||
child: Text(item['dropdown_value']),
|
||||
)).toList();
|
||||
List<DropdownMenuItem<String>> dropdownItems =
|
||||
purposeList
|
||||
.map(
|
||||
(item) => DropdownMenuItem<String>(
|
||||
value: item['dropdown_value'],
|
||||
child: Text(item['dropdown_value']),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
if (dropdownItems.isEmpty) {
|
||||
dropdownItems.add(
|
||||
DropdownMenuItem<String>(
|
||||
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
|
||||
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||
String? selectedPurpose =
|
||||
dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||
|
||||
return [
|
||||
Column(
|
||||
@ -485,9 +503,10 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
Text(
|
||||
"Class *",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74)),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
CustomTextFieldWrapper(
|
||||
@ -504,15 +523,17 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: 10), // Proper padding
|
||||
horizontal: 10,
|
||||
), // Proper padding
|
||||
),
|
||||
onChanged: purposeList.isNotEmpty
|
||||
? (newValue) {
|
||||
setState(() {
|
||||
selectedPurpose = newValue;
|
||||
});
|
||||
}
|
||||
: null,
|
||||
onChanged:
|
||||
purposeList.isNotEmpty
|
||||
? (newValue) {
|
||||
setState(() {
|
||||
selectedPurpose = newValue;
|
||||
});
|
||||
}
|
||||
: null,
|
||||
items: dropdownItems,
|
||||
),
|
||||
|
||||
@ -535,27 +556,30 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
List<Widget> _buildSecondRow(bool isDesktop) {
|
||||
return [
|
||||
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Transport",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74)),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
CustomTextFieldItnerarySubWrapper(
|
||||
isFocused: _fromFocus,
|
||||
|
||||
isDesktop: isDesktop,
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
child: TextField(
|
||||
child: TextField(
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9_-]')),
|
||||
],
|
||||
focusNode: _fromFocusNode,
|
||||
controller: _fromController,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
@ -565,28 +589,23 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (isDesktop)
|
||||
Spacer()
|
||||
else
|
||||
SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Accomodation",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74)),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
CustomTextFieldItnerarySubWrapper(
|
||||
@ -611,21 +630,17 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
if (isDesktop)
|
||||
Spacer()
|
||||
else
|
||||
SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Telephone",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74)),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
CustomTextFieldItnerarySubWrapper(
|
||||
@ -633,7 +648,7 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
isDesktop: isDesktop,
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
child: TextField(
|
||||
child: TextField(
|
||||
focusNode: _fromFocusNode,
|
||||
controller: _fromController,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
@ -643,34 +658,29 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (isDesktop)
|
||||
Spacer()
|
||||
else
|
||||
SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Other Expenses",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74)),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
CustomTextFieldItnerarySubWrapper(
|
||||
isFocused: _toFocus,
|
||||
isDesktop: isDesktop,
|
||||
color:Colors.transparent,
|
||||
color: Colors.transparent,
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
|
||||
@ -680,7 +690,11 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
"Rs ",
|
||||
// focusNode: _toFocusNode,
|
||||
// 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(
|
||||
// labelText: "To",
|
||||
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
||||
@ -694,43 +708,48 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
List<Widget> _buildCardDetailsRow(bool isDesktop) {
|
||||
|
||||
List<dynamic> purposeList = widget.apiData?['flight_class'] ?? [];
|
||||
|
||||
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
||||
.map((item)=>DropdownMenuItem<String>(
|
||||
value: item['dropdown_value'],
|
||||
child: Text(item['dropdown_value']),
|
||||
)).toList();
|
||||
List<DropdownMenuItem<String>> dropdownItems =
|
||||
purposeList
|
||||
.map(
|
||||
(item) => DropdownMenuItem<String>(
|
||||
value: item['dropdown_value'],
|
||||
child: Text(item['dropdown_value']),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
if (dropdownItems.isEmpty) {
|
||||
dropdownItems.add(
|
||||
DropdownMenuItem<String>(
|
||||
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
|
||||
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||
String? selectedPurpose =
|
||||
dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
||||
return [
|
||||
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Card Number*",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74)),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
CustomTextFieldItnerarySubWrapper(
|
||||
@ -738,7 +757,7 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
isDesktop: isDesktop,
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
child: TextField(
|
||||
child: TextField(
|
||||
focusNode: _fromFocusNode,
|
||||
controller: _fromController,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
@ -748,7 +767,6 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -756,21 +774,17 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
],
|
||||
),
|
||||
|
||||
if (isDesktop)
|
||||
Spacer()
|
||||
else
|
||||
SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Currency*",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74)),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
CustomTextFieldItnerarySubWrapper(
|
||||
@ -787,36 +801,34 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: 10), // Proper padding
|
||||
horizontal: 10,
|
||||
), // Proper padding
|
||||
),
|
||||
onChanged: purposeList.isNotEmpty
|
||||
? (newValue) {
|
||||
setState(() {
|
||||
selectedPurpose = newValue;
|
||||
});
|
||||
}
|
||||
: null,
|
||||
onChanged:
|
||||
purposeList.isNotEmpty
|
||||
? (newValue) {
|
||||
setState(() {
|
||||
selectedPurpose = newValue;
|
||||
});
|
||||
}
|
||||
: null,
|
||||
items: dropdownItems,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (isDesktop)
|
||||
Spacer()
|
||||
else
|
||||
SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Card*",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74)),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
CustomTextFieldItnerarySubWrapper(
|
||||
@ -841,21 +853,17 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
if (isDesktop)
|
||||
Spacer()
|
||||
else
|
||||
SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Cash*",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74)),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
CustomTextFieldItnerarySubWrapper(
|
||||
@ -863,7 +871,7 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
isDesktop: isDesktop,
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
child: TextField(
|
||||
child: TextField(
|
||||
focusNode: _fromFocusNode,
|
||||
controller: _fromController,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
@ -873,16 +881,12 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||||
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
@ -894,17 +898,19 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
Text(
|
||||
"Delivery Location",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74)),
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Color(0xFF575A74),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
CustomTextFieldWrapper(
|
||||
isFocused: _commentsFocus, // Dropdown doesn't use focus
|
||||
isDesktop: isDesktop,
|
||||
width: isDesktop
|
||||
? MediaQuery.of(context).size.width * 0.4
|
||||
: MediaQuery.of(context).size.width * 0.66,
|
||||
width:
|
||||
isDesktop
|
||||
? MediaQuery.of(context).size.width * 0.4
|
||||
: MediaQuery.of(context).size.width * 0.66,
|
||||
child: TextField(
|
||||
focusNode: _commentsFocusNode,
|
||||
controller: _commentsController,
|
||||
@ -921,7 +927,7 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@ -944,10 +950,14 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
),
|
||||
Text(
|
||||
"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(
|
||||
backgroundColor: Colors.grey[400], // Light grey color
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
),
|
||||
child: Text(
|
||||
@ -971,7 +979,6 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10), // Space between buttons
|
||||
|
||||
// Save Changes Button
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
@ -979,9 +986,7 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue, // Primary color for save
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
||||
),
|
||||
child: Text(
|
||||
@ -991,4 +996,4 @@ class _ForexScreenState extends State<ForexScreen> {
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,93 +1,713 @@
|
||||
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(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
|
||||
Widget build(BuildContext context) {
|
||||
return
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Accomodation Booking List",
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
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('#')),
|
||||
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(),
|
||||
),
|
||||
final isDesktop = MediaQuery.of(context).size.width > 1024;
|
||||
|
||||
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(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Header with title and button
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if ((!isDesktop) && widget.accommodationList.isNotEmpty)
|
||||
Text(
|
||||
" ",
|
||||
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),
|
||||
|
||||
// Scroll behavior based on device
|
||||
_buildData(context, isDesktop),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<DataRow> _buildDataRows() {
|
||||
|
||||
List<Map<String, dynamic>> filteredList = accommodationList
|
||||
.where((item) => item["is_active"] == "1")
|
||||
.toList();
|
||||
Widget _buildData(BuildContext context, bool isDesktop) {
|
||||
List<Map<String, dynamic>> filteredList =
|
||||
widget.accommodationList
|
||||
.where((item) => item["is_active"] == "1")
|
||||
.toList();
|
||||
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
|
||||
DataCell(Text(item["destination_city"]!)),
|
||||
DataCell(Text(item["hotel_name"]!)),
|
||||
DataCell(Text(item["checkin_date"]!)),
|
||||
DataCell(Text(item["checkout_date"]!)),
|
||||
DataCell(Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
|
||||
onPressed: () {
|
||||
// View action
|
||||
},
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.edit, color: Colors.green),
|
||||
onPressed: () {
|
||||
onOpen(true, item, "Accomodation");
|
||||
},
|
||||
),
|
||||
child: Padding(
|
||||
padding: isDesktop ? EdgeInsets.all(12) : EdgeInsets.all(8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
// 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), ),
|
||||
// ],
|
||||
// )
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.delete, color: Colors.red),
|
||||
onPressed: () {
|
||||
onDeleteAccommodation(item);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
),
|
||||
],
|
||||
)),
|
||||
]);
|
||||
}).toList();
|
||||
),
|
||||
),
|
||||
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 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:responsive_builder/responsive_builder.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../../config/apiUrl.dart';
|
||||
import '../../data/models/plan.dart';
|
||||
import '../../utils/auth_utils.dart';
|
||||
|
||||
|
||||
class BusListWidget extends StatelessWidget{
|
||||
class BusListWidget extends StatefulWidget {
|
||||
final List<Map<String, dynamic>> busList;
|
||||
final Function(bool, Map<String, dynamic>, String) onOpen;
|
||||
final Function(Map<String,dynamic>) onDeleteBus;
|
||||
const BusListWidget({super.key, required this.busList, required this.onOpen, required this.onDeleteBus});
|
||||
final Function(Map<String, dynamic>) 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
|
||||
Widget build(BuildContext context) {
|
||||
final isDesktop = MediaQuery.of(context).size.width > 1024;
|
||||
|
||||
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(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// Header with title and button
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if ((!isDesktop) && widget.busList.isNotEmpty)
|
||||
Text(
|
||||
" ",
|
||||
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),
|
||||
|
||||
// Scroll behavior based on device
|
||||
_buildData(context, isDesktop),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildData(BuildContext context, bool isDesktop) {
|
||||
List<Map<String, dynamic>> filteredList =
|
||||
widget.busList.where((item) => item["is_active"] == "1").toList();
|
||||
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";
|
||||
// }
|
||||
// }
|
||||
|
||||
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];
|
||||
|
||||
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: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
// 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(
|
||||
"Bus Booking List",
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
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('#')),
|
||||
DataColumn(label: Text('From')),
|
||||
DataColumn(label: Text('To')),
|
||||
DataColumn(label: Text('Date')),
|
||||
DataColumn(label: Text('Time')),
|
||||
|
||||
DataColumn(label: Text('Actions')),
|
||||
],
|
||||
rows: _buildDataRows(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<DataRow> _buildDataRows() {
|
||||
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
|
||||
};
|
||||
|
||||
List<Map<String, dynamic>> filteredList = busList
|
||||
.where((item) => item["is_active"] == "1")
|
||||
.toList();
|
||||
print("filteredList- $filteredList");
|
||||
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 filteredList.asMap().entries.map((entry) {
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
final Map<String, dynamic> item = entry.value;
|
||||
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
|
||||
};
|
||||
|
||||
return DataRow(cells: [
|
||||
// DataCell(Text(item["indx"]?.toString() ?? "N/A")), // Index column
|
||||
DataCell(Text(item["from"]!)),
|
||||
DataCell(Text(item["to"]!)),
|
||||
DataCell(Text(item["date"]!)),
|
||||
DataCell(Text(item["time"]!)),
|
||||
DataCell(Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
|
||||
onPressed: () {
|
||||
// View action
|
||||
},
|
||||
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,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.edit, color: Colors.green),
|
||||
onPressed: () {
|
||||
onOpen(true, item, "Bus");
|
||||
},
|
||||
),
|
||||
),
|
||||
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,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.delete, color: Colors.red),
|
||||
onPressed: () {
|
||||
onDeleteBus(item);
|
||||
},
|
||||
),
|
||||
],
|
||||
)),
|
||||
]);
|
||||
}).toList();
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text("$date, $time", style: GoogleFonts.poppins(fontSize: 12)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,94 +1,914 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class FlightListWidget extends StatelessWidget {
|
||||
final List<Map<String,dynamic>> flightList;
|
||||
final Function( bool,Map<String,dynamic>, String) onOpen;
|
||||
final Function(Map<String,dynamic>) onDeleteFlight;
|
||||
import '../../services/apiService.dart';
|
||||
import '../../utils/auth_utils.dart';
|
||||
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Flight Booking List",
|
||||
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
final isDesktop = MediaQuery.of(context).size.width > 1024;
|
||||
|
||||
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(),
|
||||
),
|
||||
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(
|
||||
padding:
|
||||
isDesktop ? const EdgeInsets.all(16.0) : const EdgeInsets.all(5.0),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
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(
|
||||
" ",
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
|
||||
],
|
||||
// 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() {
|
||||
|
||||
List<Map<String, dynamic>> filteredList = flightList
|
||||
.where((item) => item["is_active"] == "1")
|
||||
.toList();
|
||||
Widget _buildData(BuildContext context, bool isDesktop) {
|
||||
List<Map<String, dynamic>> filteredList =
|
||||
widget.flightList.where((item) => item["is_active"] == "1").toList();
|
||||
print("filteredList- $filteredList");
|
||||
|
||||
List<dynamic> visatypeList = widget.apiData?['flight_class'] ?? [];
|
||||
|
||||
return filteredList.asMap().entries.map((entry) {
|
||||
Map<String,dynamic> item = entry.value;
|
||||
print("Trip Type: ${item["trip_type"]}");
|
||||
String getRequestForClass(String? specialRequestKey) {
|
||||
if (specialRequestKey == null) return "N/A";
|
||||
|
||||
return visatypeList
|
||||
.firstWhere(
|
||||
(element) =>
|
||||
element["dropdown_key"].toString() == specialRequestKey,
|
||||
orElse: () => {"dropdown_value": "N/A"},
|
||||
)["dropdown_value"]
|
||||
.toString();
|
||||
}
|
||||
|
||||
return DataRow(cells: [
|
||||
// DataCell(Text(item["indx"]?.toString() ?? "N/A")),
|
||||
DataCell(Text(item["trip_type"]?.toString() ?? "N/A")),
|
||||
DataCell(Text(item["trips"].isNotEmpty ? item["trips"][0]["from_place"]?.toString() ?? "N/A" : "N/A")),
|
||||
DataCell(Text(item["trips"].isNotEmpty ? item["trips"][0]["to_place"]?.toString() ?? "N/A" : "N/A")),
|
||||
// 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";
|
||||
// }
|
||||
// }
|
||||
|
||||
DataCell(Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
|
||||
onPressed: () {
|
||||
// View action
|
||||
},
|
||||
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,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.edit, color: Colors.green),
|
||||
onPressed: () {
|
||||
onOpen(true, item, "Flight");
|
||||
},
|
||||
),
|
||||
child: Padding(
|
||||
padding: isDesktop ? EdgeInsets.all(12) : EdgeInsets.all(5),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 2),
|
||||
child: Row(
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
// 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(),
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.delete, color: Colors.red),
|
||||
onPressed: () {
|
||||
onDeleteFlight(item);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
]);
|
||||
}).toList();
|
||||
),
|
||||
Expanded(
|
||||
child: Text(value, style: GoogleFonts.poppins(fontSize: 12)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||