Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
Sai Srinivas
GEN_ERP_2025
Commits
6d1deaf2
Commit
6d1deaf2
authored
Aug 26, 2025
by
Mohit Kumar
Browse files
AttendanceList
RewardList TourExpenses Implementation
parent
55280429
Changes
57
Hide whitespace changes
Inline
Side-by-side
lib/screens/HomeScreen.dart
View file @
6d1deaf2
...
@@ -10,6 +10,7 @@ import 'package:flutter_svg/flutter_svg.dart';
...
@@ -10,6 +10,7 @@ import 'package:flutter_svg/flutter_svg.dart';
import
'package:generp/Utils/commonWidgets.dart'
;
import
'package:generp/Utils/commonWidgets.dart'
;
import
'../Utils/commonServices.dart'
;
import
'../Utils/commonServices.dart'
;
import
'genTracker/ScanEnterGeneratorIDScreen.dart'
;
import
'genTracker/ScanEnterGeneratorIDScreen.dart'
;
import
'hrm/HrmDashboardScreen.dart'
;
import
'notifierExports.dart'
;
import
'notifierExports.dart'
;
import
'screensExports.dart'
;
import
'screensExports.dart'
;
import
'package:geolocator/geolocator.dart'
;
import
'package:geolocator/geolocator.dart'
;
...
@@ -124,6 +125,8 @@ class _MyHomePageState extends State<MyHomePage> {
...
@@ -124,6 +125,8 @@ class _MyHomePageState extends State<MyHomePage> {
@override
@override
Widget
build
(
BuildContext
context
)
{
Widget
build
(
BuildContext
context
)
{
double
screenWidth
=
MediaQuery
.
of
(
context
).
size
.
width
;
double
screenHeight
=
MediaQuery
.
of
(
context
).
size
.
height
;
switch
(
_source
.
keys
.
toList
()[
0
])
{
switch
(
_source
.
keys
.
toList
()[
0
])
{
case
ConnectivityResult
.
mobile
:
case
ConnectivityResult
.
mobile
:
connection
=
'Online'
;
connection
=
'Online'
;
...
@@ -138,10 +141,10 @@ class _MyHomePageState extends State<MyHomePage> {
...
@@ -138,10 +141,10 @@ class _MyHomePageState extends State<MyHomePage> {
return
(
connection
==
'Online'
)
return
(
connection
==
'Online'
)
?
Consumer2
<
HomescreenNotifier
,
ProfileNotifer
>(
?
Consumer2
<
HomescreenNotifier
,
ProfileNotifer
>(
builder:
(
context
,
homescreen
,
profile
,
child
)
{
builder:
(
context
,
homescreen
,
profile
,
child
)
{
final
coreRequiredRoles
=
[
"12"
,
"540"
,
"433"
,
"434"
,
];
final
coreRequiredRoles
=
[
"12"
,
"540"
,
"433"
,
"434"
,
"430"
];
final
requiredRoles
=
[
"430"
,
"430"
,
"431"
,
"431"
];
final
requiredRoles
=
[
"430"
,
"430"
,
"431"
,
"431"
];
final
coreNames
=
[
"CRM"
,
"Orders"
,
"Service"
,
"Gen Tracker"
,
];
final
coreNames
=
[
"CRM"
,
"Orders"
,
"Service"
,
"Gen Tracker"
,
"HRM"
];
final
names
=
[
"Attendance"
,
"Finance"
,
"ERP"
,
"Whizzdom"
];
final
names
=
[
"Attendance"
,
"Finance"
,
"ERP"
,
"Whizzdom"
];
final
subtitles
=
[
final
subtitles
=
[
"Check-in,Check-out"
,
"Check-in,Check-out"
,
...
@@ -154,6 +157,7 @@ class _MyHomePageState extends State<MyHomePage> {
...
@@ -154,6 +157,7 @@ class _MyHomePageState extends State<MyHomePage> {
"assets/svg/home/home_order_ic.svg"
,
"assets/svg/home/home_order_ic.svg"
,
"assets/svg/home/home_service_ic.svg"
,
"assets/svg/home/home_service_ic.svg"
,
"assets/svg/home/home_gentracker_ic.svg"
,
"assets/svg/home/home_gentracker_ic.svg"
,
"assets/svg/home/home_erp_ic.svg"
,
];
];
final
icons
=
[
final
icons
=
[
...
@@ -167,6 +171,7 @@ class _MyHomePageState extends State<MyHomePage> {
...
@@ -167,6 +171,7 @@ class _MyHomePageState extends State<MyHomePage> {
"Orders, TPC, Dispatch"
,
"Orders, TPC, Dispatch"
,
"Visits, P.C. Wallet"
,
"Visits, P.C. Wallet"
,
"Generator Details"
,
"Generator Details"
,
"Tour Bills, Live Attendance"
,
];
];
final
coreFilteredItems
=
<
Map
<
String
,
String
>>[];
final
coreFilteredItems
=
<
Map
<
String
,
String
>>[];
final
filteredItems
=
<
Map
<
String
,
String
>>[];
final
filteredItems
=
<
Map
<
String
,
String
>>[];
...
@@ -211,23 +216,23 @@ class _MyHomePageState extends State<MyHomePage> {
...
@@ -211,23 +216,23 @@ class _MyHomePageState extends State<MyHomePage> {
toolbarHeight:
0
,
toolbarHeight:
0
,
backgroundColor:
Colors
.
white
,
backgroundColor:
Colors
.
white
,
),
),
body:
Container
(
body:
SingleChildScrollView
(
decoration:
BoxDecoration
(
child:
Container
(
gradient:
LinearGradient
(
decoration:
BoxDecoration
(
colors:
[
gradient:
LinearGradient
(
AppColors
.
scaffold_bg_color
,
colors:
[
AppColors
.
scaffold_bg_color
,
AppColors
.
scaffold_bg_color
,
Color
(
0xFFCEEDFF
),
AppColors
.
scaffold_bg_color
,
],
Color
(
0xFFCEEDFF
),
begin:
Alignment
.
topCenter
,
],
end:
Alignment
.
bottomCenter
,
begin:
Alignment
.
topCenter
,
end:
Alignment
.
bottomCenter
,
),
),
),
),
child:
Column
(
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
children:
[
children:
[
Expanded
(
InkResponse
(
flex:
4
,
child:
InkResponse
(
onTap:
()
{
onTap:
()
{
HapticFeedback
.
selectionClick
();
HapticFeedback
.
selectionClick
();
_showProfileBottomSheet
(
_showProfileBottomSheet
(
...
@@ -275,9 +280,9 @@ class _MyHomePageState extends State<MyHomePage> {
...
@@ -275,9 +280,9 @@ class _MyHomePageState extends State<MyHomePage> {
mainAxisAlignment:
mainAxisAlignment:
MainAxisAlignment
.
center
,
MainAxisAlignment
.
center
,
crossAxisAlignment:
CrossAxisAlignment
.
start
,
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
children:
[
// Text(
// Text(
// "${profile.employeeName}",
// "${profile.employeeName}",
// maxLines: 1,
// maxLines: 1,
...
@@ -331,7 +336,7 @@ class _MyHomePageState extends State<MyHomePage> {
...
@@ -331,7 +336,7 @@ class _MyHomePageState extends State<MyHomePage> {
mainAxisAlignment:
mainAxisAlignment:
MainAxisAlignment
.
start
,
MainAxisAlignment
.
start
,
children:
[
children:
[
Container
(
Container
(
width:
12
,
width:
12
,
height:
12
,
height:
12
,
...
@@ -353,8 +358,8 @@ class _MyHomePageState extends State<MyHomePage> {
...
@@ -353,8 +358,8 @@ class _MyHomePageState extends State<MyHomePage> {
color:
Color
(
0xFF2D2D2D
),
color:
Color
(
0xFF2D2D2D
),
),
),
),
),
],
],
),
),
],
],
...
@@ -396,7 +401,7 @@ class _MyHomePageState extends State<MyHomePage> {
...
@@ -396,7 +401,7 @@ class _MyHomePageState extends State<MyHomePage> {
),
),
),
),
),
),
],
],
),
),
),
),
...
@@ -406,10 +411,7 @@ class _MyHomePageState extends State<MyHomePage> {
...
@@ -406,10 +411,7 @@ class _MyHomePageState extends State<MyHomePage> {
),
),
),
),
),
),
),
SizedBox
(
Expanded
(
flex:
13
,
child:
SizedBox
(
child:
Column
(
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
children:
[
...
@@ -541,8 +543,8 @@ class _MyHomePageState extends State<MyHomePage> {
...
@@ -541,8 +543,8 @@ class _MyHomePageState extends State<MyHomePage> {
// ),
// ),
// ),
// ),
// ],
// ],
if
(
homescreen
.
roleStatus
.
contains
(
if
(
homescreen
.
roleStatus
.
contains
(
"432"
,
"432"
,
))
...[
))
...[
...
@@ -556,7 +558,7 @@ class _MyHomePageState extends State<MyHomePage> {
...
@@ -556,7 +558,7 @@ class _MyHomePageState extends State<MyHomePage> {
left:
10
,
left:
10
,
bottom:
5
,
bottom:
5
,
top:
10
top:
10
),
),
child:
Text
(
child:
Text
(
"Workforce & Operations"
,
"Workforce & Operations"
,
...
@@ -723,7 +725,7 @@ class _MyHomePageState extends State<MyHomePage> {
...
@@ -723,7 +725,7 @@ class _MyHomePageState extends State<MyHomePage> {
left:
10
,
left:
10
,
bottom:
5
,
bottom:
5
,
top:
10
top:
10
),
),
padding:
const
EdgeInsets
.
only
(
padding:
const
EdgeInsets
.
only
(
top:
10
,
top:
10
,
...
@@ -950,7 +952,7 @@ class _MyHomePageState extends State<MyHomePage> {
...
@@ -950,7 +952,7 @@ class _MyHomePageState extends State<MyHomePage> {
);
);
}
}
},
},
child:
Container
(
child:
Container
(
padding:
EdgeInsets
.
symmetric
(
padding:
EdgeInsets
.
symmetric
(
vertical:
5
,
vertical:
5
,
...
@@ -1027,7 +1029,7 @@ class _MyHomePageState extends State<MyHomePage> {
...
@@ -1027,7 +1029,7 @@ class _MyHomePageState extends State<MyHomePage> {
),
),
],
],
],
],
// if (filteredItems.isNotEmpty) ...[
// if (filteredItems.isNotEmpty) ...[
// Container(
// Container(
// margin: EdgeInsets.symmetric(
// margin: EdgeInsets.symmetric(
...
@@ -1235,7 +1237,7 @@ class _MyHomePageState extends State<MyHomePage> {
...
@@ -1235,7 +1237,7 @@ class _MyHomePageState extends State<MyHomePage> {
// ),
// ),
// ),
// ),
// ],
// ],
if
(
coreFilteredItems
.
isNotEmpty
)
...[
if
(
coreFilteredItems
.
isNotEmpty
)
...[
Container
(
Container
(
margin:
EdgeInsets
.
only
(
margin:
EdgeInsets
.
only
(
...
@@ -1263,6 +1265,7 @@ class _MyHomePageState extends State<MyHomePage> {
...
@@ -1263,6 +1265,7 @@ class _MyHomePageState extends State<MyHomePage> {
child:
GridView
.
builder
(
child:
GridView
.
builder
(
shrinkWrap:
true
,
shrinkWrap:
true
,
itemCount:
coreFilteredItems
.
length
,
itemCount:
coreFilteredItems
.
length
,
physics:
NeverScrollableScrollPhysics
(),
gridDelegate:
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount
(
SliverGridDelegateWithFixedCrossAxisCount
(
crossAxisCount:
2
,
crossAxisCount:
2
,
...
@@ -1332,6 +1335,19 @@ class _MyHomePageState extends State<MyHomePage> {
...
@@ -1332,6 +1335,19 @@ class _MyHomePageState extends State<MyHomePage> {
),
),
),
),
);
);
case
"HRM"
:
res
=
await
Navigator
.
push
(
context
,
MaterialPageRoute
(
builder:
(
context
)
=>
HrmdashboardScreen
(),
settings:
RouteSettings
(
name:
'CrmdashboardScreen'
,
),
),
);
default
:
default
:
print
(
"111"
);
print
(
"111"
);
break
;
break
;
...
@@ -1342,7 +1358,7 @@ class _MyHomePageState extends State<MyHomePage> {
...
@@ -1342,7 +1358,7 @@ class _MyHomePageState extends State<MyHomePage> {
);
);
}
}
},
},
child:
Container
(
child:
Container
(
padding:
EdgeInsets
.
symmetric
(
padding:
EdgeInsets
.
symmetric
(
vertical:
5
,
vertical:
5
,
...
@@ -1406,7 +1422,7 @@ class _MyHomePageState extends State<MyHomePage> {
...
@@ -1406,7 +1422,7 @@ class _MyHomePageState extends State<MyHomePage> {
Expanded
(
Expanded
(
flex:
1
,
flex:
1
,
child:
SvgPicture
.
asset
(
child:
SvgPicture
.
asset
(
f
ilteredItems
[
ci
][
'icon'
]
??
coreF
ilteredItems
[
ci
][
'icon'
]
??
"-"
,
"-"
,
),
),
),
),
...
@@ -1418,241 +1434,241 @@ class _MyHomePageState extends State<MyHomePage> {
...
@@ -1418,241 +1434,241 @@ class _MyHomePageState extends State<MyHomePage> {
),
),
),
),
],
],
],
],
),
),
),
),
),
Align
(
Align
(
alignment:
Alignment
.
bottomCenter
,
child:
Container
(
height:
40
,
alignment:
Alignment
.
bottomCenter
,
alignment:
Alignment
.
bottomCenter
,
margin:
EdgeInsets
.
only
(
bottom:
20
),
child:
Container
(
child:
Image
.
asset
(
height:
40
,
fit:
BoxFit
.
scaleDown
,
alignment:
Alignment
.
bottomCenter
,
"assets/images/horizontal_logo.png"
,
margin:
EdgeInsets
.
only
(
bottom:
20
),
child:
Image
.
asset
(
fit:
BoxFit
.
scaleDown
,
"assets/images/horizontal_logo.png"
,
),
),
),
),
),
),
// Expanded(
//
Expanded(
//
flex: 10,
//
flex: 10,
//
child: Container(
//
child: Container
(
//
padding: EdgeInsets.only
(
//
padding: EdgeInsets.only(
//
left: 20,
//
lef
t: 20,
//
righ
t: 20,
//
right
:
2
0,
//
top
: 0,
// to
p
: 0,
//
bot
to
m
:
1
0,
//
bottom: 10
,
//
)
,
// ),
//
margin: EdgeInsets.only(top: 10
),
//
margin: EdgeInsets.only(top: 10),
//
child: GridView.builder(
//
child: GridView.builder(
//
itemCount: filteredItems.length,
//
itemCount: filteredItems.length,
//
gridDelegate:
//
gridDelegate:
//
SliverGridDelegateWithFixedCrossAxisCount(
//
SliverGridDelegateWithFixedC
rossAxisCount
(
//
c
rossAxisCount
: 2,
// crossAxis
Count: 2
,
// crossAxis
Spacing: 10
,
//
cross
AxisSpacing: 10,
//
main
AxisSpacing: 10,
//
mainAxisSpacing: 10
,
//
)
,
//
),
//
itemBuilder: (context, index) {
// item
Bu
il
d
er
: (context,
index
) {
//
final
item
= f
il
t
er
edItems[
index
];
//
final item = filteredItems[index];
//
return InkResponse(
//
return InkResponse(
//
onTap: () async {
//
onTap: () async {
//
var res;
//
var res;
//
switch (item['name']) {
//
switch (item['name']) {
//
case "Attendance":
//
case "Attendance":
//
res = await Navigator.push(
//
res = await Navigator.push(
//
context,
//
context,
//
MaterialPageRoute(
//
MaterialPageRoute(
//
builder:
//
builder:
//
(context) =>
//
(context) =>
//
AttendanceScreen(),
//
AttendanceScreen(),
//
settings: RouteSettings(
//
settings: RouteSettings(
//
arguments: 'AttendanceScreen',
//
arguments: 'AttendanceScreen'
,
//
)
,
//
),
//
),
//
),
//
);
//
)
;
//
break
;
//
break;
//
case "ERP":
//
case "ERP":
//
bool isGpsEnabled =
//
bool isGps
Enabled
=
//
await Geolocator.isLocationService
Enabled
();
//
await Geolocator.isLocationService
Enabled
();
//
if (isGps
Enabled
) {
//
if (
isGpsEnable
d) {
//
if (
Platform.isAndroi
d) {
//
if (Pl
at
f
or
m.isAndroid) {
//
res = await Navig
ator
.push(
//
res = await Navigator.push(
//
context,
//
context,
//
MaterialPageRoute(
//
MaterialPageRoute(
//
builder:
//
builder:
//
(context) => WebErpScreen(
//
(context) => WebErpScreen(
//
erp_url:
//
erp_url:
//
homescreen
//
homescreen
//
.webPageUrl,
//
.webPageUrl
,
//
)
,
//
),
//
),
//
),
//
);
//
);
//
} else {
//
} else {
//
res = await Navigator.push(
//
res = await Navigator.push(
//
context,
//
context,
//
MaterialPageRoute(
//
MaterialPageRoute(
//
builder:
//
builder:
//
(context) => WebERPIOS(
//
(context) => WebERPIOS(
//
url:
//
url:
//
homescreen
//
homescreen
//
.webPageUrl,
//
.webPageUrl
,
//
)
,
//
),
//
),
//
),
//
);
//
);
//
}
//
}
//
} else {
//
} else {
//
requestGpsPermission();
//
requestGpsPermission();
//
}
//
}
//
//
// break;
//
break;
//
case "Gen Tracker":
//
case "Gen Tracker":
//
res = await Navigator.push(
//
res = await Navigator.push(
//
context,
//
context,
//
MaterialPageRoute(
//
MaterialPageRoute(
//
builder:
//
builder:
//
(context) =>
//
(context) =>
//
Gentrackerdashboard(),
//
Gentrackerdashboard(),
//
settings: RouteSettings(
//
settings: RouteSettings(
//
arguments:
//
arguments:
//
'Gentrackerdashboard',
//
'Gentrackerdashboard'
,
//
)
,
//
),
//
),
//
),
//
);
//
)
;
//
break
;
//
break;
//
case "Service Engineer":
//
case "Service Engineer":
//
res = await Navigator.push(
//
res = await Navigator.push(
//
context,
//
context,
//
MaterialPageRoute(
//
MaterialPageRoute(
//
builder:
//
builder:
//
(context) =>
//
(context) =>
//
Serviceengineerdashboard(),
//
Serviceengineerdashboard(
),
// ),
//
),
//
);
//
)
;
//
break
;
//
break;
//
case "Nearby":
//
case "Nearby":
//
res = await Navigator.push(
//
res = await Navigator.push(
//
context,
//
context,
//
MaterialPageRoute(
//
MaterialPageRoute(
//
builder:
//
builder:
//
(context) =>
//
(context) =>
//
Nearbygenerators(),
//
Nearbygenerators(
),
// ),
//
),
//
);
//
);
//
//
// break;
//
break;
//
case "Inventory":
//
case "Inventory":
//
res = await Navigator.push(
//
res = await Navigator.push(
//
context,
//
context,
//
MaterialPageRoute(
//
MaterialPageRoute(
//
builder:
//
builder:
//
(context) =>
//
(context) =>
//
InventoryScreen(),
//
InventoryScreen(
),
// ),
//
),
//
);
//
)
;
//
break
;
//
break;
//
case "Whizzdom":
//
case "Whizzdom":
//
bool isGpsEnabled =
//
bool isGps
Enabled
=
//
await Geolocator.isLocationService
Enabled
();
//
await Geolocator.isLocationService
Enabled
();
//
if (isGps
Enabled
) {
//
if (isGpsEnabled) {
//
res = await Navigator.push(
//
res = await Navigator.push(
//
context,
//
context,
//
MaterialPageRoute(
//
MaterialPageRoute(
//
builder:
//
builder:
//
(
//
(
//
context,
//
context,
//
) => WebWhizzdomScreen(
//
) => WebWhizzdomScreen(
//
whizzdom_url:
//
whizzdom_url:
//
homescreen
//
homescreen
//
.whizzdomPageUrl,
//
.whizzdomPageUrl
,
//
)
,
//
),
//
),
//
),
//
);
//
);
//
} else {
//
} else {
//
requestGpsPermission();
//
requestGpsPermission();
//
}
//
}
//
break;
//
break;
//
case "Common":
//
case "Common":
//
res = await Navigator.push(
//
res = await Navigator.push(
//
context,
//
context,
//
MaterialPageRoute(
//
MaterialPageRoute(
//
builder:
//
builder:
//
(context) =>
//
(context) =>
//
Commondashboard(),
//
Commondashboard(
),
// ),
//
),
//
);
//
)
;
//
break
;
//
break;
//
case "Finance":
//
case "Finance":
//
res = await Navigator.push(
//
res = await Navigator.push(
//
context,
//
context,
//
MaterialPageRoute(
//
MaterialPageRoute(
//
builder:
//
builder:
//
(context) =>
//
(context) =>
//
Financedashboard(),
//
Financedashboard(),
//
settings: RouteSettings(
//
settings: RouteSettings(
//
arguments: 'Financedashboard',
//
arguments: 'Financedashboard'
,
//
)
,
//
),
//
),
//
),
//
);
//
)
;
//
break
;
//
break;
//
case "Orders":
//
case "Orders":
//
res = await Navigator.push(
//
res = await Navigator.push(
//
context,
//
context,
//
MaterialPageRoute(
//
MaterialPageRoute(
//
builder:
//
builder:
//
(context) =>
//
(context) =>
//
Ordermoduledashboard(),
//
Ordermoduledashboard(
),
// ),
//
),
//
);
//
);
//
case "CRM":
//
case "CRM":
//
res = await Navigator.push(
//
res = await Navigator.push(
//
context,
//
context,
//
MaterialPageRoute(
//
MaterialPageRoute(
//
builder:
//
builder:
//
(context) =>
//
(context) =>
//
CrmdashboardScreen(),
//
CrmdashboardScreen(),
//
settings: RouteSettings(
//
settings: RouteSettings(
//
name: 'CrmdashboardScreen',
//
name: 'CrmdashboardScreen'
,
//
)
,
//
),
//
),
//
),
//
);
//
);
//
default:
//
default:
//
print("111");
//
print("111")
;
//
break
;
//
break;
//
}
//
}
//
if (res == true) {
//
if (res == true) {
//
homescreen.DashboardApiFunction(
//
homescreen.DashboardApiFunction(
//
context,
//
context,
//
);
//
);
//
}
//
}
// }
,
//
},
//
child: Container(
//
child: Container
(
//
decoration: BoxDecoration
(
//
decoration: BoxDecoration(
//
color: Colors.white,
//
color: Colors.white
,
//
borderRadius: BorderRadius.circular(30)
,
//
borderRadius: BorderRadius.circular(30
),
// ),
//
),
//
child: Column(
//
child: Column(
//
crossAxisAlignment:
//
c
rossAxisAlignment
:
//
C
rossAxisAlignment
.center,
//
Cross
AxisAlignment
.center,
//
main
AxisAlignment
:
//
m
ainAxisAlignment
:
//
M
ainAxisAlignment
.center,
//
MainAxisAlignment.center,
//
children: [
//
children: [
//
SvgPicture.asset(
//
SvgPicture.asset(
//
item['icon']!,
//
item['icon']!
,
//
height: 45
,
//
height: 45
,
//
)
,
// ),
//
SizedBox(height: 10
),
//
SizedBox(height: 10
),
//
Text(item['name']!
),
//
Text(item['name']!)
,
//
]
,
//
]
,
//
)
,
//
),
//
),
//
),
//
);
//
);
//
},
//
}
,
//
)
,
//
),
//
),
//
),
//
),
// )
,
]
,
]
,
)
,
),
),
),
),
// floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
// floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
...
...
lib/screens/hrm/AddLiveAttendance.dart
0 → 100644
View file @
6d1deaf2
import
'dart:io'
;
import
'package:flutter/material.dart'
;
import
'package:flutter_svg/svg.dart'
;
import
'package:dropdown_button2/dropdown_button2.dart'
;
import
'package:geolocator/geolocator.dart'
;
import
'package:image_picker/image_picker.dart'
;
import
'package:permission_handler/permission_handler.dart'
;
import
'../../Utils/app_colors.dart'
;
import
'../../Utils/dropdownTheme.dart'
;
class
AddLiveAttendanceScreen
extends
StatefulWidget
{
const
AddLiveAttendanceScreen
({
Key
?
key
})
:
super
(
key:
key
);
@override
State
<
AddLiveAttendanceScreen
>
createState
()
=>
_AddLiveAttendanceScreenState
();
}
class
_AddLiveAttendanceScreenState
extends
State
<
AddLiveAttendanceScreen
>
{
String
?
selectedType
;
Dropdowntheme
ddtheme
=
Dropdowntheme
();
final
TextEditingController
locationController
=
TextEditingController
();
final
TextEditingController
descriptionController
=
TextEditingController
();
final
List
<
String
>
types
=
[
"Check In"
,
"Check Out"
];
final
ImagePicker
picker
=
ImagePicker
();
XFile
?
proofFile
;
// store selected proof
// computed labels
String
get
locationHeading
=>
selectedType
==
null
?
"Location"
:
"
$selectedType
Location"
;
String
get
descriptionHeading
=>
selectedType
==
null
?
"Description"
:
"
$selectedType
Description"
;
String
get
proofButtonText
=>
selectedType
==
null
?
"Attach Proof"
:
"Attach
$selectedType
Proof"
;
bool
get
isSubmitEnabled
=>
selectedType
!=
null
&&
locationController
.
text
.
trim
().
isNotEmpty
&&
proofFile
!=
null
;
// proof is required
@override
void
initState
()
{
super
.
initState
();
locationController
.
addListener
(()
{
setState
(()
{});
});
}
@override
void
dispose
()
{
locationController
.
dispose
();
descriptionController
.
dispose
();
super
.
dispose
();
}
void
_showPicker
(
BuildContext
context
)
{
showModalBottomSheet
(
context:
context
,
builder:
(
BuildContext
bc
)
{
return
SafeArea
(
child:
Wrap
(
children:
<
Widget
>[
ListTile
(
leading:
const
Icon
(
Icons
.
photo_camera
),
title:
const
Text
(
'Camera'
),
onTap:
()
async
{
Navigator
.
of
(
context
).
pop
();
final
XFile
?
image
=
await
picker
.
pickImage
(
source
:
ImageSource
.
camera
);
if
(
image
!=
null
)
{
setState
(()
=>
proofFile
=
image
);
}
},
),
ListTile
(
leading:
const
Icon
(
Icons
.
photo_library
),
title:
const
Text
(
'Gallery'
),
onTap:
()
async
{
Navigator
.
of
(
context
).
pop
();
final
XFile
?
image
=
await
picker
.
pickImage
(
source
:
ImageSource
.
gallery
);
if
(
image
!=
null
)
{
setState
(()
=>
proofFile
=
image
);
}
},
),
],
),
);
},
);
}
Future
<
String
>
getCurrentLocation
()
async
{
var
status
=
await
Permission
.
location
.
request
();
if
(
status
.
isGranted
)
{
Position
position
=
await
Geolocator
.
getCurrentPosition
(
desiredAccuracy:
LocationAccuracy
.
high
,
);
return
"
${position.latitude.toStringAsFixed(7)}
,
${position.longitude.toStringAsFixed(7)}
"
;
}
else
{
return
"Permission denied"
;
}
}
/// New: submit function
void
submitAttendance
()
{
print
(
"==== Attendance Submitted ===="
);
print
(
"Type:
$selectedType
"
);
print
(
"Location:
${locationController.text}
"
);
print
(
"Description:
${descriptionController.text}
"
);
print
(
"Proof:
${proofFile?.path ?? 'No file'}
"
);
print
(
"============================="
);
}
@override
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
backgroundColor:
Colors
.
white
,
appBar:
AppBar
(
automaticallyImplyLeading:
false
,
backgroundColor:
const
Color
(
0xFEFFFFFF
),
title:
Row
(
children:
[
InkResponse
(
onTap:
()
=>
Navigator
.
pop
(
context
,
true
),
child:
SvgPicture
.
asset
(
"assets/svg/appbar_back_button.svg"
,
height:
25
,
),
),
const
SizedBox
(
width:
10
),
Text
(
"Add Live Attendance"
,
style:
TextStyle
(
fontSize:
18
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w600
,
color:
AppColors
.
semi_black
,
),
),
],
),
),
body:
SingleChildScrollView
(
padding:
const
EdgeInsets
.
all
(
18
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
/// Type Dropdown
const
Text
(
"Type"
,
style:
TextStyle
(
fontSize:
15
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w500
)),
const
SizedBox
(
height:
6
),
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
2
),
decoration:
BoxDecoration
(
color:
Colors
.
grey
.
shade100
,
borderRadius:
BorderRadius
.
circular
(
8
),
),
child:
DropdownButtonHideUnderline
(
child:
DropdownButton2
<
String
>(
isExpanded:
true
,
hint:
const
Text
(
"Select Type"
,
overflow:
TextOverflow
.
ellipsis
,
style:
TextStyle
(
fontSize:
15
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w400
),
),
value:
selectedType
,
items:
types
.
map
((
e
)
=>
DropdownMenuItem
<
String
>(
value:
e
,
child:
Text
(
e
,
style:
TextStyle
(
fontSize:
14
,
),
overflow:
TextOverflow
.
ellipsis
,
)
))
.
toList
(),
onChanged:
(
val
)
=>
setState
(()
=>
selectedType
=
val
),
// buttonStyleData: ddtheme.buttonStyleData,
iconStyleData:
ddtheme
.
iconStyleData
,
// menuItemStyleData: ddtheme.menuItemStyleData,
dropdownStyleData:
ddtheme
.
dropdownStyleData
,
),
),
),
const
SizedBox
(
height:
16
),
/// Location field
Text
(
locationHeading
,
style:
const
TextStyle
(
fontSize:
15
,
fontFamily:
"Plus Jakarta Sans"
,
fontStyle:
FontStyle
.
normal
,
fontWeight:
FontWeight
.
w500
)),
const
SizedBox
(
height:
6
),
TextField
(
controller:
locationController
,
readOnly:
true
,
onTap:
()
async
{
String
loc
=
await
getCurrentLocation
();
locationController
.
text
=
loc
;
},
decoration:
_inputDecoration
(
"Tap to get location"
),
),
const
SizedBox
(
height:
16
),
/// Description
Text
(
descriptionHeading
,
style:
const
TextStyle
(
fontSize:
15
,
fontFamily:
"Plus Jakarta Sans"
,
fontStyle:
FontStyle
.
normal
,
fontWeight:
FontWeight
.
w500
)
),
const
SizedBox
(
height:
6
),
TextField
(
controller:
descriptionController
,
maxLines:
3
,
decoration:
_inputDecoration
(
"Write Description"
),
),
const
SizedBox
(
height:
20
),
/// Attach Proof
SizedBox
(
width:
double
.
infinity
,
child:
OutlinedButton
(
onPressed:
()
=>
_showPicker
(
context
),
style:
OutlinedButton
.
styleFrom
(
backgroundColor:
Colors
.
blue
.
shade50
,
side:
BorderSide
(
color:
Colors
.
blue
.
shade200
),
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
14
),
),
padding:
const
EdgeInsets
.
symmetric
(
vertical:
14
),
),
child:
Text
(
proofButtonText
,
style:
const
TextStyle
(
fontSize:
16
,
color:
Colors
.
blue
,
fontFamily:
"Plus Jakarta Sans"
,
fontStyle:
FontStyle
.
normal
,
fontWeight:
FontWeight
.
w500
)),
),
),
/// Show proof preview
if
(
proofFile
!=
null
)
...[
const
SizedBox
(
height:
10
),
Row
(
children:
[
const
Icon
(
Icons
.
check_circle
,
color:
Colors
.
green
),
const
SizedBox
(
width:
8
),
Expanded
(
child:
Text
(
"Attached:
${proofFile!.name}
"
,
overflow:
TextOverflow
.
ellipsis
)),
IconButton
(
icon:
const
Icon
(
Icons
.
close
,
color:
Colors
.
red
),
onPressed:
()
=>
setState
(()
=>
proofFile
=
null
),
),
],
)
],
const
SizedBox
(
height:
24
),
/// Submit button
SizedBox
(
width:
double
.
infinity
,
child:
ElevatedButton
(
onPressed:
isSubmitEnabled
?
submitAttendance
:
null
,
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
isSubmitEnabled
?
Colors
.
blue
:
Colors
.
grey
.
shade400
,
padding:
const
EdgeInsets
.
symmetric
(
vertical:
14
),
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
8
)),
),
child:
const
Text
(
"Submit"
,
style:
TextStyle
(
fontSize:
16
,
color:
Colors
.
white
,
fontWeight:
FontWeight
.
w500
)),
),
),
],
),
),
);
}
InputDecoration
_inputDecoration
(
String
hint
)
{
return
InputDecoration
(
hintText:
hint
,
hintStyle:
const
TextStyle
(
fontSize:
14
,
fontFamily:
"Plus Jakarta Sans"
,
fontStyle:
FontStyle
.
normal
,
fontWeight:
FontWeight
.
w400
,
color:
Colors
.
grey
,
),
filled:
true
,
fillColor:
Colors
.
grey
.
shade100
,
enabledBorder:
OutlineInputBorder
(
borderRadius:
BorderRadius
.
circular
(
8
),
borderSide:
BorderSide
.
none
,
),
focusedBorder:
OutlineInputBorder
(
borderRadius:
BorderRadius
.
circular
(
8
),
borderSide:
const
BorderSide
(
color:
Colors
.
blue
),
),
);
}
}
lib/screens/hrm/AddManualAttendance.dart
0 → 100644
View file @
6d1deaf2
import
'package:flutter/cupertino.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter_svg/svg.dart'
;
import
'package:dropdown_button2/dropdown_button2.dart'
;
import
'package:image_picker/image_picker.dart'
;
import
'../../Utils/app_colors.dart'
;
import
'../../Utils/dropdownTheme.dart'
;
class
AddManualAttendanceScreen
extends
StatefulWidget
{
const
AddManualAttendanceScreen
({
super
.
key
});
@override
State
<
AddManualAttendanceScreen
>
createState
()
=>
_AddManualAttendanceScreenState
();
}
class
_AddManualAttendanceScreenState
extends
State
<
AddManualAttendanceScreen
>
{
final
TextEditingController
dateController
=
TextEditingController
();
Dropdowntheme
ddtheme
=
Dropdowntheme
();
// Separate controllers for each section
final
checkInTime
=
TextEditingController
();
final
checkInLocation
=
TextEditingController
();
final
checkInDescription
=
TextEditingController
();
XFile
?
checkInProof
;
final
checkOutTime
=
TextEditingController
();
final
checkOutLocation
=
TextEditingController
();
final
checkOutDescription
=
TextEditingController
();
XFile
?
checkOutProof
;
final
ImagePicker
picker
=
ImagePicker
();
String
?
selectedType
;
final
List
<
String
>
types
=
[
"Check In"
,
"Check Out"
,
"Check In/Out"
];
DateTime
?
_date
;
bool
get
isSubmitEnabled
{
if
(
selectedType
==
"Check In"
)
{
return
checkInLocation
.
text
.
trim
().
isNotEmpty
;
}
else
if
(
selectedType
==
"Check Out"
)
{
return
checkOutLocation
.
text
.
trim
().
isNotEmpty
;
}
else
if
(
selectedType
==
"Check In/Out"
)
{
return
checkInLocation
.
text
.
trim
().
isNotEmpty
&&
checkOutLocation
.
text
.
trim
().
isNotEmpty
;
}
return
false
;
}
@override
void
initState
()
{
super
.
initState
();
checkInLocation
.
addListener
(()
=>
setState
(()
{}));
checkOutLocation
.
addListener
(()
=>
setState
(()
{}));
}
// ===== Print all submitted values =====
void
_submitForm
()
{
print
(
"===== Manual Attendance Submitted ====="
);
print
(
"Date:
${dateController.text}
"
);
print
(
"Type:
$selectedType
"
);
print
(
"Time:
${checkInTime.text}
"
);
print
(
"Location:
${checkInLocation.text}
"
);
print
(
"Description:
${checkInDescription.text}
"
);
print
(
"Proof:
${checkInProof != null ? checkOutProof!.path : 'No file attached'}
"
);
print
(
"======================================="
);
print
(
"Date:
${dateController.text}
"
);
print
(
"Type:
$selectedType
"
);
print
(
"Time:
${checkOutTime.text}
"
);
print
(
"Location:
${checkOutLocation.text}
"
);
print
(
"Description:
${checkOutDescription.text}
"
);
print
(
"Proof:
${checkInProof != null ? checkOutProof!.path : 'No file attached'}
"
);
}
// ===== Pick File =====
Future
<
void
>
_pickFile
(
bool
isCheckIn
)
async
{
final
XFile
?
file
=
await
picker
.
pickImage
(
source
:
ImageSource
.
gallery
);
if
(
file
!=
null
)
{
setState
(()
{
if
(
isCheckIn
)
{
checkInProof
=
file
;
}
else
{
checkOutProof
=
file
;
}
});
}
}
void
setDate
(
DateTime
newDate
)
{
_date
=
newDate
;
dateController
.
text
=
"
${newDate.day}
-
${newDate.month}
-
${newDate.year}
"
;
}
Future
<
void
>
_pickTime
(
TextEditingController
controller
)
async
{
final
TimeOfDay
?
picked
=
await
showTimePicker
(
context:
context
,
initialTime:
TimeOfDay
.
now
());
if
(
picked
!=
null
)
{
controller
.
text
=
picked
.
format
(
context
);
}
}
@override
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
backgroundColor:
Colors
.
white
,
appBar:
AppBar
(
automaticallyImplyLeading:
false
,
backgroundColor:
const
Color
(
0xFEFFFFFF
),
title:
Row
(
children:
[
InkResponse
(
onTap:
()
=>
Navigator
.
pop
(
context
,
true
),
child:
SvgPicture
.
asset
(
"assets/svg/appbar_back_button.svg"
,
height:
25
),
),
const
SizedBox
(
width:
10
),
Text
(
"Add Manual Attendance"
,
style:
TextStyle
(
fontSize:
18
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w600
,
color:
AppColors
.
semi_black
,
)),
],
),
),
body:
Padding
(
padding:
const
EdgeInsets
.
all
(
18
),
child:
SingleChildScrollView
(
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
/// Date
_buildLabel
(
"Date"
),
const
SizedBox
(
height:
6
),
TextField
(
controller:
dateController
,
readOnly:
true
,
onTap:
()
=>
setDate
(
DateTime
.
now
()),
decoration:
_inputDecoration
(
"Select Date"
)
.
copyWith
(
suffixIcon:
const
Icon
(
Icons
.
calendar_today
)),
),
const
SizedBox
(
height:
16
),
/// Type Dropdown
_buildLabel
(
"Type"
),
const
SizedBox
(
height:
6
),
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
2
),
decoration:
BoxDecoration
(
color:
Colors
.
grey
.
shade100
,
borderRadius:
BorderRadius
.
circular
(
8
),
),
child:
DropdownButtonHideUnderline
(
child:
DropdownButton2
<
String
>(
isExpanded:
true
,
hint:
const
Text
(
"Select Type"
),
value:
selectedType
,
items:
types
.
map
((
e
)
=>
DropdownMenuItem
<
String
>(
value:
e
,
child:
Text
(
e
),
))
.
toList
(),
onChanged:
(
val
)
=>
setState
(()
=>
selectedType
=
val
),
// buttonStyleData: ddtheme.buttonStyleData,
iconStyleData:
ddtheme
.
iconStyleData
,
// menuItemStyleData: ddtheme.menuItemStyleData,
dropdownStyleData:
ddtheme
.
dropdownStyleData
,
),
),
),
const
SizedBox
(
height:
20
),
/// Conditional Sections
if
(
selectedType
==
"Check In"
)
_buildSection
(
title:
"Check In"
,
timeController:
checkInTime
,
locationController:
checkInLocation
,
descriptionController:
checkInDescription
,
proofFile:
checkInProof
,
onPickProof:
()
=>
_pickFile
(
true
),
),
if
(
selectedType
==
"Check Out"
)
_buildSection
(
title:
"Check Out"
,
timeController:
checkOutTime
,
locationController:
checkOutLocation
,
descriptionController:
checkOutDescription
,
proofFile:
checkOutProof
,
onPickProof:
()
=>
_pickFile
(
false
),
),
if
(
selectedType
==
"Check In/Out"
)
...[
_buildSection
(
title:
"Check In"
,
timeController:
checkInTime
,
locationController:
checkInLocation
,
descriptionController:
checkInDescription
,
proofFile:
checkInProof
,
onPickProof:
()
=>
_pickFile
(
true
),
),
_buildSection
(
title:
"Check Out"
,
timeController:
checkOutTime
,
locationController:
checkOutLocation
,
descriptionController:
checkOutDescription
,
proofFile:
checkOutProof
,
onPickProof:
()
=>
_pickFile
(
false
),
),
],
const
SizedBox
(
height:
24
),
/// Submit Button
SizedBox
(
width:
double
.
infinity
,
child:
ElevatedButton
(
style:
ElevatedButton
.
styleFrom
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
14
),
backgroundColor:
isSubmitEnabled
?
Colors
.
blue
:
Colors
.
grey
.
shade400
,
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
8
)),
),
onPressed:
isSubmitEnabled
?
()
{
_submitForm
();
print
(
"Submit pressed for
$selectedType
"
);
}
:
null
,
child:
const
Text
(
"Submit"
,
style:
TextStyle
(
fontSize:
16
,
color:
Colors
.
white
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w500
,
)),
),
),
],
),
),
),
);
}
/// Reusable Section
Widget
_buildSection
({
required
String
title
,
required
TextEditingController
timeController
,
required
TextEditingController
locationController
,
required
TextEditingController
descriptionController
,
required
XFile
?
proofFile
,
required
VoidCallback
onPickProof
,
})
{
return
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
_buildLabel
(
"
$title
Time"
),
const
SizedBox
(
height:
6
),
TextField
(
controller:
timeController
,
readOnly:
true
,
onTap:
()
=>
_pickTime
(
timeController
),
decoration:
_inputDecoration
(
"Select Time"
)
.
copyWith
(
suffixIcon:
const
Icon
(
Icons
.
access_time
)),
),
const
SizedBox
(
height:
16
),
_buildLabel
(
"
$title
Location"
),
const
SizedBox
(
height:
6
),
TextField
(
controller:
locationController
,
decoration:
_inputDecoration
(
"Enter Location"
),
),
const
SizedBox
(
height:
16
),
_buildLabel
(
"
$title
Description"
),
const
SizedBox
(
height:
6
),
TextField
(
controller:
descriptionController
,
maxLines:
3
,
decoration:
_inputDecoration
(
"Write Description"
),
),
const
SizedBox
(
height:
18
),
/// Proof
SizedBox
(
width:
double
.
infinity
,
child:
OutlinedButton
(
onPressed:
onPickProof
,
style:
OutlinedButton
.
styleFrom
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
14
),
backgroundColor:
Colors
.
blue
.
shade50
,
side:
BorderSide
(
color:
Colors
.
blue
.
shade200
),
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
14
)),
),
child:
Text
(
"Attach
$title
Proof"
,
style:
const
TextStyle
(
fontSize:
16
,
color:
Colors
.
blue
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w500
)),
),
),
// Show proof preview
if
(
proofFile
!=
null
)
...[
const
SizedBox
(
height:
10
),
Row
(
children:
[
const
Icon
(
Icons
.
check_circle
,
color:
Colors
.
green
),
const
SizedBox
(
width:
8
),
Expanded
(
child:
Text
(
"Attached:
${proofFile!.name}
"
,
overflow:
TextOverflow
.
ellipsis
)),
IconButton
(
icon:
const
Icon
(
Icons
.
close
,
color:
Colors
.
red
),
onPressed:
()
=>
setState
(()
=>
proofFile
=
null
),
),
],
)
],
const
SizedBox
(
height:
24
),
],
);
}
Widget
_buildLabel
(
String
text
)
=>
Text
(
text
,
style:
const
TextStyle
(
fontSize:
15
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w500
));
InputDecoration
_inputDecoration
(
String
hint
)
{
return
InputDecoration
(
hintText:
hint
,
hintStyle:
const
TextStyle
(
fontSize:
14
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w400
,
color:
Colors
.
grey
),
filled:
true
,
fillColor:
Colors
.
grey
.
shade100
,
enabledBorder:
OutlineInputBorder
(
borderRadius:
BorderRadius
.
circular
(
8
),
borderSide:
BorderSide
.
none
),
focusedBorder:
OutlineInputBorder
(
borderRadius:
BorderRadius
.
circular
(
8
),
borderSide:
const
BorderSide
(
color:
Colors
.
blue
)),
);
}
}
lib/screens/hrm/AddTourExpBillScreen.dart
0 → 100644
View file @
6d1deaf2
import
'dart:io'
;
import
'package:connectivity_plus/connectivity_plus.dart'
;
import
'package:dropdown_button2/dropdown_button2.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter_svg/svg.dart'
;
import
'package:provider/provider.dart'
;
import
'package:intl/intl.dart'
;
import
'../../Utils/app_colors.dart'
;
import
'../../Utils/commonServices.dart'
;
import
'../../Utils/commonWidgets.dart'
;
import
'../../Utils/dropdownTheme.dart'
;
class
AddBillScreen
extends
StatefulWidget
{
final
String
pageTitleName
;
const
AddBillScreen
({
super
.
key
,
required
this
.
pageTitleName
});
@override
State
<
AddBillScreen
>
createState
()
=>
_AddBillScreenState
();
}
class
_AddBillScreenState
extends
State
<
AddBillScreen
>
{
Dropdowntheme
ddtheme
=
Dropdowntheme
();
List
<
FocusNode
>
focusNodes
=
List
.
generate
(
8
,
(
index
)
=>
FocusNode
());
Map
_source
=
{
ConnectivityResult
.
mobile
:
true
};
final
MyConnectivity
_connectivity
=
MyConnectivity
.
instance
;
TextEditingController
placeController
=
TextEditingController
();
TextEditingController
dateController
=
TextEditingController
();
TextEditingController
noteController
=
TextEditingController
();
String
?
selectedDAAmount
;
String
?
selectedTourType
;
List
<
Map
<
String
,
String
>>
travelExpenses
=
[
{
"title"
:
"Bike"
,
"amount"
:
"1800"
,
"icon"
:
"assets/svg/ic_bike.svg"
},
{
"title"
:
"Taxi"
,
"amount"
:
"300"
,
"icon"
:
"assets/svg/ic_taxi.svg"
},
];
List
<
Map
<
String
,
String
>>
hotelExpenses
=
[
{
"title"
:
"Hotel Sharada"
,
"amount"
:
"1800"
,
"icon"
:
"assets/svg/ic_hotel.svg"
},
{
"title"
:
"Hotel Nikitan"
,
"amount"
:
"1800"
,
"icon"
:
"assets/svg/ic_hotel.svg"
},
];
List
<
Map
<
String
,
String
>>
otherExpenses
=
[
{
"title"
:
"Book"
,
"amount"
:
"1800"
,
"icon"
:
"assets/svg/ic_book.svg"
},
];
@override
void
initState
()
{
super
.
initState
();
_connectivity
.
initialise
();
_connectivity
.
myStream
.
listen
((
source
)
{
setState
(()
=>
_source
=
source
);
});
}
@override
void
dispose
()
{
placeController
.
dispose
();
dateController
.
dispose
();
noteController
.
dispose
();
focusNodes
.
map
((
e
)
=>
e
.
dispose
());
_connectivity
.
disposeStream
();
super
.
dispose
();
}
Future
<
bool
>
_onBackPressed
(
BuildContext
context
)
async
{
Navigator
.
pop
(
context
,
true
);
return
true
;
}
@override
Widget
build
(
BuildContext
context
)
{
switch
(
_source
.
keys
.
toList
()[
0
])
{
case
ConnectivityResult
.
mobile
:
case
ConnectivityResult
.
wifi
:
connection
=
'Online'
;
break
;
case
ConnectivityResult
.
none
:
default
:
connection
=
'Offline'
;
}
return
(
connection
==
"Online"
)
?
Platform
.
isAndroid
?
WillPopScope
(
onWillPop:
()
=>
_onBackPressed
(
context
),
child:
SafeArea
(
top:
false
,
bottom:
true
,
child:
_scaffold
(
context
),
),
)
:
_scaffold
(
context
)
:
NoNetwork
(
context
);
}
Widget
_scaffold
(
BuildContext
context
)
{
return
Scaffold
(
resizeToAvoidBottomInset:
true
,
backgroundColor:
AppColors
.
scaffold_bg_color
,
appBar:
appbarNew
(
context
,
widget
.
pageTitleName
,
0xFFFFFFFF
),
body:
SingleChildScrollView
(
child:
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
14
,
vertical:
12
),
margin:
const
EdgeInsets
.
all
(
12
),
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
20
),
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
/// Place of Visit
TextWidget
(
context
,
"Place of Visit"
),
textFieldNew
(
context
,
placeController
,
"Enter Place"
),
/// DA Amount
TextWidget
(
context
,
"DA Amount"
),
dropDownField
(
context
,
"Select DA Amount"
,
[
"100"
,
"200"
,
"300"
],
selectedDAAmount
,
(
val
)
{
setState
(()
=>
selectedDAAmount
=
val
);
}),
/// Tour Type
TextWidget
(
context
,
"Tour Type"
),
dropDownField
(
context
,
"Select Tour"
,
[
"Business"
,
"Personal"
],
selectedTourType
,
(
val
)
{
setState
(()
=>
selectedTourType
=
val
);
}),
/// Tour Date
TextWidget
(
context
,
"Tour Date"
),
GestureDetector
(
onTap:
()
async
{
DateTime
?
picked
=
await
showDatePicker
(
context:
context
,
initialDate:
DateTime
.
now
(),
firstDate:
DateTime
(
2022
),
lastDate:
DateTime
(
2100
),
);
if
(
picked
!=
null
)
{
dateController
.
text
=
DateFormat
(
"dd MMM yyyy"
).
format
(
picked
);
setState
(()
{});
}
},
child:
textFieldNew
(
context
,
dateController
,
"Enter Date"
,
enabled:
false
),
),
/// Note
TextWidget
(
context
,
"Note"
),
textFieldNew
(
context
,
noteController
,
"Write Note"
,
maxLines:
3
),
const
SizedBox
(
height:
16
),
/// Travel Expenses
sectionHeader
(
"Travel Expenses"
,
onAddTap:
()
{
// TODO: Add Travel Expense
}),
expenseList
(
travelExpenses
),
/// Hotel Expenses
sectionHeader
(
"Hotel Expenses"
,
onAddTap:
()
{
// TODO: Add Hotel Expense
}),
expenseList
(
hotelExpenses
),
/// Other Expenses
sectionHeader
(
"Other Expenses"
,
onAddTap:
()
{
// TODO: Add Other Expense
}),
expenseList
(
otherExpenses
),
const
SizedBox
(
height:
80
),
],
),
),
),
floatingActionButtonLocation:
FloatingActionButtonLocation
.
centerFloat
,
bottomNavigationBar:
InkResponse
(
onTap:
()
{
// TODO: Submit API Call
},
child:
Container
(
height:
45
,
alignment:
Alignment
.
center
,
margin:
const
EdgeInsets
.
symmetric
(
horizontal:
10
,
vertical:
15
),
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
10
,
vertical:
5
),
decoration:
BoxDecoration
(
color:
AppColors
.
app_blue
,
borderRadius:
BorderRadius
.
circular
(
15
),
),
child:
const
Text
(
"Submit"
,
style:
TextStyle
(
fontSize:
15
,
fontFamily:
"JakartaMedium"
,
color:
Colors
.
white
,
),
),
),
),
);
}
/// --- Custom Widgets Below ---
Widget
textFieldNew
(
BuildContext
context
,
TextEditingController
controller
,
String
hint
,
{
bool
enabled
=
true
,
int
maxLines
=
1
})
{
return
Container
(
margin:
const
EdgeInsets
.
only
(
bottom:
12
),
decoration:
BoxDecoration
(
color:
AppColors
.
text_field_color
,
borderRadius:
BorderRadius
.
circular
(
14
),
),
child:
TextFormField
(
controller:
controller
,
enabled:
enabled
,
maxLines:
maxLines
,
decoration:
_inputDecoration
(
hint
)
),
);
}
Widget
dropDownField
(
BuildContext
context
,
String
hint
,
List
<
String
>
items
,
String
?
value
,
Function
(
String
?)
onChanged
)
{
return
Container
(
margin:
const
EdgeInsets
.
only
(
bottom:
12
),
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
12
),
decoration:
BoxDecoration
(
color:
AppColors
.
text_field_color
,
borderRadius:
BorderRadius
.
circular
(
14
),
),
child:
DropdownButtonHideUnderline
(
child:
DropdownButton2
<
String
>(
isExpanded:
true
,
hint:
Text
(
hint
,
style:
const
TextStyle
(
fontSize:
14
,
color:
Colors
.
grey
)),
value:
value
,
items:
items
.
map
((
e
)
=>
DropdownMenuItem
(
value:
e
,
child:
Text
(
e
))).
toList
(),
onChanged:
onChanged
,
iconStyleData:
ddtheme
.
iconStyleData
,
// menuItemStyleData: ddtheme.menuItemStyleData,
dropdownStyleData:
ddtheme
.
dropdownStyleData
,
),
),
);
}
Widget
sectionHeader
(
String
title
,
{
VoidCallback
?
onAddTap
})
{
return
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
title
,
style:
const
TextStyle
(
fontSize:
14
,
fontWeight:
FontWeight
.
w600
)),
const
SizedBox
(
height:
6
),
Container
(
height:
45
,
decoration:
BoxDecoration
(
border:
Border
.
all
(
color:
Colors
.
grey
.
shade400
,
width:
0.7
,
style:
BorderStyle
.
solid
),
borderRadius:
BorderRadius
.
circular
(
12
),
),
child:
InkWell
(
onTap:
onAddTap
,
child:
Center
(
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
const
[
Icon
(
Icons
.
add
,
color:
Colors
.
blue
),
SizedBox
(
width:
6
),
Text
(
"Add Expenses"
,
style:
TextStyle
(
color:
Colors
.
blue
,
fontSize:
14
)),
],
),
),
),
),
const
SizedBox
(
height:
10
),
],
);
}
Widget
expenseList
(
List
<
Map
<
String
,
String
>>
items
)
{
return
Container
(
height:
84
,
margin:
const
EdgeInsets
.
only
(
bottom:
12
),
child:
ListView
.
builder
(
scrollDirection:
Axis
.
horizontal
,
itemCount:
items
.
length
,
itemBuilder:
(
context
,
index
)
{
final
exp
=
items
[
index
];
return
Container
(
width:
120
,
margin:
const
EdgeInsets
.
only
(
right:
10
),
padding:
const
EdgeInsets
.
all
(
10
),
decoration:
BoxDecoration
(
color:
const
Color
(
0xFFE6F6FF
),
borderRadius:
BorderRadius
.
circular
(
12
),
),
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
SvgPicture
.
asset
(
exp
[
"icon"
]
??
"assets/svg/ic_default.svg"
,
height:
22
),
const
SizedBox
(
height:
6
),
Text
(
exp
[
"title"
]
??
"-"
,
style:
const
TextStyle
(
fontSize:
13
,
fontWeight:
FontWeight
.
w500
)),
Text
(
"₹
${exp["amount"]}
"
,
style:
const
TextStyle
(
fontSize:
12
,
color:
Colors
.
blue
)),
],
),
);
},
),
);
}
InputDecoration
_inputDecoration
(
String
hint
)
{
return
InputDecoration
(
hintText:
hint
,
hintStyle:
TextStyle
(
fontSize:
14
,
fontFamily:
"Plus Jakarta Sans"
,
fontStyle:
FontStyle
.
normal
,
fontWeight:
FontWeight
.
w400
,
color:
Color
(
0xFFB4BEC0
),
),
filled:
true
,
fillColor:
Colors
.
grey
.
shade100
,
enabledBorder:
InputBorder
.
none
,
disabledBorder:
InputBorder
.
none
,
focusedBorder:
InputBorder
.
none
,
);
}
}
lib/screens/hrm/AttendanceRequestDetail.dart
0 → 100644
View file @
6d1deaf2
import
'package:dotted_line/dotted_line.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter_svg/svg.dart'
;
import
'package:geocoding/geocoding.dart'
;
import
'package:provider/provider.dart'
;
import
'package:url_launcher/url_launcher.dart'
;
import
'../../Notifiers/hrmProvider/attendanceDetailsProvider.dart'
;
import
'../../Notifiers/HomeScreenNotifier.dart'
;
import
'../../Utils/app_colors.dart'
;
import
'../finance/FileViewer.dart'
;
/// screen for attendance details
class
AttendanceRequestDetailScreen
extends
StatefulWidget
{
final
attendanceListId
;
const
AttendanceRequestDetailScreen
({
super
.
key
,
required
this
.
attendanceListId
});
@override
State
<
AttendanceRequestDetailScreen
>
createState
()
=>
_AttendanceRequestDetailScreenState
();
}
class
_AttendanceRequestDetailScreenState
extends
State
<
AttendanceRequestDetailScreen
>
{
late
AttendanceDetailsProvider
provider
;
// @override
// void initState() {
// super.initState();
//
// /// fetch API after widget is built
// WidgetsBinding.instance.addPostFrameCallback((_) {
// final home = Provider.of<HomescreenNotifier>(context, listen: false);
// provider = Provider.of<AttendanceDetailsProvider>(context, listen: false);
//
// provider.fetchAttendanceRequestDetail(
// context,
// widget.attendanceListId
// );
// });
// }
@override
Widget
build
(
BuildContext
context
)
{
return
ChangeNotifierProvider
(
create:
(
_
)
=>
AttendanceDetailsProvider
()..
fetchAttendanceRequestDetail
(
context
,
widget
.
attendanceListId
),
child:
Consumer
<
AttendanceDetailsProvider
>(
builder:
(
context
,
provider
,
child
)
{
return
Scaffold
(
appBar:
AppBar
(
automaticallyImplyLeading:
false
,
backgroundColor:
Color
(
0xFFFFFFFF
),
title:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
start
,
crossAxisAlignment:
CrossAxisAlignment
.
center
,
children:
[
InkResponse
(
onTap:
()
=>
Navigator
.
pop
(
context
,
true
),
child:
SvgPicture
.
asset
(
"assets/svg/appbar_back_button.svg"
,
height:
25
,
),
),
SizedBox
(
width:
10
),
InkResponse
(
onTap:
()
=>
Navigator
.
pop
(
context
,
true
),
child:
Text
(
"Attendance Details"
,
style:
TextStyle
(
fontSize:
18
,
height:
1.1
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w600
,
color:
AppColors
.
semi_black
,
),
),
),
],
),
),
backgroundColor:
Color
(
0xFFF6F6F8
),
body:
Builder
(
builder:
(
context
)
{
if
(
provider
.
isLoading
)
{
return
const
Center
(
child:
CircularProgressIndicator
(
color:
Colors
.
blue
,));
}
if
(
provider
.
errorMessage
!=
null
)
{
return
Center
(
child:
Text
(
provider
.
errorMessage
!));
}
if
(
provider
.
response
?.
requestDetails
==
null
)
{
return
const
Center
(
child:
Text
(
"No details found"
));
}
final
details
=
provider
.
response
!.
requestDetails
!;
/// scr
return
SingleChildScrollView
(
padding:
const
EdgeInsets
.
all
(
16.0
),
child:
Column
(
children:
[
Card
(
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
16
),
),
elevation:
2
,
child:
Padding
(
padding:
const
EdgeInsets
.
all
(
16.0
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Container
(
margin:
const
EdgeInsets
.
only
(
bottom:
0.5
),
padding:
const
EdgeInsets
.
all
(
12
),
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
12
),
),
child:
Row
(
children:
[
/// Left Avatar
Container
(
height:
48
,
width:
48
,
decoration:
BoxDecoration
(
shape:
BoxShape
.
circle
,
color:
const
Color
(
0xFFEDF8FF
),
// icon bg
),
child:
Center
(
child:
SvgPicture
.
asset
(
height:
28
,
width:
28
,
"assets/svg/hrm/attendanceList.svg"
,
fit:
BoxFit
.
contain
,
),
),
),
const
SizedBox
(
width:
12
),
/// Middle text
Expanded
(
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
details
.
type
??
"-"
,
style:
const
TextStyle
(
fontSize:
14
,
fontStyle:
FontStyle
.
normal
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w400
,
),
),
const
SizedBox
(
height:
2
),
Text
(
details
.
date
??
"-"
,
style:
const
TextStyle
(
fontSize:
12
,
fontStyle:
FontStyle
.
normal
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w400
,
color:
Color
(
0xff818181
),
),
),
],
),
),
/// Right side (Live/Manual)
Container
(
height:
30
,
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
12
),
decoration:
BoxDecoration
(
borderRadius:
BorderRadius
.
circular
(
6
),
color:
getDecorationColor
(
details
.
status
)
),
child:
Center
(
child:
Text
(
details
.
status
??
"-"
,
style:
TextStyle
(
fontSize:
13
,
fontWeight:
FontWeight
.
w600
,
color:
getTextColor
(
details
.
status
.
toString
()),
),
),
),
),
],
),
),
// Employee Details
_buildSectionHeader
(
"Employee Details"
),
_buildDetailTile
(
"Employee Name"
,
details
.
employeeName
),
_buildDetailTile
(
"Created Employee"
,
details
.
createdEmpName
),
// Check In/Out
_buildSectionHeader
(
"Check In/Out Details"
),
_buildDate_TimeTile
(
"Check In Date & Time"
,
details
.
date
,
details
.
checkInTime
),
_buildDate_TimeTile
(
"Check Out Date & Time"
,
details
.
date
,
details
.
checkOutTime
),
_buildDetailTile
(
"Original Check In"
,
details
.
checkInTime
),
_buildDetailTile
(
"Original Check Out"
,
"--"
),
_buildDetailTile
(
"Original Check In Location"
,
details
.
checkInLocation
),
_buildDetailTile
(
"Original Check Out Location"
,
details
.
checkOutLocation
),
buildLocationTile
(
"Location"
,
details
.
location
),
// Proofs
_buildSectionHeader
(
"Proofs"
),
_buildProofLink
(
context
,
"Check In Proof"
,
details
.
checkInProofDirFilePath
),
_buildProofLink
(
context
,
"Check Out Proof"
,
details
.
checkOutProofDirFilePath
),
// Remarks & Approvals
_buildSectionHeader
(
"Remarks & Approvals"
),
_buildDetailTile
(
"Level 1 Approved By"
,
details
.
level1EmpName
),
_buildDetailTile
(
"Level 2 Approved By"
,
details
.
level2EmpName
),
_buildDetailTile
(
"Level 1 Remark"
,
details
.
level1Remarks
),
_buildDetailTile
(
"Level 2 Remark"
,
details
.
level2Remarks
),
///remain data
_buildSectionHeader
(
"Other Details"
),
_buildDetailTile
(
"Check In Type"
,
details
.
checkInType
),
_buildDetailTile
(
"Check Out Type"
,
details
.
chechOutType
),
_buildDetailTile
(
"Check Out Time"
,
details
.
checkOutTime
),
// Attendance Info
_buildDetailTile
(
"ID"
,
details
.
id
),
_buildDetailTile
(
"Attendance Type"
,
details
.
attendanceType
),
_buildDetailTile
(
"Note"
,
details
.
note
),
_buildDetailTile
(
"Created Datetime"
,
details
.
requestedDatetime
),
],
),
),
),
SizedBox
(
height:
30
,)
],
),
);
},
),
);
},
)
);
}
/// Reusable Row Widget for details
Widget
_buildDetailTile
(
String
label
,
String
?
value
)
{
return
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
3
),
child:
Row
(
children:
[
Expanded
(
flex:
6
,
child:
Text
(
label
,
style:
const
TextStyle
(
fontSize:
14
,
color:
Color
(
0xff2D2D2D
),
fontStyle:
FontStyle
.
normal
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w400
,
),
),
),
Expanded
(
flex:
0
,
child:
Text
(
value
??
"-"
,
style:
const
TextStyle
(
fontSize:
14
,
color:
Color
(
0xff818181
),
fontStyle:
FontStyle
.
normal
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w400
,
)
),
),
],
),
);
}
/// for location
Widget
buildLocationTile
(
String
label
,
String
?
value
)
{
return
FutureBuilder
<
String
>(
future:
getReadableLocation
(
value
),
builder:
(
context
,
snapshot
)
{
final
locationText
=
snapshot
.
data
??
"-"
;
return
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
3
),
child:
Row
(
children:
[
Expanded
(
flex:
6
,
child:
Text
(
label
,
style:
const
TextStyle
(
fontSize:
14
,
color:
Color
(
0xff2D2D2D
),
fontStyle:
FontStyle
.
normal
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w400
,
),
),
),
Expanded
(
flex:
0
,
child:
GestureDetector
(
onTap:
()
async
{
final
uri
=
Uri
.
parse
(
"https://www.google.com/maps/search/?api=1&query=
$value
"
);
if
(
await
canLaunchUrl
(
uri
))
{
await
launchUrl
(
uri
,
mode:
LaunchMode
.
externalApplication
);
}
},
child:
Text
(
locationText
,
style:
const
TextStyle
(
fontSize:
14
,
color:
Colors
.
blue
,
decoration:
TextDecoration
.
underline
,
fontStyle:
FontStyle
.
normal
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w400
,
),
),
),
),
],
),
);
},
);
}
Future
<
String
>
getReadableLocation
(
String
?
value
)
async
{
if
(
value
==
null
)
return
"-"
;
try
{
List
<
Location
>
locations
=
await
locationFromAddress
(
value
);
List
<
Placemark
>
placemarks
=
await
placemarkFromCoordinates
(
locations
[
0
].
latitude
,
locations
[
0
].
longitude
,
);
return
placemarks
.
first
.
locality
??
value
;
}
catch
(
e
)
{
return
value
;
// fallback to raw coordinates
}
}
/// for date and time
Widget
_buildDate_TimeTile
(
String
label
,
String
?
date
,
String
?
time
)
{
return
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
3
),
child:
Row
(
children:
[
Expanded
(
flex:
5
,
child:
Text
(
label
,
style:
const
TextStyle
(
fontSize:
14
,
color:
Color
(
0xff2D2D2D
),
fontStyle:
FontStyle
.
normal
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w400
,
),
),
),
Expanded
(
flex:
0
,
child:
Row
(
children:
[
Text
(
'
$date
, '
??
"-"
,
style:
const
TextStyle
(
fontSize:
14
,
color:
Color
(
0xff818181
),
fontStyle:
FontStyle
.
normal
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w400
,
)
),
Text
(
time
??
"-"
,
style:
const
TextStyle
(
fontSize:
14
,
color:
Color
(
0xff818181
),
fontStyle:
FontStyle
.
normal
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w400
,
)
),
],
)
),
],
),
);
}
///////////////////////
Widget
_buildSectionHeader
(
String
title
)
{
return
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
8
),
child:
Row
(
children:
[
Text
(
title
,
style:
const
TextStyle
(
fontStyle:
FontStyle
.
normal
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w600
,
fontSize:
14
,
),
),
const
SizedBox
(
width:
10
),
Expanded
(
child:
DottedLine
(
dashLength:
4
,
dashGapLength:
2
,
lineThickness:
1
,
dashColor:
Color
(
0xff888888
),
)
),
],
),
);
}
/// Proof section (image/file path)
Widget
_buildProofLink
(
BuildContext
context
,
String
label
,
String
?
filePath
)
{
return
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
6
),
child:
Row
(
children:
[
Expanded
(
flex:
5
,
child:
Text
(
label
,
style:
const
TextStyle
(
fontSize:
13
,
fontWeight:
FontWeight
.
w500
,
),
),
),
Expanded
(
flex:
0
,
child:
filePath
!=
null
?
InkWell
(
onTap:
()
{
showDialog
(
context:
context
,
builder:
(
_
)
=>
Dialog
(
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
12
),
),
child:
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
12
),
child:
Fileviewer
(
fileName:
label
,
fileUrl:
filePath
,),
),
),
);
},
child:
const
Text
(
"View"
,
style:
TextStyle
(
fontSize:
14
,
color:
Colors
.
blue
,
fontStyle:
FontStyle
.
normal
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w400
,
decoration:
TextDecoration
.
underline
),
),
)
:
const
Text
(
"-"
),
),
],
),
);
}
Color
getTextColor
(
value
)
{
var
color
=
AppColors
.
approved_text_color
;
switch
(
value
)
{
case
'Requested'
:
return
AppColors
.
requested_text_color
;
case
'Level 1 Approved'
:
return
AppColors
.
approved_text_color
;
case
'Level 1 Rejected'
:
return
AppColors
.
rejected_text_color
;
case
'Level 2 Approved'
:
return
AppColors
.
approved_text_color
;
case
'Level 2 Rejected'
:
return
AppColors
.
rejected_text_color
;
case
'Processed'
:
return
AppColors
.
processed_text_color
;
case
'Payment Rejected'
:
return
AppColors
.
rejected_text_color
;
}
return
color
;
}
Color
getDecorationColor
(
value
)
{
var
color
=
AppColors
.
approved_bg_color
;
switch
(
value
)
{
case
'Requested'
:
return
AppColors
.
requested_bg_color
;
case
'Level 1 Approved'
:
return
AppColors
.
approved_bg_color
;
case
'Level 1 Rejected'
:
return
AppColors
.
rejected_bg_color
;
case
'Level 2 Approved'
:
return
AppColors
.
approved_bg_color
;
case
'Level 2 Rejected'
:
return
AppColors
.
rejected_bg_color
;
case
'Processed'
:
return
AppColors
.
processed_bg_color
;
case
'Payment Rejected'
:
return
AppColors
.
rejected_bg_color
;
}
return
color
;
}
}
lib/screens/hrm/Attendancelist.dart
0 → 100644
View file @
6d1deaf2
import
'package:dropdown_button2/dropdown_button2.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter_svg/svg.dart'
;
import
'package:generp/Utils/GlobalConstants.dart'
;
import
'package:generp/screens/hrm/AddManualAttendance.dart'
;
import
'package:generp/screens/hrm/AttendanceRequestDetail.dart'
;
import
'package:provider/provider.dart'
;
import
'../../Notifiers/hrmProvider/attendanceListProvider.dart'
;
import
'../../Models/hrmModels/attendanceRequestListResponse.dart'
;
import
'../../Utils/app_colors.dart'
;
import
'../commonDateRangeFilter.dart'
;
import
'AddLiveAttendance.dart'
;
import
'package:intl/intl.dart'
;
Map
<
String
,
String
>
getDateRange
(
String
selectedDate
)
{
final
now
=
DateTime
.
now
();
final
formatter
=
DateFormat
(
"yyyy-MM-dd"
);
late
DateTime
from
;
late
DateTime
to
;
switch
(
selectedDate
)
{
case
"All"
:
from
=
now
;
to
=
now
;
break
;
case
"Today"
:
from
=
now
;
to
=
now
;
break
;
case
"Yesterday"
:
from
=
now
.
subtract
(
const
Duration
(
days:
1
));
to
=
now
.
subtract
(
const
Duration
(
days:
1
));
break
;
case
"This Month"
:
from
=
DateTime
(
now
.
year
,
now
.
month
,
1
);
to
=
DateTime
(
now
.
year
,
now
.
month
+
1
,
0
);
break
;
case
"Past 7 days"
:
from
=
now
.
subtract
(
const
Duration
(
days:
6
));
to
=
now
;
break
;
case
"Last Month"
:
from
=
DateTime
(
now
.
year
,
now
.
month
-
1
,
1
);
to
=
DateTime
(
now
.
year
,
now
.
month
,
0
);
break
;
case
"Custom"
:
// For custom, you should open a date picker dialog
from
=
now
;
// Placeholder
to
=
now
;
// Placeholder
break
;
default
:
from
=
now
;
to
=
now
;
}
return
{
"from"
:
formatter
.
format
(
from
),
"to"
:
formatter
.
format
(
to
),
};
}
class
Attendancelist
extends
StatefulWidget
{
const
Attendancelist
({
super
.
key
});
@override
State
<
Attendancelist
>
createState
()
=>
_AttendancelistState
();
}
class
_AttendancelistState
extends
State
<
Attendancelist
>
{
String
selectedType
=
"All"
;
String
selectedDate
=
"Today"
;
final
List
<
String
>
typeOptions
=
[
"All"
,
"Check In"
,
"Check Out"
,
"Check In/Out"
];
final
List
<
String
>
dateOptions
=
[
"Today"
,
"Yesterday"
,
"This Month"
,
"Past 7 days"
,
"Last Month"
,
"Custom"
,
];
@override
Widget
build
(
BuildContext
context
)
{
final
dateRange
=
getDateRange
(
selectedDate
);
final
fromDate
=
dateRange
[
"from"
]!;
final
toDate
=
dateRange
[
"to"
]!;
final
type
=
(
selectedType
==
"All"
)
?
""
:
selectedType
;
return
SafeArea
(
top:
false
,
child:
ChangeNotifierProvider
(
create:
(
_
)
=>
Attendancelistprovider
()..
fetchAttendanceRequests
(
context
,
type
,
""
,
""
),
child:
Consumer
<
Attendancelistprovider
>(
builder:
(
context
,
provider
,
child
)
{
return
Scaffold
(
appBar:
AppBar
(
automaticallyImplyLeading:
false
,
backgroundColor:
Colors
.
white
,
title:
Row
(
children:
[
InkResponse
(
onTap:
()
=>
Navigator
.
pop
(
context
,
true
),
child:
SvgPicture
.
asset
(
"assets/svg/appbar_back_button.svg"
,
height:
25
,
),
),
const
SizedBox
(
width:
10
),
Text
(
"Attendance List"
,
style:
TextStyle
(
fontSize:
18
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w600
,
color:
AppColors
.
semi_black
,
),
),
],
),
actions:
[
InkResponse
(
onTap:
()
{
showModalBottomSheet
(
context:
context
,
shape:
const
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
vertical
(
top:
Radius
.
circular
(
16
)),
),
builder:
(
context
)
{
return
StatefulBuilder
(
builder:
(
context
,
setModalState
)
{
return
Padding
(
padding:
const
EdgeInsets
.
all
(
16
),
child:
Column
(
mainAxisSize:
MainAxisSize
.
min
,
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
const
Text
(
"Filter Attendance"
,
style:
TextStyle
(
fontSize:
17
,
fontWeight:
FontWeight
.
bold
,
color:
Colors
.
blue
,
fontFamily:
"Plus Jakarta Sans"
),
),
const
SizedBox
(
height:
16
),
/// Type Dropdown
Text
(
"Type"
,
style:
TextStyle
(
fontSize:
16
,
fontWeight:
FontWeight
.
w400
,
fontFamily:
"Plus Jakarta Sans"
)
),
SizedBox
(
height:
6
),
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
2
),
decoration:
BoxDecoration
(
color:
Colors
.
grey
.
shade100
,
borderRadius:
BorderRadius
.
circular
(
8
),
),
child:
DropdownButtonHideUnderline
(
child:
DropdownButton2
<
String
>(
isExpanded:
true
,
hint:
const
Text
(
"Select Type"
,
style:
TextStyle
(
fontSize:
14
),
overflow:
TextOverflow
.
ellipsis
,
),
value:
selectedType
,
items:
typeOptions
.
map
((
type
)
=>
DropdownMenuItem
<
String
>(
value:
type
,
child:
Text
(
type
),
))
.
toList
(),
onChanged:
(
val
)
{
setModalState
(()
{
selectedType
=
val
!;
});
},
buttonStyleData:
const
ButtonStyleData
(
height:
52
,
padding:
EdgeInsets
.
zero
,
),
dropdownStyleData:
DropdownStyleData
(
decoration:
BoxDecoration
(
borderRadius:
BorderRadius
.
circular
(
8
),
),
),
),
),
),
const
SizedBox
(
height:
18
),
/// Date Dropdown
const
Text
(
"Date"
,
style:
TextStyle
(
fontSize:
16
,
fontWeight:
FontWeight
.
w400
,
fontFamily:
"Plus Jakarta Sans"
)),
const
SizedBox
(
height:
6
),
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
2
),
decoration:
BoxDecoration
(
color:
Colors
.
grey
.
shade100
,
borderRadius:
BorderRadius
.
circular
(
8
),
),
child:
DropdownButtonHideUnderline
(
child:
DropdownButton2
<
String
>(
isExpanded:
true
,
hint:
const
Text
(
"Select Date"
,
style:
TextStyle
(
fontSize:
14
),
overflow:
TextOverflow
.
ellipsis
,
),
value:
selectedDate
,
items:
dateOptions
.
map
((
date
)
=>
DropdownMenuItem
<
String
>(
value:
date
,
child:
Text
(
date
),
))
.
toList
(),
onChanged:
(
val
)
{
setModalState
(()
{
selectedDate
=
val
!;
});
},
buttonStyleData:
const
ButtonStyleData
(
height:
52
,
padding:
EdgeInsets
.
zero
,
),
dropdownStyleData:
DropdownStyleData
(
decoration:
BoxDecoration
(
borderRadius:
BorderRadius
.
circular
(
8
),
),
),
),
),
),
const
SizedBox
(
height:
26
),
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceEvenly
,
children:
[
Expanded
(
child:
ElevatedButton
(
style:
ElevatedButton
.
styleFrom
(
backgroundColor:
Colors
.
blue
,
padding:
const
EdgeInsets
.
symmetric
(
vertical:
14
),
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
8
),
),
),
onPressed:
()
{
// on apply
provider
.
fetchAttendanceRequests
(
context
,
selectedType
,
""
,
""
);
Navigator
.
pop
(
context
);
},
child:
const
Text
(
"Apply"
,
style:
TextStyle
(
fontFamily:
"Plus Jakarta Sans"
,
color:
Colors
.
white
,
fontWeight:
FontWeight
.
w600
,
),
),
),
),
const
SizedBox
(
width:
12
),
Expanded
(
child:
OutlinedButton
(
style:
OutlinedButton
.
styleFrom
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
14
),
side:
BorderSide
(
color:
Colors
.
grey
.
shade400
),
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
8
),
),
),
onPressed:
()
{
// on cancel
Navigator
.
pop
(
context
);
},
child:
const
Text
(
"Cancel"
,
style:
TextStyle
(
fontFamily:
"Plus Jakarta Sans"
,
color:
Colors
.
black87
,
fontWeight:
FontWeight
.
w600
,
),
),
),
),
],
),
const
SizedBox
(
height:
22
),
],
),
);
},
);
},
);
},
child:
SvgPicture
.
asset
(
"assets/svg/filter_ic.svg"
,
height:
25
,
),
),
const
SizedBox
(
width:
20
),
],
),
backgroundColor:
const
Color
(
0xFFF6F6F8
),
body:
Column
(
children:
[
/// Attendance list
Expanded
(
child:
Builder
(
builder:
(
context
)
{
if
(
provider
.
isLoading
)
{
return
const
Center
(
child:
CircularProgressIndicator
(
color:
Colors
.
blue
,));
}
if
(
provider
.
errorMessage
!=
null
)
{
return
Center
(
child:
Text
(
provider
.
errorMessage
!));
}
if
(
provider
.
response
?.
requestList
==
null
||
provider
.
response
!.
requestList
!.
isEmpty
)
{
return
const
Center
(
child:
Text
(
"No requests found"
));
}
final
list
=
provider
.
response
!.
requestList
!;
return
ListView
.
builder
(
padding:
const
EdgeInsets
.
all
(
8
),
itemCount:
list
.
length
,
itemBuilder:
(
context
,
index
)
{
final
item
=
list
[
index
];
final
initials
=
_generateInitials
(
item
);
return
InkWell
(
borderRadius:
BorderRadius
.
circular
(
16
),
onTap:
()
{
/// navigation flow
Navigator
.
push
(
context
,
MaterialPageRoute
(
builder:
(
context
)
=>
AttendanceRequestDetailScreen
(
attendanceListId:
item
.
id
,
),
),
);
},
child:
Container
(
margin:
const
EdgeInsets
.
symmetric
(
horizontal:
8.5
,
vertical:
5
),
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
12
,
vertical:
8.5
),
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
16
),
),
child:
Row
(
children:
[
/// Left Avatar Circle
Container
(
height:
48
,
width:
50
,
padding:
const
EdgeInsets
.
all
(
8.0
),
decoration:
BoxDecoration
(
color:
_getAvatarColor
(
item
.
status
),
shape:
BoxShape
.
circle
,
),
child:
Center
(
child:
Text
(
getText
(
item
.
status
),
style:
TextStyle
(
color:
_getTextColor
(
item
.
status
),
fontSize:
14
,
fontWeight:
FontWeight
.
bold
,
),
),
),
),
const
SizedBox
(
width:
10
),
/// Middle Section
Expanded
(
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
item
.
type
??
"-"
,
maxLines:
1
,
overflow:
TextOverflow
.
ellipsis
,
style:
const
TextStyle
(
fontSize:
15
,
color:
Color
(
0xff2D2D2D
),
fontFamily:
"Plus Jakarta Sans"
,
fontStyle:
FontStyle
.
normal
,
fontWeight:
FontWeight
.
w400
),
),
Text
(
item
.
date
??
"-"
,
style:
const
TextStyle
(
fontSize:
12.5
,
color:
Color
(
0xff818181
),
fontFamily:
"Plus Jakarta Sans"
,
fontStyle:
FontStyle
.
normal
,
fontWeight:
FontWeight
.
w400
),
),
],
),
),
/// Right Status (Live / Manual)
Text
(
item
.
attendanceType
??
"-"
,
textAlign:
TextAlign
.
right
,
style:
TextStyle
(
fontFamily:
"Plus Jakarta Sans"
,
fontSize:
14
,
fontWeight:
FontWeight
.
w500
,
color:
(
item
.
attendanceType
??
""
).
toLowerCase
()
==
"live"
?
Colors
.
green
:
Colors
.
orange
,
),
),
],
),
),
);
},
);
},
),
)
],
),
bottomNavigationBar:
Container
(
alignment:
Alignment
.
bottomCenter
,
height:
65
,
decoration:
const
BoxDecoration
(
color:
Colors
.
white
),
child:
Row
(
crossAxisAlignment:
CrossAxisAlignment
.
center
,
mainAxisAlignment:
MainAxisAlignment
.
spaceEvenly
,
children:
[
Expanded
(
child:
InkResponse
(
onTap:
()
{
Navigator
.
push
(
context
,
MaterialPageRoute
(
builder:
(
context
)
=>
AddLiveAttendanceScreen
(),
settings:
const
RouteSettings
(
name:
'AddLiveAttendanceScreen'
,
),
),
).
then
((
_
)
{
});
},
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
SvgPicture
.
asset
(
"assets/svg/hrm/live.svg"
),
const
SizedBox
(
width:
10
),
Text
(
"Live Request"
,
style:
TextStyle
(
color:
AppColors
.
semi_black
)),
],
),
),
),
const
SizedBox
(
width:
10
),
SvgPicture
.
asset
(
"assets/svg/crm/vertical_line_ic.svg"
),
const
SizedBox
(
width:
10
),
Expanded
(
child:
InkResponse
(
onTap:
()
{
Navigator
.
push
(
context
,
MaterialPageRoute
(
builder:
(
context
)
=>
const
AddManualAttendanceScreen
(),
settings:
const
RouteSettings
(
name:
'AddManualAttendanceScreen'
),
),
).
then
((
_
)
{
});
},
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
SvgPicture
.
asset
(
"assets/svg/hrm/manual.svg"
),
const
SizedBox
(
width:
10
),
Text
(
"Manual Request"
,
style:
TextStyle
(
color:
AppColors
.
semi_black
)),
],
),
),
),
],
),
),
);
},
),
),
);
}
/// Generate avatar initials like L1A, L1R
String
_generateInitials
(
RequestList
item
)
{
final
attType
=
(
item
.
attendanceType
?.
isNotEmpty
??
false
)
?
item
.
attendanceType
![
0
].
toUpperCase
()
:
"U"
;
final
type
=
(
item
.
type
?.
isNotEmpty
??
false
)
?
item
.
type
![
0
].
toUpperCase
()
:
"X"
;
return
"
$attType$type
"
;
}
/// Avatar color generator
Color
_getAvatarColor
(
value
)
{
var
color
=
AppColors
.
approved_bg_color
;
switch
(
value
)
{
case
'Requested'
:
return
AppColors
.
requested_bg_color
;
case
'Level 1 Approved'
:
return
AppColors
.
approved_bg_color
;
case
'Level 1 Rejected'
:
return
AppColors
.
rejected_bg_color
;
case
'Level 2 Approved'
:
return
AppColors
.
approved_bg_color
;
case
'Level 2 Rejected'
:
return
AppColors
.
rejected_bg_color
;
case
'Updated'
:
return
AppColors
.
processed_bg_color
;
case
'Payment Rejected'
:
return
AppColors
.
rejected_bg_color
;
}
return
color
;
}
Color
_getTextColor
(
value
)
{
var
color
=
AppColors
.
approved_text_color
;
switch
(
value
)
{
case
'Requested'
:
return
AppColors
.
requested_text_color
;
case
'Level 1 Approved'
:
return
AppColors
.
approved_text_color
;
case
'Level 1 Rejected'
:
return
AppColors
.
rejected_text_color
;
case
'Level 2 Approved'
:
return
AppColors
.
approved_text_color
;
case
'Level 2 Rejected'
:
return
AppColors
.
rejected_text_color
;
case
'Updated'
:
return
AppColors
.
processed_text_color
;
}
return
color
;
}
getText
(
value
)
{
switch
(
value
)
{
case
'Requested'
:
return
"R"
;
case
'Level 1 Approved'
:
return
"L1A"
;
case
'Level 1 Rejected'
:
return
"L1R"
;
case
'Level 2 Approved'
:
return
"L2A"
;
case
'Level 2 Rejected'
:
return
"L2R"
;
case
'Updated'
:
return
"U"
;
default
:
return
"Requested"
;
}
}
}
lib/screens/hrm/HrmDashboardScreen.dart
0 → 100644
View file @
6d1deaf2
import
'package:flutter/material.dart'
;
import
'package:flutter_svg/svg.dart'
;
import
'package:generp/screens/hrm/RewardListScreen.dart'
;
import
'../../Utils/app_colors.dart'
;
import
'AttendanceRequestDetail.dart'
;
import
'LeaveApplicationScreen.dart'
;
import
'TourExpensesListScreen.dart'
;
import
'attendancelist.dart'
;
class
HrmdashboardScreen
extends
StatefulWidget
{
const
HrmdashboardScreen
({
super
.
key
});
@override
State
<
HrmdashboardScreen
>
createState
()
=>
_HrmdashboardScreenState
();
}
class
_HrmdashboardScreenState
extends
State
<
HrmdashboardScreen
>
{
@override
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
appBar:
AppBar
(
automaticallyImplyLeading:
false
,
backgroundColor:
const
Color
(
0xFFCEEDFF
),
// elevation: 2.0,
title:
SizedBox
(
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
start
,
crossAxisAlignment:
CrossAxisAlignment
.
center
,
children:
[
InkResponse
(
onTap:
()
=>
Navigator
.
pop
(
context
,
true
),
child:
SvgPicture
.
asset
(
"assets/svg/appbar_back_button.svg"
,
height:
25
,
),
),
const
SizedBox
(
width:
10
),
InkResponse
(
onTap:
()
=>
Navigator
.
pop
(
context
,
true
),
child:
Text
(
"HRM"
,
style:
TextStyle
(
fontSize:
18
,
height:
1.1
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w600
,
color:
AppColors
.
semi_black
,
),
),
),
],
),
),
),
backgroundColor:
Color
(
0xffF6F6F8
),
body:
SingleChildScrollView
(
child:
Column
(
children:
[
/// Background elements
Stack
(
children:
[
Container
(
width:
double
.
infinity
,
height:
490
,
color:
const
Color
(
0xffF6F6F8
),
),
Container
(
width:
double
.
infinity
,
height:
490
,
padding:
const
EdgeInsets
.
only
(
top:
1
,
bottom:
30
),
decoration:
const
BoxDecoration
(
gradient:
LinearGradient
(
colors:
[
Color
(
0xFFCEEDFF
),
Color
(
0xFFf9f9fb
),
Color
(
0xffF6F6F8
)
],
begin:
Alignment
.
topCenter
,
end:
Alignment
.
bottomCenter
,
),
),
),
Container
(
width:
double
.
infinity
,
padding:
const
EdgeInsets
.
only
(
top:
1
,
bottom:
30
),
child:
Image
.
asset
(
"assets/images/vector.png"
,
height:
230
,
width:
double
.
infinity
,
fit:
BoxFit
.
fitWidth
,
),
),
Column
(
children:
[
/// Top Section with Gradient
Container
(
width:
double
.
infinity
,
padding:
const
EdgeInsets
.
only
(
top:
60
,
bottom:
30
),
child:
Column
(
children:
[
/// Illustration
SvgPicture
.
asset
(
"assets/images/capa.svg"
,
height:
146
,
width:
400
,
fit:
BoxFit
.
contain
,
),
const
SizedBox
(
height:
32
),
/// Organization Structure Button
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
20
,
vertical:
8
),
decoration:
BoxDecoration
(
border:
Border
.
all
(
color:
const
Color
(
0xFF1487C9
),
// border color
width:
1.2
,
// thickness of the border
),
color:
const
Color
(
0xffEDF8FF
),
borderRadius:
BorderRadius
.
circular
(
30
),
),
child:
Row
(
mainAxisSize:
MainAxisSize
.
min
,
children:
[
SvgPicture
.
asset
(
"assets/svg/hrm/groupIc.svg"
,
height:
29
,
width:
29
,
fit:
BoxFit
.
contain
,
),
const
SizedBox
(
width:
7
),
const
Text
(
"Organization Structure"
,
style:
TextStyle
(
fontSize:
15
,
fontWeight:
FontWeight
.
w500
,
fontStyle:
FontStyle
.
normal
,
fontFamily:
"Plus Jakarta Sans"
),
),
const
Icon
(
Icons
.
chevron_right
,
color:
Colors
.
black54
),
],
),
),
],
),
),
/// Bottom Grid Section
Padding
(
padding:
const
EdgeInsets
.
all
(
15
),
child:
GridView
.
count
(
crossAxisCount:
2
,
// items per row
crossAxisSpacing:
8.5
,
mainAxisSpacing:
16
,
childAspectRatio:
2.0
,
// tiles height
shrinkWrap:
true
,
physics:
const
NeverScrollableScrollPhysics
(),
children:
[
_buildTile
(
label:
"Attendance List"
,
subtitle:
"Real-time request"
,
assetIcon:
"assets/svg/hrm/attendanceList.svg"
,
txtColor:
const
Color
(
0xff1487C9
),
onTap:
()
{
Navigator
.
push
(
context
,
MaterialPageRoute
(
builder:
(
context
)
=>
const
Attendancelist
(),
),
);
},
),
_buildTile
(
label:
"Leave Application"
,
subtitle:
"Apply & Track"
,
assetIcon:
"assets/svg/hrm/leaveApplication.svg"
,
txtColor:
const
Color
(
0xff1487C9
),
onTap:
()
{
Navigator
.
push
(
context
,
MaterialPageRoute
(
builder:
(
context
)
=>
const
LeaveApplicationScreen
(),
),
);
},
),
_buildTile
(
label:
"Rewards List"
,
subtitle:
"Track earned rewards"
,
assetIcon:
"assets/svg/hrm/rewardList.svg"
,
txtColor:
const
Color
(
0xff1487C9
),
onTap:
()
{
Navigator
.
push
(
context
,
MaterialPageRoute
(
builder:
(
context
)
=>
const
RewardListScreen
(),
),
);
},
),
_buildTile
(
label:
"Tour Expenses"
,
subtitle:
"Submit and manage claims"
,
assetIcon:
"assets/svg/hrm/tourExp.svg"
,
txtColor:
const
Color
(
0xff1487C9
),
onTap:
()
{
Navigator
.
push
(
context
,
MaterialPageRoute
(
builder:
(
context
)
=>
const
TourExpensesListScreen
(),
),
);
},
),
],
),
),
],
),
],
),
],
),
),
);
}
/// Reusable Tile Widget (Row style)
Widget
_buildTile
({
required
String
label
,
required
String
subtitle
,
required
String
assetIcon
,
// SVG/PNG asset instead of IconData
required
Color
txtColor
,
VoidCallback
?
onTap
,
})
{
return
InkWell
(
onTap:
onTap
,
borderRadius:
BorderRadius
.
circular
(
20
),
child:
Container
(
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
20
),
),
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
12
,
vertical:
14
),
child:
Row
(
children:
[
/// Left side text
Expanded
(
flex:
2
,
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
label
,
style:
TextStyle
(
fontSize:
15
,
fontFamily:
"Plus Jakarta Sans"
,
fontStyle:
FontStyle
.
normal
,
fontWeight:
FontWeight
.
w500
,
color:
txtColor
,
),
),
const
SizedBox
(
height:
5
),
Text
(
subtitle
,
style:
const
TextStyle
(
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w400
,
fontSize:
12
,
height:
1.4
,
color:
Color
(
0xff818181
),
),
),
],
),
),
/// Right side icon (SVG/PNG)
Expanded
(
child:
Align
(
alignment:
Alignment
.
centerRight
,
child:
Container
(
height:
48
,
width:
48
,
decoration:
BoxDecoration
(
shape:
BoxShape
.
circle
,
color:
const
Color
(
0xFFEDF8FF
),
// icon bg
),
child:
Center
(
child:
SvgPicture
.
asset
(
height:
28
,
width:
28
,
assetIcon
,
fit:
BoxFit
.
contain
,
),
),
),
),
),
],
),
),
);
}
}
\ No newline at end of file
lib/screens/hrm/LeaveApplicationScreen.dart
0 → 100644
View file @
6d1deaf2
import
'package:flutter/material.dart'
;
import
'package:flutter_svg/flutter_svg.dart'
;
class
LeaveApplicationScreen
extends
StatefulWidget
{
const
LeaveApplicationScreen
({
Key
?
key
})
:
super
(
key:
key
);
@override
State
<
LeaveApplicationScreen
>
createState
()
=>
_LeaveApplicationScreenState
();
}
class
_LeaveApplicationScreenState
extends
State
<
LeaveApplicationScreen
>
{
final
TextEditingController
_reasonController
=
TextEditingController
();
DateTime
?
_startDate
;
DateTime
?
_endDate
;
Future
<
void
>
_pickDate
({
required
bool
isStart
})
async
{
final
DateTime
?
picked
=
await
showDatePicker
(
context:
context
,
initialDate:
DateTime
.
now
(),
firstDate:
DateTime
(
2020
),
lastDate:
DateTime
(
2100
),
);
if
(
picked
!=
null
)
{
setState
(()
{
if
(
isStart
)
{
_startDate
=
picked
;
}
else
{
_endDate
=
picked
;
}
});
}
}
@override
Widget
build
(
BuildContext
context
)
{
return
Scaffold
(
appBar:
AppBar
(
automaticallyImplyLeading:
false
,
backgroundColor:
Colors
.
white
,
title:
Row
(
children:
[
InkResponse
(
onTap:
()
=>
Navigator
.
pop
(
context
,
true
),
child:
SvgPicture
.
asset
(
"assets/svg/appbar_back_button.svg"
,
height:
25
,
),
),
const
SizedBox
(
width:
10
),
const
Text
(
"Leave Application"
,
style:
TextStyle
(
fontSize:
18
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w600
,
color:
Colors
.
black87
,
),
),
],
),
),
body:
Padding
(
padding:
const
EdgeInsets
.
all
(
16.0
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Align
(
alignment:
Alignment
.
topRight
,
child:
Text
(
"Dummy Screen !"
,
style:
TextStyle
(
fontSize:
10
,
height:
1
,
fontWeight:
FontWeight
.
bold
,
),
),
),
const
Text
(
"Apply for Leave"
,
style:
TextStyle
(
fontSize:
20
,
fontWeight:
FontWeight
.
bold
,
),
),
const
SizedBox
(
height:
20
),
TextField
(
controller:
_reasonController
,
decoration:
const
InputDecoration
(
labelText:
"Reason for Leave"
,
border:
OutlineInputBorder
(),
),
maxLines:
3
,
),
const
SizedBox
(
height:
20
),
Row
(
children:
[
Expanded
(
child:
ElevatedButton
(
onPressed:
()
=>
_pickDate
(
isStart:
true
),
child:
Text
(
_startDate
==
null
?
"Select Start Date"
:
"Start:
${_startDate!.toLocal()}
"
.
split
(
' '
)[
0
]),
),
),
const
SizedBox
(
width:
10
),
Expanded
(
child:
ElevatedButton
(
onPressed:
()
=>
_pickDate
(
isStart:
false
),
child:
Text
(
_endDate
==
null
?
"Select End Date"
:
"End:
${_endDate!.toLocal()}
"
.
split
(
' '
)[
0
]),
),
),
],
),
const
Spacer
(),
SizedBox
(
width:
double
.
infinity
,
child:
ElevatedButton
(
onPressed:
()
{
// Handle submission logic here
ScaffoldMessenger
.
of
(
context
).
showSnackBar
(
const
SnackBar
(
content:
Text
(
"Leave application submitted"
)),
);
},
child:
const
Text
(
"Submit Application"
),
),
),
],
),
),
);
}
}
lib/screens/hrm/RewardListScreen.dart
0 → 100644
View file @
6d1deaf2
import
'package:dotted_line/dotted_line.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter_svg/svg.dart'
;
import
'package:generp/screens/hrm/RewardSearchScreen.dart'
;
import
'package:provider/provider.dart'
;
import
'../../Notifiers/hrmProvider/rewardListProvider.dart'
;
import
'../../Utils/app_colors.dart'
;
class
RewardListScreen
extends
StatefulWidget
{
const
RewardListScreen
({
Key
?
key
})
:
super
(
key:
key
);
@override
State
<
RewardListScreen
>
createState
()
=>
_RewardListScreenState
();
}
class
_RewardListScreenState
extends
State
<
RewardListScreen
>
{
@override
Widget
build
(
BuildContext
context
)
{
return
ChangeNotifierProvider
(
create:
(
_
)
=>
RewardListProvider
()..
fetchRewardList
(
context
),
child:
Consumer
<
RewardListProvider
>(
builder:
(
context
,
provider
,
child
)
{
return
Scaffold
(
appBar:
AppBar
(
automaticallyImplyLeading:
false
,
backgroundColor:
Colors
.
white
,
title:
Row
(
children:
[
InkResponse
(
onTap:
()
=>
Navigator
.
pop
(
context
,
true
),
child:
SvgPicture
.
asset
(
"assets/svg/appbar_back_button.svg"
,
height:
25
,
),
),
const
SizedBox
(
width:
10
),
Text
(
"Reward List"
,
style:
TextStyle
(
fontSize:
18
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w600
,
color:
AppColors
.
semi_black
,
),
),
],
),
actions:
[
InkResponse
(
onTap:
()
{
Navigator
.
push
(
context
,
MaterialPageRoute
(
builder:
(
context
)
=>
RewardSearchScreen
(),
settings:
const
RouteSettings
(
name:
'AddLiveAttendanceScreen'
,
),
),
).
then
((
_
)
{
});
},
child:
SvgPicture
.
asset
(
"assets/svg/search_ic.svg"
,
height:
25
,
),
),
const
SizedBox
(
width:
20
),
],
),
backgroundColor:
Color
(
0xFFF6F6F8
),
body:
Builder
(
builder:
(
context
)
{
if
(
provider
.
isLoading
)
{
return
const
Center
(
child:
CircularProgressIndicator
(
color:
Colors
.
blue
,));
}
if
(
provider
.
errorMessage
!=
null
)
{
return
Center
(
child:
Text
(
provider
.
errorMessage
!));
}
if
(
provider
.
response
==
null
)
{
return
const
Center
(
child:
Text
(
"No details found"
));
}
final
rewardDetail
=
provider
.
response
!;
final
rewardResponse
=
provider
.
response
!;
final
rewards
=
rewardResponse
.
rewardsList
;
// main list object
final
achieved
=
rewardResponse
.
achievedAmount
??
"0"
;
final
disbursed
=
rewardResponse
.
disbursedAmount
??
"0"
;
final
balance
=
rewardResponse
.
balanceAmount
??
"0"
;
return
SingleChildScrollView
(
padding:
const
EdgeInsets
.
all
(
16
),
child:
Column
(
children:
[
/// --- Top Summary Cards ---
Container
(
height:
110
,
width:
double
.
infinity
,
padding:
const
EdgeInsets
.
all
(
18
),
decoration:
BoxDecoration
(
color:
const
Color
(
0xffd9ffd6
),
borderRadius:
BorderRadius
.
circular
(
18
),
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
Text
(
"₹
${achieved}
"
,
// Achieved Amount from response
style:
const
TextStyle
(
fontSize:
20
,
color:
Color
(
0xff0D9C00
),
fontStyle:
FontStyle
.
normal
,
fontWeight:
FontWeight
.
w500
),
),
const
SizedBox
(
height:
10
),
const
Text
(
"Achievement Amount"
,
style:
TextStyle
(
fontSize:
14
,
color:
Color
(
0xff2D2D2D
),
fontStyle:
FontStyle
.
normal
,
fontWeight:
FontWeight
.
w400
),
),
],
),
),
const
SizedBox
(
height:
12
),
Row
(
children:
[
Expanded
(
child:
Container
(
height:
110
,
padding:
const
EdgeInsets
.
all
(
16
),
decoration:
BoxDecoration
(
color:
const
Color
(
0xffe8ddff
),
borderRadius:
BorderRadius
.
circular
(
16
),
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
"₹
${disbursed}
"
,
// Disbursed Amount
style:
const
TextStyle
(
fontSize:
20
,
color:
Color
(
0xff493272
),
fontStyle:
FontStyle
.
normal
,
fontWeight:
FontWeight
.
w500
),
),
const
SizedBox
(
height:
8
),
const
Text
(
"Disbursed Amount"
,
style:
TextStyle
(
fontSize:
14
,
color:
Color
(
0xff2D2D2D
),
fontStyle:
FontStyle
.
normal
,
fontWeight:
FontWeight
.
w400
)),
],
),
),
),
const
SizedBox
(
width:
12
),
Expanded
(
child:
Container
(
height:
110
,
padding:
const
EdgeInsets
.
all
(
16
),
decoration:
BoxDecoration
(
color:
const
Color
(
0xfffffbc3
),
borderRadius:
BorderRadius
.
circular
(
16
),
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
"₹
${balance}
"
,
// Balance Amount
style:
const
TextStyle
(
fontSize:
18
,
color:
Color
(
0xff605C00
),
fontFamily:
"Plus Jakarta Sans"
,
fontStyle:
FontStyle
.
normal
,
fontWeight:
FontWeight
.
w500
),
),
const
SizedBox
(
height:
8
),
const
Text
(
"Balance Amount"
,
style:
TextStyle
(
fontSize:
14
,
color:
Color
(
0xff2D2D2D
),
fontFamily:
"Plus Jakarta Sans"
,
fontStyle:
FontStyle
.
normal
,
fontWeight:
FontWeight
.
w400
)),
],
),
),
),
],
),
const
SizedBox
(
height:
20
),
/// --- Reward List Card ---
if
(
rewards
!=
null
)
_rewardListCard
(
title:
rewards
.
description
??
"-"
,
// rewardsList fields
dateTime:
rewards
.
dateTime
??
"-"
,
achieved:
achieved
,
disbursed:
disbursed
,
balance:
balance
,
enteredBy:
rewards
.
enteredBy
??
"-"
,
)
else
const
Text
(
"No rewards available"
),
],
),
);
}
),
);
}
)
);
}
/// Reusable Reward Card Function
Widget
_rewardListCard
({
required
String
title
,
required
String
dateTime
,
required
String
achieved
,
required
String
disbursed
,
required
String
balance
,
required
String
enteredBy
,
})
{
return
Container
(
margin:
const
EdgeInsets
.
only
(
bottom:
16
),
padding:
const
EdgeInsets
.
all
(
16
),
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
16
),
boxShadow:
[
BoxShadow
(
color:
Colors
.
grey
.
withOpacity
(
0.1
),
blurRadius:
6
,
offset:
const
Offset
(
0
,
3
),
)
],
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
/// Header Row
Row
(
children:
[
CircleAvatar
(
radius:
22.5
,
backgroundColor:
Color
(
0xffEDF8FF
),
child:
SvgPicture
.
asset
(
height:
28
,
width:
28
,
"assets/svg/hrm/rewardList.svg"
,
fit:
BoxFit
.
contain
,
),
),
const
SizedBox
(
width:
8
),
Expanded
(
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
title
,
style:
const
TextStyle
(
fontSize:
14.5
,
color:
Color
(
0xff2D2D2D
),
fontFamily:
"Plus Jakarta Sans"
,
fontStyle:
FontStyle
.
normal
,
fontWeight:
FontWeight
.
w400
),
),
Text
(
dateTime
,
style:
const
TextStyle
(
fontSize:
12.5
,
color:
Color
(
0xff818181
),
fontFamily:
"Plus Jakarta Sans"
,
fontStyle:
FontStyle
.
normal
,
fontWeight:
FontWeight
.
w400
),
),
],
),
),
],
),
const
SizedBox
(
height:
12
),
/// Amount Details
Padding
(
padding:
const
EdgeInsets
.
all
(
2.0
),
child:
Row
(
children:
[
const
Text
(
"Amount Details"
,
style:
TextStyle
(
fontSize:
15
,
color:
Color
(
0xff2D2D2D
),
fontFamily:
"Plus Jakarta Sans"
,
fontStyle:
FontStyle
.
normal
,
fontWeight:
FontWeight
.
w600
),
),
const
SizedBox
(
width:
10
),
Expanded
(
child:
DottedLine
(
dashLength:
4
,
dashGapLength:
2
,
lineThickness:
1
,
dashColor:
Color
(
0xff999999
),
)
),
],
),
),
const
SizedBox
(
height:
6
),
_buildKeyValue
(
"Achieved Amount"
,
achieved
),
_buildKeyValue
(
"Disbursed Amount"
,
disbursed
),
_buildKeyValue
(
"Balance Amount"
,
balance
),
const
SizedBox
(
height:
10
),
/// Employee Details
Padding
(
padding:
const
EdgeInsets
.
all
(
2.0
),
child:
Row
(
children:
[
const
Text
(
"Employee Details"
,
style:
TextStyle
(
fontSize:
15
,
color:
Color
(
0xff2D2D2D
),
fontFamily:
"Plus Jakarta Sans"
,
fontStyle:
FontStyle
.
normal
,
fontWeight:
FontWeight
.
w600
),
),
const
SizedBox
(
width:
10
),
Expanded
(
child:
DottedLine
(
dashLength:
4
,
dashGapLength:
2
,
lineThickness:
1
,
dashColor:
Color
(
0xff999999
),
)
),
],
),
),
_buildKeyValue
(
"Entered By"
,
enteredBy
),
],
),
);
}
/// Key-Value Row
Widget
_buildKeyValue
(
String
key
,
String
value
)
{
return
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
3.5
,
horizontal:
2
),
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
Text
(
key
,
style:
const
TextStyle
(
fontSize:
14
,
color:
Color
(
0xff2D2D2D
),
fontFamily:
"Plus Jakarta Sans"
,
fontStyle:
FontStyle
.
normal
,
fontWeight:
FontWeight
.
w400
)
),
Text
(
value
,
style:
const
TextStyle
(
fontSize:
14
,
color:
Color
(
0xff2D2D2D
),
fontFamily:
"Plus Jakarta Sans"
,
fontStyle:
FontStyle
.
normal
,
fontWeight:
FontWeight
.
w400
)
),
],
),
);
}
}
lib/screens/hrm/RewardSearchScreen.dart
0 → 100644
View file @
6d1deaf2
import
'package:flutter/material.dart'
;
import
'package:flutter_svg/svg.dart'
;
import
'../../Utils/app_colors.dart'
;
class
RewardSearchScreen
extends
StatefulWidget
{
const
RewardSearchScreen
({
super
.
key
});
@override
State
<
RewardSearchScreen
>
createState
()
=>
_RewardSearchScreenState
();
}
class
_RewardSearchScreenState
extends
State
<
RewardSearchScreen
>
{
final
TextEditingController
_searchController
=
TextEditingController
();
// Dummy data
final
List
<
String
>
accounts
=
[
"Siddha Bank"
,
"Siddharth Shivam"
,
"Sidheshwar Temple"
,
"Axis Bank"
,
"SBI"
,
];
final
List
<
String
>
inquiries
=
[
"Inquiry Siddha Bank"
,
"Inquiry Sidharth"
,
"Inquiry Sidheshwar"
,
"Customer SBI"
,
];
final
List
<
String
>
leads
=
[
"Lead Siddha Bank"
,
"Lead Shivam"
,
"Lead Sidheshwar"
,
"Lead HDFC"
,
];
// Filtered data
String
query
=
""
;
@override
Widget
build
(
BuildContext
context
)
{
final
filteredAccounts
=
accounts
.
where
((
e
)
=>
e
.
toLowerCase
().
contains
(
query
.
toLowerCase
())).
toList
();
final
filteredInquiries
=
inquiries
.
where
((
e
)
=>
e
.
toLowerCase
().
contains
(
query
.
toLowerCase
())).
toList
();
final
filteredLeads
=
leads
.
where
((
e
)
=>
e
.
toLowerCase
().
contains
(
query
.
toLowerCase
())).
toList
();
return
Scaffold
(
appBar:
AppBar
(
automaticallyImplyLeading:
false
,
backgroundColor:
const
Color
(
0xFFFFFFFF
),
title:
Row
(
children:
[
InkResponse
(
onTap:
()
=>
Navigator
.
pop
(
context
,
true
),
child:
SvgPicture
.
asset
(
"assets/svg/appbar_back_button.svg"
,
height:
25
,
),
),
const
SizedBox
(
width:
10
),
const
Text
(
"Search"
,
style:
TextStyle
(
fontSize:
18
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w600
,
color:
Colors
.
black87
,
),
),
],
),
),
backgroundColor:
Color
(
0xFFF6F6F8
),
body:
SingleChildScrollView
(
padding:
const
EdgeInsets
.
all
(
16
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
/// Search Field
TextField
(
controller:
_searchController
,
onChanged:
(
value
)
{
setState
(()
{
query
=
value
;
});
},
decoration:
InputDecoration
(
hintText:
"Search"
,
prefixIcon:
const
Icon
(
Icons
.
search
,
color:
Colors
.
black54
),
filled:
true
,
fillColor:
const
Color
(
0xffFFFFFF
),
contentPadding:
const
EdgeInsets
.
symmetric
(
horizontal:
16
,
vertical:
12
),
border:
OutlineInputBorder
(
borderRadius:
BorderRadius
.
circular
(
12
),
borderSide:
BorderSide
.
none
,
),
),
),
const
SizedBox
(
height:
20
),
if
(
query
.
isNotEmpty
)
Text
(
'Result for “
$query
”'
,
style:
const
TextStyle
(
fontSize:
18
,
color:
Color
(
0xff1487C9
),
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w600
,
),
),
const
SizedBox
(
height:
20
),
if
(
query
.
isNotEmpty
&&
filteredAccounts
.
isNotEmpty
)
_buildSection
(
"Accounts"
,
filteredAccounts
),
if
(
query
.
isNotEmpty
&&
filteredInquiries
.
isNotEmpty
)
_buildSection
(
"Inquiries"
,
filteredInquiries
),
if
(
query
.
isNotEmpty
&&
filteredLeads
.
isNotEmpty
)
_buildSection
(
"Leads"
,
filteredLeads
),
if
(
query
.
isNotEmpty
&&
filteredAccounts
.
isEmpty
&&
filteredInquiries
.
isEmpty
&&
filteredLeads
.
isEmpty
)
const
Center
(
child:
Text
(
"No results found"
,
style:
TextStyle
(
fontSize:
14
,
color:
Colors
.
grey
),
),
),
],
),
),
);
}
/// Reusable Section Widget
Widget
_buildSection
(
String
title
,
List
<
String
>
items
)
{
return
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
title
,
style:
const
TextStyle
(
fontSize:
14
,
fontWeight:
FontWeight
.
w500
,
fontFamily:
"Plus Jakarta Sans"
,
color:
Color
(
0xff2D2D2D
),
),
),
const
SizedBox
(
height:
10
),
Column
(
children:
items
.
map
((
e
)
=>
Container
(
margin:
const
EdgeInsets
.
only
(
bottom:
12
),
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
12
,
vertical:
12
),
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
12
),
boxShadow:
[
BoxShadow
(
color:
Colors
.
grey
.
withOpacity
(
0.1
),
blurRadius:
5
,
offset:
const
Offset
(
0
,
3
),
)
],
),
child:
Row
(
children:
[
Container
(
height:
32
,
width:
32
,
decoration:
BoxDecoration
(
color:
const
Color
(
0xffE8F3FF
),
borderRadius:
BorderRadius
.
circular
(
8
),
),
child:
const
Icon
(
Icons
.
search
,
color:
Color
(
0xff0066FF
),
size:
20
),
),
const
SizedBox
(
width:
10
),
Expanded
(
child:
Text
(
e
,
style:
const
TextStyle
(
fontSize:
14
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w400
,
color:
Color
(
0xff2D2D2D
),
),
overflow:
TextOverflow
.
ellipsis
,
),
),
],
),
))
.
toList
(),
)
],
);
}
}
lib/screens/hrm/TourExpensesDetailsScreen.dart
0 → 100644
View file @
6d1deaf2
import
'package:dotted_line/dotted_line.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter_svg/svg.dart'
;
import
'package:provider/provider.dart'
;
import
'../../Notifiers/hrmProvider/tourExpensesDetailsProvider.dart'
;
import
'../../Utils/app_colors.dart'
;
import
'../finance/FileViewer.dart'
;
class
TourExpensesDetailsScreen
extends
StatelessWidget
{
final
String
tourBillId
;
const
TourExpensesDetailsScreen
({
Key
?
key
,
required
this
.
tourBillId
})
:
super
(
key:
key
);
@override
Widget
build
(
BuildContext
context
)
{
return
ChangeNotifierProvider
(
create:
(
_
)
=>
TourExpensesDetailsProvider
()
..
fetchTourExpensesDetails
(
context
,
tourBillId
),
child:
Scaffold
(
appBar:
AppBar
(
automaticallyImplyLeading:
false
,
backgroundColor:
Color
(
0xFFFFFFFF
),
title:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
start
,
crossAxisAlignment:
CrossAxisAlignment
.
center
,
children:
[
InkResponse
(
onTap:
()
=>
Navigator
.
pop
(
context
,
true
),
child:
SvgPicture
.
asset
(
"assets/svg/appbar_back_button.svg"
,
height:
25
,
),
),
SizedBox
(
width:
10
),
InkResponse
(
onTap:
()
=>
Navigator
.
pop
(
context
,
true
),
child:
Text
(
"Tour Expenses"
,
style:
TextStyle
(
fontSize:
18
,
height:
1.1
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w600
,
color:
AppColors
.
semi_black
,
),
),
),
],
),
),
backgroundColor:
AppColors
.
scaffold_bg_color
,
body:
Consumer
<
TourExpensesDetailsProvider
>(
builder:
(
context
,
provider
,
child
)
{
if
(
provider
.
isLoading
)
{
return
const
Center
(
child:
CircularProgressIndicator
());
}
if
(
provider
.
errorMessage
!=
null
)
{
return
Center
(
child:
Text
(
provider
.
errorMessage
!));
}
final
response
=
provider
.
response
;
if
(
response
==
null
)
{
return
const
Center
(
child:
Text
(
"No data available"
));
}
return
SingleChildScrollView
(
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
/// Header Card at the very top
_expenseHeaderCard
(
title:
response
.
requestDetails
?.
placeOfVisit
??
"Tour"
,
date:
response
.
tourExpenses
?.
fromDate
??
"-"
,
status:
response
.
requestDetails
?.
approvalStatus
??
"-"
,
details:
[
{
"key"
:
"TL Pending Approval Amount"
,
"value"
:
"-"
},
{
"key"
:
"Total Approved Amount"
,
"value"
:
response
.
tourExpenses
?.
appliedAmount
??
"-"
},
{
"key"
:
"Total Balance Amount"
,
"value"
:
"-"
},
{
"key"
:
"HR Expiring Amount (Within 24Hrs)"
,
"value"
:
"-"
},
{
"key"
:
"HR Pending Approval Amount"
,
"value"
:
"-"
},
{
"key"
:
"Total Disbursed Amount"
,
"value"
:
"-"
},
],
),
const
SizedBox
(
height:
16
),
/// Tour Expense Card (Main Summary)
if
(
response
.
requestDetails
!=
null
&&
response
.
tourExpenses
!=
null
)
...[
const
SizedBox
(
height:
10
),
Padding
(
padding:
const
EdgeInsets
.
only
(
left:
30.0
),
child:
Text
(
"Tour Summary"
,
style:
TextStyle
(
fontFamily:
"JakartaMedium"
,
fontSize:
14
,
color:
AppColors
.
grey_thick
,
),
),
),
const
SizedBox
(
height:
8
),
SizedBox
(
height:
220
,
// adjust height to match your card
child:
ListView
(
scrollDirection:
Axis
.
horizontal
,
padding:
EdgeInsets
.
symmetric
(
// horizontal margin for centering
horizontal:
MediaQuery
.
of
(
context
).
size
.
width
*
0.05
,
),
children:
[
SizedBox
(
width:
MediaQuery
.
of
(
context
).
size
.
width
*
0.85
,
child:
_tourExpenseCard
(
employeeName:
response
.
requestDetails
?.
employeeName
??
"-"
,
placeOfVisit:
response
.
requestDetails
?.
placeOfVisit
??
"-"
,
daAmount:
response
.
tourExpenses
?.
da
??
"0"
,
totalAmount:
response
.
tourExpenses
?.
appliedAmount
??
"0"
,
fromDate:
response
.
tourExpenses
?.
fromDate
??
"-"
,
toDate:
response
.
tourExpenses
?.
toDate
??
"-"
,
remarks:
response
.
tourExpenses
?.
extraNote
??
"-"
,
),
),
],
),
),
],
const
SizedBox
(
height:
10
),
/// Travel Expenses Cards
if
(
response
.
travelExpenses
!=
null
&&
response
.
travelExpenses
!.
isNotEmpty
)
...[
const
SizedBox
(
height:
10
),
Padding
(
padding:
const
EdgeInsets
.
only
(
left:
30.0
),
child:
Text
(
"Travel Expenses"
,
style:
TextStyle
(
fontFamily:
"JakartaMedium"
,
fontSize:
14
,
color:
AppColors
.
grey_thick
,
),
),
),
const
SizedBox
(
height:
8
),
SizedBox
(
height:
216
,
child:
ListView
.
separated
(
scrollDirection:
Axis
.
horizontal
,
padding:
EdgeInsets
.
symmetric
(
horizontal:
MediaQuery
.
of
(
context
).
size
.
width
*
0.04
,
),
itemCount:
response
.
travelExpenses
!.
length
,
separatorBuilder:
(
_
,
__
)
=>
const
SizedBox
(
width:
12
),
itemBuilder:
(
context
,
index
)
{
final
t
=
response
.
travelExpenses
![
index
];
return
SizedBox
(
width:
MediaQuery
.
of
(
context
).
size
.
width
*
0.90
,
// card width
child:
_travelExpenseCard
(
travelType:
t
.
travelType
??
"-"
,
amount:
t
.
fare
??
"0"
,
from:
t
.
froma
??
"-"
,
to:
t
.
toa
??
"-"
,
onViewTap:
()
{
debugPrint
(
"Open:
${t.imageDirFilePath}
"
);
//Fileviewer(fileName: "", fileUrl: t.imageDirFilePath.toString())
showDialog
(
context:
context
,
builder:
(
_
)
=>
Dialog
(
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
12
),
),
child:
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
12
),
child:
Image
.
network
(
t
.
imageDirFilePath
.
toString
())
),
),
);
},
),
);
},
),
)
],
const
SizedBox
(
height:
10
),
/// Hotel Expenses Cards
if
(
response
.
hotelExpenses
!=
null
&&
response
.
hotelExpenses
!.
isNotEmpty
)
...[
const
SizedBox
(
height:
10
),
Padding
(
padding:
const
EdgeInsets
.
only
(
left:
30.0
),
child:
Text
(
"Hotel Expenses"
,
style:
TextStyle
(
fontFamily:
"JakartaMedium"
,
fontSize:
14
,
color:
AppColors
.
grey_thick
,
),
),
),
const
SizedBox
(
height:
8
),
SizedBox
(
height:
216
,
child:
ListView
.
separated
(
scrollDirection:
Axis
.
horizontal
,
padding:
EdgeInsets
.
symmetric
(
horizontal:
MediaQuery
.
of
(
context
).
size
.
width
*
0.04
,
),
itemCount:
response
.
hotelExpenses
!.
length
,
separatorBuilder:
(
_
,
__
)
=>
const
SizedBox
(
width:
12
),
itemBuilder:
(
context
,
index
)
{
final
h
=
response
.
hotelExpenses
![
index
];
return
SizedBox
(
width:
MediaQuery
.
of
(
context
).
size
.
width
*
0.90
,
child:
_hotelExpenseCard
(
hotelName:
h
.
hotelName
??
"-"
,
amount:
h
.
amount
??
"0"
,
fromDate:
h
.
fromDate
??
"-"
,
toDate:
h
.
toDate
??
"-"
,
onViewTap:
()
{
debugPrint
(
"Open:
${h.imageDirFilePath}
"
);
showDialog
(
context:
context
,
builder:
(
_
)
=>
Dialog
(
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
12
),
),
child:
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
12
),
child:
Image
.
network
(
h
.
imageDirFilePath
.
toString
())
),
),
);
},
),
);
},
),
),
],
const
SizedBox
(
height:
10
),
/// Other Expenses Cards
if
(
response
.
otherExpenses
!=
null
&&
response
.
otherExpenses
!.
isNotEmpty
)
...[
const
SizedBox
(
height:
10
),
Padding
(
padding:
const
EdgeInsets
.
only
(
left:
30.0
),
child:
Text
(
"Other Expenses"
,
style:
TextStyle
(
fontFamily:
"JakartaMedium"
,
fontSize:
14
,
color:
AppColors
.
grey_thick
,
),
),
),
const
SizedBox
(
height:
8
),
SizedBox
(
height:
216
,
child:
ListView
.
separated
(
scrollDirection:
Axis
.
horizontal
,
padding:
EdgeInsets
.
symmetric
(
horizontal:
MediaQuery
.
of
(
context
).
size
.
width
*
0.04
,
),
itemCount:
response
.
otherExpenses
!.
length
,
separatorBuilder:
(
_
,
__
)
=>
const
SizedBox
(
width:
12
),
itemBuilder:
(
context
,
index
)
{
final
o
=
response
.
otherExpenses
![
index
];
return
SizedBox
(
width:
MediaQuery
.
of
(
context
).
size
.
width
*
0.90
,
child:
_otherExpenseCard
(
description:
o
.
otherDesc
??
"-"
,
amount:
o
.
otherAmount
??
"0"
,
date:
o
.
otherDate
??
"-"
,
onViewTap:
()
{
debugPrint
(
"Open:
${o.imageDirFilePath}
"
);
showDialog
(
context:
context
,
builder:
(
_
)
=>
Dialog
(
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
12
),
),
child:
ClipRRect
(
borderRadius:
BorderRadius
.
circular
(
12
),
child:
Image
.
network
(
o
.
imageDirFilePath
.
toString
())
),
),
);
},
),
);
},
),
),
],
const
SizedBox
(
height:
25
),
],
),
);
},
),
),
);
}
/// Attach your reusable card functions here
Widget
_tourExpenseCard
({
required
String
employeeName
,
required
String
placeOfVisit
,
required
String
daAmount
,
required
String
totalAmount
,
required
String
fromDate
,
required
String
toDate
,
required
String
remarks
,
})
{
// paste your same implementation here
return
Container
(
margin:
const
EdgeInsets
.
only
(
bottom:
16
),
padding:
const
EdgeInsets
.
all
(
16
),
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
16
),
boxShadow:
[
BoxShadow
(
color:
Colors
.
grey
.
withOpacity
(
0.1
),
blurRadius:
6
,
offset:
const
Offset
(
0
,
3
),
)
],
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Row
(
children:
[
CircleAvatar
(
radius:
22.5
,
backgroundColor:
const
Color
(
0xffFFF3E0
),
child:
SvgPicture
.
asset
(
"assets/svg/hrm/travel_ic.svg"
,
),
),
const
SizedBox
(
width:
8
),
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
employeeName
,
style:
TextStyle
(
fontSize:
14.5
,
fontFamily:
"JakartaMedium"
,
fontWeight:
FontWeight
.
w600
,
color:
AppColors
.
semi_black
,
)
),
Text
(
placeOfVisit
,
style:
TextStyle
(
fontFamily:
"JakartaMedium"
,
fontSize:
14
,
color:
AppColors
.
app_blue
,
),
),
],
),
],
),
const
SizedBox
(
height:
12
),
_buildSectionHeader
(
"Amount Details"
),
_buildKeyValue
(
"DA Amount"
,
daAmount
),
_buildKeyValue
(
"Total Amount"
,
totalAmount
),
const
SizedBox
(
height:
10
),
_buildSectionHeader
(
"Tour Time"
),
_buildKeyValue
(
"From Date"
,
fromDate
),
_buildKeyValue
(
"To Date"
,
toDate
),
const
SizedBox
(
height:
10
),
_buildSectionHeader
(
"Remarks"
),
_buildKeyValue
(
"Extra Note"
,
remarks
),
],
),
);
}
Widget
_travelExpenseCard
({
required
String
travelType
,
required
String
amount
,
required
String
from
,
required
String
to
,
required
VoidCallback
onViewTap
,
})
{
// paste your travel card code here
return
Container
(
margin:
const
EdgeInsets
.
only
(
bottom:
16
),
padding:
const
EdgeInsets
.
all
(
16
),
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
16
),
boxShadow:
[
BoxShadow
(
color:
Colors
.
grey
.
withOpacity
(
0.1
),
blurRadius:
6
,
offset:
const
Offset
(
0
,
3
),
)
],
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
Row
(
children:
[
CircleAvatar
(
radius:
20
,
backgroundColor:
const
Color
(
0xffFFF3E0
),
child:
SvgPicture
.
asset
(
"assets/svg/hrm/travel_ic.svg"
,
),
),
const
SizedBox
(
width:
8
),
Text
(
travelType
,
style:
TextStyle
(
fontSize:
14.5
,
fontFamily:
"JakartaMedium"
,
fontWeight:
FontWeight
.
w600
,
color:
AppColors
.
semi_black
,
)
),
],
),
Text
(
"₹
$amount
"
,
style:
TextStyle
(
fontFamily:
"JakartaMedium"
,
fontSize:
14
,
color:
AppColors
.
app_blue
,
),
),
],
),
const
SizedBox
(
height:
15
),
_buildSectionHeader
(
"Travel Details"
),
const
SizedBox
(
height:
4
),
_buildKeyValue
(
"From"
,
from
),
const
SizedBox
(
height:
2
),
_buildKeyValue
(
"To"
,
to
),
const
SizedBox
(
height:
2
),
_buildKeyValue
(
"Image"
,
"View"
,
isLink:
true
,
onTap:
onViewTap
),
],
),
);
}
Widget
_hotelExpenseCard
({
required
String
hotelName
,
required
String
amount
,
required
String
fromDate
,
required
String
toDate
,
required
VoidCallback
onViewTap
,
})
{
// paste your hotel card code here
return
Container
(
margin:
const
EdgeInsets
.
only
(
bottom:
16
),
padding:
const
EdgeInsets
.
all
(
16
),
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
16
),
boxShadow:
[
BoxShadow
(
color:
Colors
.
grey
.
withOpacity
(
0.1
),
blurRadius:
6
,
offset:
const
Offset
(
0
,
3
),
)
],
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
Row
(
children:
[
CircleAvatar
(
radius:
20
,
backgroundColor:
const
Color
(
0xffFCE4EC
),
child:
SvgPicture
.
asset
(
"assets/svg/hrm/hotel_ic.svg"
,
),
),
const
SizedBox
(
width:
8
),
Text
(
hotelName
,
style:
TextStyle
(
fontSize:
14.5
,
fontFamily:
"JakartaMedium"
,
fontWeight:
FontWeight
.
w600
,
color:
AppColors
.
semi_black
,
)
),
],
),
Text
(
"₹
$amount
"
,
style:
TextStyle
(
fontFamily:
"JakartaMedium"
,
fontSize:
14
,
color:
AppColors
.
app_blue
,
),
),
],
),
const
SizedBox
(
height:
15
),
_buildSectionHeader
(
"Living Details"
),
const
SizedBox
(
height:
4
),
_buildKeyValue
(
"From"
,
fromDate
),
const
SizedBox
(
height:
2
),
_buildKeyValue
(
"To"
,
toDate
),
const
SizedBox
(
height:
2
),
_buildKeyValue
(
"Image"
,
"View"
,
isLink:
true
,
onTap:
onViewTap
),
],
),
);
}
Widget
_otherExpenseCard
({
required
String
description
,
required
String
amount
,
required
String
date
,
required
VoidCallback
onViewTap
,
})
{
// paste your other expense card code here
return
Container
(
margin:
const
EdgeInsets
.
only
(
bottom:
16
),
padding:
const
EdgeInsets
.
all
(
16
),
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
16
),
boxShadow:
[
BoxShadow
(
color:
Colors
.
grey
.
withOpacity
(
0.1
),
blurRadius:
6
,
offset:
const
Offset
(
0
,
3
),
)
],
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
Row
(
children:
[
CircleAvatar
(
radius:
20
,
backgroundColor:
const
Color
(
0xffEDE7F6
),
child:
SvgPicture
.
asset
(
"assets/svg/hrm/books_ic.svg"
,
),
),
const
SizedBox
(
width:
8
),
Text
(
description
,
style:
TextStyle
(
fontSize:
14.5
,
fontFamily:
"JakartaMedium"
,
fontWeight:
FontWeight
.
w600
,
color:
AppColors
.
semi_black
,
)
),
],
),
Text
(
"₹
$amount
"
,
style:
TextStyle
(
fontFamily:
"JakartaMedium"
,
fontSize:
14
,
color:
AppColors
.
app_blue
,
),
),
],
),
const
SizedBox
(
height:
15
),
_buildSectionHeader
(
"Other Details"
),
const
SizedBox
(
height:
4
),
_buildKeyValue
(
"Date"
,
date
),
const
SizedBox
(
height:
2
),
_buildKeyValue
(
"Description"
,
description
),
const
SizedBox
(
height:
2
),
_buildKeyValue
(
"Image"
,
"View"
,
isLink:
true
,
onTap:
onViewTap
),
],
),
);
}
Widget
_buildSectionHeader
(
String
title
)
{
return
Row
(
children:
[
Text
(
title
,
style:
const
TextStyle
(
fontSize:
14
,
fontFamily:
"JakartaSemiBold"
,
)
),
const
SizedBox
(
width:
16
),
Expanded
(
child:
DottedLine
(
dashGapLength:
3
,
dashGapColor:
Colors
.
white
,
dashColor:
AppColors
.
grey_semi
,
dashLength:
2
,
lineThickness:
0.5
,
),
)
],
);
}
Widget
_buildKeyValue
(
String
key
,
String
value
,
{
bool
isLink
=
false
,
VoidCallback
?
onTap
})
{
return
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
3.5
,
horizontal:
2
),
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
Text
(
key
,
style:
TextStyle
(
fontFamily:
"JakartaRegular"
,
fontSize:
14
,
color:
AppColors
.
semi_black
,
),
),
isLink
?
GestureDetector
(
onTap:
onTap
,
child:
const
Text
(
"View"
,
style:
TextStyle
(
fontSize:
14
,
fontWeight:
FontWeight
.
w500
,
color:
Colors
.
blue
)),
)
:
Text
(
value
,
style:
const
TextStyle
(
fontSize:
14
,
color:
Color
(
0xFF818181
),
),
),
],
),
);
}
Widget
_expenseHeaderCard
({
required
String
title
,
required
String
date
,
required
status
,
required
List
<
Map
<
String
,
String
>>
details
,
})
{
bool
showMore
=
false
;
return
StatefulBuilder
(
builder:
(
context
,
setState
)
{
return
Card
(
margin:
EdgeInsets
.
symmetric
(
horizontal:
0
,
vertical:
1.2
),
shape:
const
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
only
(
bottomLeft:
Radius
.
circular
(
30
),
bottomRight:
Radius
.
circular
(
30
),
),
),
elevation:
2
,
child:
Container
(
decoration:
const
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
only
(
bottomLeft:
Radius
.
circular
(
30
),
bottomRight:
Radius
.
circular
(
30
),
),
),
padding:
const
EdgeInsets
.
symmetric
(
vertical:
12
,
horizontal:
14
),
child:
Column
(
children:
[
/// Header Row
Row
(
children:
[
Container
(
height:
45
,
width:
45
,
padding:
const
EdgeInsets
.
all
(
7.5
),
decoration:
const
BoxDecoration
(
color:
Color
(
0xFFE6F6FF
),
shape:
BoxShape
.
circle
,
),
child:
SvgPicture
.
asset
(
"assets/svg/hrm/tour_main_ic.svg"
),
),
const
SizedBox
(
width:
10
),
Expanded
(
flex:
5
,
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
title
,
style:
const
TextStyle
(
fontSize:
14
,
fontFamily:
"JakartaRegular"
,
color:
Color
(
0xff2D2D2D
)
)
),
const
SizedBox
(
height:
3
),
Text
(
date
,
style:
const
TextStyle
(
fontSize:
12
,
color:
Color
(
0xff818181
)
)
),
],
),
),
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
10
,
vertical:
6
),
decoration:
BoxDecoration
(
borderRadius:
BorderRadius
.
circular
(
8
),
color:
_getStatusBgColor
(
status
),
),
child:
Text
(
status
,
style:
TextStyle
(
fontSize:
14
,
fontFamily:
"JakartaRegular"
,
color:
_getStatusTxtColor
(
status
))),
)
],
),
const
SizedBox
(
height:
10
),
/// Expanded Section
if
(
showMore
)
...[
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
8
),
child:
Row
(
children:
[
const
Text
(
"Amount Details"
,
style:
TextStyle
(
fontSize:
14
,
fontFamily:
"JakartaSemiBold"
,
)
),
const
SizedBox
(
width:
10
),
Expanded
(
child:
DottedLine
(
dashGapLength:
4
,
dashGapColor:
Colors
.
white
,
dashColor:
AppColors
.
grey_semi
,
dashLength:
2
,
lineThickness:
0.5
,
),
),
],
),
),
const
SizedBox
(
height:
6
),
Column
(
children:
details
.
map
((
d
)
{
return
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
8
),
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
spaceBetween
,
children:
[
Expanded
(
flex:
4
,
child:
Text
(
d
[
"key"
]
??
"-"
,
style:
TextStyle
(
fontSize:
14
,
color:
AppColors
.
semi_black
,
fontFamily:
"JakartaRegular"
,
)
)
),
Expanded
(
flex:
3
,
child:
Text
(
d
[
"value"
]
??
"-"
,
textAlign:
TextAlign
.
right
,
style:
const
TextStyle
(
fontSize:
14
,
fontFamily:
"JakartaRegular"
,
color:
Color
(
0xff818181
)
)
)
),
],
),
);
}).
toList
(),
),
],
/// Toggle Button
InkWell
(
onTap:
()
=>
setState
(()
=>
showMore
=
!
showMore
),
child:
Padding
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
8
),
child:
Row
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
[
Text
(
showMore
?
"Hide Details"
:
"View Details"
,
style:
TextStyle
(
fontSize:
14
,
fontFamily:
"JakartaMedium"
,
fontWeight:
FontWeight
.
w500
,
color:
AppColors
.
app_blue
,
)
),
const
SizedBox
(
width:
6
),
Transform
.
flip
(
flipY:
showMore
,
child:
SvgPicture
.
asset
(
"assets/svg/arrow_dropdown.svg"
,
height:
25
,
width:
20
,
color:
AppColors
.
app_blue
,
),
)
],
),
),
)
],
),
),
);
},
);
}
/// Avatar color generator
Color
_getStatusBgColor
(
value
)
{
var
color
=
AppColors
.
approved_bg_color
;
switch
(
value
)
{
case
'HR Approved'
:
return
AppColors
.
approved_bg_color
;
case
'Expired at HR'
:
return
AppColors
.
rejected_bg_color
;
case
'Expired at TL'
:
return
AppColors
.
rejected_bg_color
;
}
return
color
;
}
Color
_getStatusTxtColor
(
value
)
{
var
color
=
AppColors
.
approved_text_color
;
switch
(
value
)
{
case
'HR Approved'
:
return
AppColors
.
approved_text_color
;
case
'Expired at HR'
:
return
AppColors
.
rejected_text_color
;
case
'Expired at TL'
:
return
AppColors
.
rejected_text_color
;
}
return
color
;
}
getText
(
value
)
{
switch
(
value
)
{
case
'HR Approved'
:
return
"A"
;
case
'Expired at HR'
:
return
"E"
;
case
'Expired at TL'
:
return
"E"
;
case
'Updated'
:
return
"U"
;
default
:
return
"Requested"
;
}
}
}
lib/screens/hrm/TourExpensesListScreen.dart
0 → 100644
View file @
6d1deaf2
import
'package:flutter/material.dart'
;
import
'package:flutter/services.dart'
;
import
'package:flutter_svg/svg.dart'
;
import
'package:generp/screens/hrm/TourExpensesDetailsScreen.dart'
;
import
'package:provider/provider.dart'
;
import
'../../Utils/app_colors.dart'
;
import
'../../Models/hrmModels/tourExpensesListResponse.dart'
;
import
'../../Notifiers/hrmProvider/tourExpensesProvider.dart'
;
import
'AddTourExpBillScreen.dart'
;
class
TourExpensesListScreen
extends
StatefulWidget
{
const
TourExpensesListScreen
({
super
.
key
});
@override
State
<
TourExpensesListScreen
>
createState
()
=>
_TourExpensesListScreenState
();
}
class
_TourExpensesListScreenState
extends
State
<
TourExpensesListScreen
>
{
@override
Widget
build
(
BuildContext
context
)
{
return
SafeArea
(
top:
false
,
child:
ChangeNotifierProvider
(
create:
(
_
)
=>
TourExpensesProvider
()..
fetchTourExpenses
(
context
,
"1"
),
child:
Consumer
<
TourExpensesProvider
>(
builder:
(
context
,
provider
,
child
)
{
return
Scaffold
(
appBar:
AppBar
(
automaticallyImplyLeading:
false
,
backgroundColor:
Colors
.
white
,
title:
Row
(
children:
[
InkResponse
(
onTap:
()
=>
Navigator
.
pop
(
context
,
true
),
child:
SvgPicture
.
asset
(
"assets/svg/appbar_back_button.svg"
,
height:
25
,
),
),
const
SizedBox
(
width:
10
),
const
Text
(
"Tour Expenses"
,
style:
TextStyle
(
fontSize:
18
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w600
,
color:
Colors
.
black87
,
),
),
],
),
),
backgroundColor:
const
Color
(
0xFFF6F6F8
),
body:
Column
(
children:
[
Expanded
(
child:
Builder
(
builder:
(
context
)
{
if
(
provider
.
isLoading
)
{
return
const
Center
(
child:
CircularProgressIndicator
(
color:
Colors
.
blue
));
}
if
(
provider
.
errorMessage
!=
null
)
{
return
Center
(
child:
Text
(
provider
.
errorMessage
!));
}
if
(
provider
.
response
?.
tourList
==
null
||
provider
.
response
!.
tourList
!.
isEmpty
)
{
return
const
Center
(
child:
Text
(
"No Tour Expenses Found"
));
}
final
list
=
provider
.
response
!.
tourList
!;
return
ListView
.
builder
(
padding:
const
EdgeInsets
.
all
(
12
),
itemCount:
list
.
length
,
itemBuilder:
(
context
,
index
)
{
final
TourList
item
=
list
[
index
];
return
InkWell
(
onTap:
()
{
/// navigation flow
Navigator
.
push
(
context
,
MaterialPageRoute
(
builder:
(
context
)
=>
TourExpensesDetailsScreen
(
tourBillId:
item
.
id
.
toString
(),
),
),
);
},
child:
Container
(
margin:
const
EdgeInsets
.
symmetric
(
vertical:
6
),
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
14
,
vertical:
12
),
decoration:
BoxDecoration
(
color:
Colors
.
white
,
borderRadius:
BorderRadius
.
circular
(
16
),
),
child:
Row
(
children:
[
/// Left Avatar Circle
Container
(
height:
46
,
width:
46
,
decoration:
BoxDecoration
(
color:
_getAvatarColor
(
item
.
approvalStatus
),
shape:
BoxShape
.
circle
,
),
child:
Center
(
child:
Text
(
getText
(
item
.
approvalStatus
),
style:
TextStyle
(
fontSize:
15
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w500
,
color:
_getAvatarTxtColor
(
item
.
approvalStatus
),
),
),
),
),
const
SizedBox
(
width:
12
),
/// Middle Section
Expanded
(
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
children:
[
Text
(
item
.
placeOfVisit
??
"-"
,
maxLines:
1
,
overflow:
TextOverflow
.
ellipsis
,
style:
const
TextStyle
(
fontSize:
14
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w400
,
color:
Color
(
0xff2d2d2d
),
),
),
Text
(
item
.
appliedDate
??
"-"
,
style:
const
TextStyle
(
fontSize:
12
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w400
,
color:
Color
(
0xff818181
),
),
),
],
),
),
/// Right Section (Applied Amount)
Text
(
"₹
${item.appliedAmount ?? '0'}
"
,
style:
const
TextStyle
(
fontSize:
14
,
fontFamily:
"Plus Jakarta Sans"
,
fontWeight:
FontWeight
.
w500
,
color:
Color
(
0xff1487c9
),
)
),
],
),
),
);
},
);
},
),
),
],
),
floatingActionButtonLocation:
FloatingActionButtonLocation
.
centerFloat
,
floatingActionButton:
InkResponse
(
onTap:
()
{
HapticFeedback
.
selectionClick
();
Navigator
.
push
(
context
,
MaterialPageRoute
(
builder:
(
context
)
=>
const
AddBillScreen
(
pageTitleName:
"Add Bill"
,),
settings:
const
RouteSettings
(
name:
'AddTourExpBillScreen'
),
),
).
then
((
_
)
{
});
// show add bill screen here
},
child:
Container
(
height:
45
,
alignment:
Alignment
.
center
,
margin:
EdgeInsets
.
symmetric
(
horizontal:
20
),
padding:
EdgeInsets
.
symmetric
(
horizontal:
10
,
vertical:
5
),
decoration:
BoxDecoration
(
color:
AppColors
.
app_blue
,
borderRadius:
BorderRadius
.
circular
(
15
),
),
child:
Text
(
"Add Bill"
,
style:
TextStyle
(
fontSize:
15
,
fontFamily:
"JakartaMedium"
,
color:
Colors
.
white
,
),
),
),
),
// /// Bottom Add Bill Button
// bottomNavigationBar: Container(
// padding: const EdgeInsets.all(18),
// color: Colors.white,
// child: ElevatedButton(
// style: ElevatedButton.styleFrom(
// backgroundColor: Color(0xff1487c9),
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(15),
// ),
// padding: const EdgeInsets.symmetric(vertical: 14),
// ),
// onPressed: () {
// // to work
// },
// child: const Text(
// "Add Bill",
// style: TextStyle(
// fontSize: 16,
// fontFamily: "Plus Jakarta Sans",
// fontWeight: FontWeight.w500,
// color: Colors.white,
// ),
// ),
// ),
// ),
);
},
),
),
);
}
/// Avatar color generator
Color
_getAvatarColor
(
value
)
{
var
color
=
AppColors
.
approved_bg_color
;
switch
(
value
)
{
case
'HR Approved'
:
return
AppColors
.
approved_bg_color
;
case
'Expired at HR'
:
return
AppColors
.
rejected_bg_color
;
case
'Expired at TL'
:
return
AppColors
.
rejected_bg_color
;
}
return
color
;
}
Color
_getAvatarTxtColor
(
value
)
{
var
color
=
AppColors
.
approved_text_color
;
switch
(
value
)
{
case
'HR Approved'
:
return
AppColors
.
approved_text_color
;
case
'Expired at HR'
:
return
AppColors
.
rejected_text_color
;
case
'Expired at TL'
:
return
AppColors
.
rejected_text_color
;
}
return
color
;
}
getText
(
value
)
{
switch
(
value
)
{
case
'HR Approved'
:
return
"A"
;
case
'Expired at HR'
:
return
"E"
;
case
'Expired at TL'
:
return
"E"
;
case
'Updated'
:
return
"U"
;
default
:
return
"Requested"
;
}
}
}
lib/screens/notifierExports.dart
View file @
6d1deaf2
...
@@ -56,3 +56,10 @@ export 'package:generp/Notifiers/crmProvider/addProspectLeadsProvider.dart';
...
@@ -56,3 +56,10 @@ export 'package:generp/Notifiers/crmProvider/addProspectLeadsProvider.dart';
export
'package:generp/Notifiers/crmProvider/followUpUpdateProvider.dart'
;
export
'package:generp/Notifiers/crmProvider/followUpUpdateProvider.dart'
;
export
'package:generp/Notifiers/crmProvider/appointmentCalendarProvider.dart'
;
export
'package:generp/Notifiers/crmProvider/appointmentCalendarProvider.dart'
;
export
'package:generp/Notifiers/crmProvider/addNewLeadsandProspectsProvider.dart'
;
export
'package:generp/Notifiers/crmProvider/addNewLeadsandProspectsProvider.dart'
;
export
'package:generp/Notifiers/hrmProvider/attendanceListProvider.dart'
;
export
'package:generp/Notifiers/hrmProvider/AttendanceDetailsProvider.dart'
;
export
'package:generp/Notifiers/hrmProvider/tourExpensesProvider.dart'
;
export
'package:generp/Notifiers/hrmProvider/tourExpensesDetailsProvider.dart'
;
export
'package:generp/Notifiers/hrmProvider/rewardListProvider.dart'
;
lib/screens/screensExports.dart
View file @
6d1deaf2
...
@@ -54,3 +54,5 @@ export 'package:generp/screens/crm/QuotationDetails.dart';
...
@@ -54,3 +54,5 @@ export 'package:generp/screens/crm/QuotationDetails.dart';
export
'package:generp/screens/crm/contactDetails.dart'
;
export
'package:generp/screens/crm/contactDetails.dart'
;
export
'package:generp/screens/crm/editAccountDetails.dart'
;
export
'package:generp/screens/crm/editAccountDetails.dart'
;
export
'package:generp/screens/crm/productDetails.dart'
;
export
'package:generp/screens/crm/productDetails.dart'
;
// hrm screen export
export
'package:generp/screens/hrm/Attendancelist.dart'
;
lib/services/api_calling.dart
View file @
6d1deaf2
...
@@ -25,6 +25,10 @@ import 'package:generp/Models/crmModels/crmProspectDetailsAddLeadsResponse.dart'
...
@@ -25,6 +25,10 @@ import 'package:generp/Models/crmModels/crmProspectDetailsAddLeadsResponse.dart'
import
'package:generp/Models/crmModels/crmProspectDetailsResponse.dart'
;
import
'package:generp/Models/crmModels/crmProspectDetailsResponse.dart'
;
import
'package:generp/Models/financeModels/addDirectPaymentResponse.dart'
;
import
'package:generp/Models/financeModels/addDirectPaymentResponse.dart'
;
import
'package:generp/Models/financeModels/paymentRequisitionPaymentsListResponse.dart'
;
import
'package:generp/Models/financeModels/paymentRequisitionPaymentsListResponse.dart'
;
import
'package:generp/Models/hrmModels/attendanceRequestListResponse.dart'
;
import
'package:generp/Models/hrmModels/rewardListResponse.dart'
;
import
'package:generp/Models/hrmModels/tourExpensesDetailsResponse.dart'
;
import
'package:generp/Models/hrmModels/tourExpensesListResponse.dart'
;
import
'package:generp/Models/ordersModels/PendingTPCAgentListResponse.dart'
;
import
'package:generp/Models/ordersModels/PendingTPCAgentListResponse.dart'
;
import
'package:generp/Models/ordersModels/TPCAgentDetailsResponse.dart'
;
import
'package:generp/Models/ordersModels/TPCAgentDetailsResponse.dart'
;
import
'package:generp/Models/ordersModels/TPCListResponse.dart'
;
import
'package:generp/Models/ordersModels/TPCListResponse.dart'
;
...
@@ -92,6 +96,7 @@ import '../Models/financeModels/paymentRequisitionPaymentsDetailsResponse.dart';
...
@@ -92,6 +96,7 @@ import '../Models/financeModels/paymentRequisitionPaymentsDetailsResponse.dart';
import
'../Models/financeModels/paymentRequisitionPaymentsReceiptsDetailsResponse.dart'
;
import
'../Models/financeModels/paymentRequisitionPaymentsReceiptsDetailsResponse.dart'
;
import
'../Models/financeModels/paymentRequisitionPaymentsReceiptsListResponse.dart'
;
import
'../Models/financeModels/paymentRequisitionPaymentsReceiptsListResponse.dart'
;
import
'../Models/generatorComplaintResponse.dart'
;
import
'../Models/generatorComplaintResponse.dart'
;
import
'../Models/hrmModels/attendanceRequestDetailsResponse.dart'
;
import
'../Models/loadGeneratorDetailsResponse.dart'
;
import
'../Models/loadGeneratorDetailsResponse.dart'
;
import
'../Models/financeModels/financeDashboardPagesResponse.dart'
;
import
'../Models/financeModels/financeDashboardPagesResponse.dart'
;
import
'../Models/ordersModels/AddOrderPaymentSelectAccountResponse.dart'
;
import
'../Models/ordersModels/AddOrderPaymentSelectAccountResponse.dart'
;
...
@@ -4898,6 +4903,143 @@ class ApiCalling {
...
@@ -4898,6 +4903,143 @@ class ApiCalling {
}
}
}
}
///hrm modules
///
static
Future
<
attendanceRequestListResponse
?>
attendanceRequestListAPI
(
empId
,
session
,
type
,
from
,
to
,
)
async
{
try
{
Map
<
String
,
String
>
data
=
{
'emp_id'
:
(
empId
).
toString
(),
'session_id'
:
(
session
).
toString
(),
'type'
:
(
type
),
'from'
:
(
from
),
'to'
:
(
to
),
};
final
res
=
await
post
(
data
,
AttendanceRequestListUrl
,
{});
if
(
res
!=
null
)
{
print
(
data
);
debugPrint
(
res
.
body
);
return
attendanceRequestListResponse
.
fromJson
(
jsonDecode
(
res
.
body
));
}
else
{
debugPrint
(
"Null Response"
);
return
null
;
}
}
catch
(
e
)
{
debugPrint
(
'hello bev=bug
$e
'
);
return
null
;
}
}
static
Future
<
attendanceRequestDetailsResponse
?>
attendanceRequestDetailAPI
(
empId
,
session
,
attendanceRequestId
,
)
async
{
try
{
Map
<
String
,
String
>
data
=
{
'emp_id'
:
(
empId
).
toString
(),
'session_id'
:
(
session
).
toString
(),
'attendance_request_id'
:
(
attendanceRequestId
),
};
final
res
=
await
post
(
data
,
AttendanceRequestDetailsUrl
,
{});
if
(
res
!=
null
)
{
print
(
data
);
debugPrint
(
res
.
body
);
return
attendanceRequestDetailsResponse
.
fromJson
(
jsonDecode
(
res
.
body
));
}
else
{
debugPrint
(
"Null Response"
);
return
null
;
}
}
catch
(
e
)
{
debugPrint
(
'hello bev=bug
$e
'
);
return
null
;
}
}
//reward list
static
Future
<
rewardListResponse
?>
rewardListAPI
(
empId
,
session
,
)
async
{
try
{
Map
<
String
,
String
>
data
=
{
'session_id'
:
(
session
).
toString
(),
'emp_id'
:
(
empId
).
toString
(),
};
final
res
=
await
post
(
data
,
RewardListUrl
,
{});
if
(
res
!=
null
)
{
print
(
data
);
debugPrint
(
res
.
body
);
return
rewardListResponse
.
fromJson
(
jsonDecode
(
res
.
body
));
}
else
{
debugPrint
(
"Null Response"
);
return
null
;
}
}
catch
(
e
)
{
debugPrint
(
'hello bev=bug
$e
'
);
return
null
;
}
}
//tour exp
static
Future
<
tourExpensesListResponse
?>
tourExpensesListAPI
(
empId
,
session
,
pageNumber
,
)
async
{
try
{
Map
<
String
,
String
>
data
=
{
'session_id'
:
(
session
).
toString
(),
'emp_id'
:
(
empId
).
toString
(),
'page_number'
:
(
pageNumber
),
};
final
res
=
await
post
(
data
,
TourExpensesListUrl
,
{});
if
(
res
!=
null
)
{
print
(
data
);
debugPrint
(
res
.
body
);
return
tourExpensesListResponse
.
fromJson
(
jsonDecode
(
res
.
body
));
}
else
{
debugPrint
(
"Null Response"
);
return
null
;
}
}
catch
(
e
)
{
debugPrint
(
'hello bev=bug
$e
'
);
return
null
;
}
}
static
Future
<
tourExpensesDetailsResponse
?>
tourExpensesDetailAPI
(
session
,
empId
,
tourBillId
,
)
async
{
try
{
Map
<
String
,
String
>
data
=
{
'session_id'
:
(
session
).
toString
(),
'emp_id'
:
(
empId
).
toString
(),
'tour_bill_id'
:
(
tourBillId
),
};
final
res
=
await
post
(
data
,
TourExpensesDetailsUrl
,
{});
if
(
res
!=
null
)
{
print
(
data
);
debugPrint
(
res
.
body
);
return
tourExpensesDetailsResponse
.
fromJson
(
jsonDecode
(
res
.
body
));
}
else
{
debugPrint
(
"Null Response"
);
return
null
;
}
}
catch
(
e
)
{
debugPrint
(
'hello bev=bug
$e
'
);
return
null
;
}
}
// static Future<CommonResponse?> TpcIssueListApprovalAPI(
// static Future<CommonResponse?> TpcIssueListApprovalAPI(
// empId,
// empId,
// session,
// session,
...
...
lib/services/api_names.dart
View file @
6d1deaf2
...
@@ -178,6 +178,18 @@ const crmDashboardFollowUpUrl = "${baseUrl_test}crm_dashboard_followup_list";
...
@@ -178,6 +178,18 @@ const crmDashboardFollowUpUrl = "${baseUrl_test}crm_dashboard_followup_list";
const
crmDashboardQuotationsUrl
=
"
${baseUrl_test}
crm_dashboard_quotations_list"
;
const
crmDashboardQuotationsUrl
=
"
${baseUrl_test}
crm_dashboard_quotations_list"
;
///HRM
//Attendance
const
AttendanceRequestListUrl
=
"
${baseUrl_test}
attendance_request_list"
;
const
AttendanceRequestDetailsUrl
=
"
${baseUrl_test}
attendance_request_details"
;
const
AddAttendanceRequestUrl
=
"
${baseUrl_test}
add_attendance_request"
;
// reward list
const
RewardListUrl
=
"
${baseUrl_test}
hrm_emp_self_rewards"
;
// Tour Expenses hrm_emp_self_rewards
const
TourExpensesListUrl
=
"
${baseUrl_test}
tour_bill_list"
;
const
TourExpensesDetailsUrl
=
"
${baseUrl_test}
tour_bill_details"
;
...
...
pubspec.yaml
View file @
6d1deaf2
...
@@ -126,6 +126,7 @@ flutter:
...
@@ -126,6 +126,7 @@ flutter:
-
assets/svg/service/
-
assets/svg/service/
-
assets/svg/crm/
-
assets/svg/crm/
-
assets/svg/order/
-
assets/svg/order/
-
assets/svg/hrm/
# An image asset can refer to one or more resolution-specific "variants", see
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# https://flutter.dev/to/resolution-aware-images
...
...
Prev
1
2
3
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment