import ' dart:convert ' ;
import ' package:http/http.dart ' as http ;
import ' package:flutter/material.dart ' ;
import ' package:bookatanker/common/settings.dart ' ;
import ' package:table_calendar/table_calendar.dart ' ;
import ' package:intl/intl.dart ' ;
import ' package:bookatanker/models/supplier_tankers_model.dart ' ;
import ' package:bookatanker/supplier/cart_summary.dart ' ;
class SupplierScreen extends StatefulWidget {
var details ;
SupplierScreen ( { this . details } ) ;
@ override
State < SupplierScreen > createState ( ) = > _SupplierScreenState ( ) ;
}
class _SupplierScreenState extends State < SupplierScreen > {
DateTime _focusedDay = DateTime . now ( ) ;
DateTime ? _selectedDay ;
bool isTankerDataLoading = false ;
bool isCartDataLoading = false ;
List < SupplierTankersModel > tankersList = [ ] ;
bool isSereverIssue = false ;
List cart = [ ] ;
Map < String , int > localQuantities = { } ;
Map < String , int > localTotalPrices = { } ;
Future < void > getTankers ( ) async {
isTankerDataLoading = true ;
try {
var tankerResponse = await AppSettings . getAllTankers ( widget . details . supplier_id ) ;
setState ( ( ) {
tankersList =
( ( jsonDecode ( tankerResponse ) [ ' data ' ] ) as List ) . map ( ( dynamic model ) {
return SupplierTankersModel . fromJson ( model ) ;
} ) . toList ( ) ;
isTankerDataLoading = false ;
} ) ;
} catch ( e ) {
setState ( ( ) {
isTankerDataLoading = false ;
isSereverIssue = true ;
} ) ;
/ * AppSettings . longFailedToast ( ' There is an issue at server side please try after some time ' ) ;
Navigator . pop ( context ) ; * /
}
}
Future < void > getCart ( ) async {
isCartDataLoading = true ;
try {
var tankerResponse = await AppSettings . getCartItems ( ) ;
setState ( ( ) {
cart = ( ( jsonDecode ( tankerResponse ) [ ' data ' ] [ ' items ' ] ) ) ;
isCartDataLoading = false ;
} ) ;
} catch ( e ) {
setState ( ( ) {
isCartDataLoading = false ;
isSereverIssue = true ;
} ) ;
/ * AppSettings . longFailedToast ( ' There is an issue at server side please try after some time ' ) ;
Navigator . pop ( context ) ; * /
}
}
@ override
void initState ( ) {
super . initState ( ) ;
_selectedDay = _focusedDay ;
getTankers ( ) ;
getCart ( ) ;
}
@ override
Widget build ( BuildContext context ) {
return Scaffold (
backgroundColor: Color ( 0XFFFFFFFF ) ,
appBar: AppSettings . SupplierAppBar ( ' Place Order ' , context ) ,
body: Padding (
padding: EdgeInsets . fromLTRB ( 12 , 8 , 12 , 8 ) ,
child: ListView (
children: [
// Supplier Card
Card (
shape: RoundedRectangleBorder (
borderRadius: BorderRadius . circular ( 12 ) ) ,
elevation: 2 ,
color: Colors . white ,
child: Column (
children: [
Padding (
padding: EdgeInsets . fromLTRB ( 8 , 8 , 8 , 8 ) ,
child: Column (
mainAxisAlignment: MainAxisAlignment . start ,
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
Visibility (
visible: true ,
child: Row (
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
Visibility (
visible: widget . details . supplier_name ! = ' ' ,
child: Text (
widget . details . supplier_name . toString ( ) ,
style: fontTextStyle (
16 , Color ( 0XFF2D2E30 ) , FontWeight . w600 ) ,
) ,
) ,
Row (
mainAxisAlignment: MainAxisAlignment . center ,
crossAxisAlignment: CrossAxisAlignment . center ,
children: [
Image . asset (
' images/star.png ' ,
fit: BoxFit . cover ,
width:
16 , // Match the diameter of the CircleAvatar
height: 16 ,
) ,
Visibility (
visible: true ,
child: Text (
' 4.2 (20K+ Ratings) ' ,
style: fontTextStyle ( 9 ,
Color ( 0XFF515253 ) , FontWeight . w400 ) ,
) ,
) ,
] ,
)
] ,
) ,
) ,
Visibility (
visible: true ,
child: Text (
' Drinking | Bore Water ' ,
style: fontTextStyle (
12 , Color ( 0XFF4692FD ) , FontWeight . w500 ) ,
) ,
) ,
Visibility (
visible: true ,
child: Row (
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
Text (
widget . details . displayAddress +
' ' +
widget . details . distanceInMeters
. toString ( ) +
' Km ' ,
style: fontTextStyle ( 12 , Color ( 0XFF515253 ) ,
FontWeight . w400 ) ) ,
Image . asset (
' images/heart_outline.png ' ,
fit: BoxFit . cover ,
width:
16 , // Match the diameter of the CircleAvatar
height: 16 ,
) ,
] ,
) ,
) ,
] ,
) ,
) ,
Padding (
padding: EdgeInsets . zero ,
child: Container (
width: double
. infinity , // makes it expand within the Card's width
decoration: BoxDecoration (
borderRadius: BorderRadius . vertical (
bottom: Radius . circular ( 12 ) ,
) , // match Card border
gradient: LinearGradient (
colors: [
Color ( 0xFFFFE8A3 ) ,
Color ( 0xFFFFF8DF ) ,
Color ( 0xFFFFFFFF ) ,
] ,
begin: Alignment . topLeft ,
end: Alignment . bottomRight ,
) ,
) ,
padding: EdgeInsets . symmetric ( vertical: 12 ) ,
alignment: Alignment . center ,
child: Padding (
padding: EdgeInsets . fromLTRB ( 12 , 0 , 12 , 0 ) ,
child: Row (
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
Row (
children: [
Image . asset (
' images/ring.png ' ,
fit: BoxFit . cover ,
width:
16 , // Match the diameter of the CircleAvatar
height: 16 ,
) ,
Text ( ' Best water quality ' ,
style: fontTextStyle (
10 ,
Color ( 0XFF2D2E30 ) ,
FontWeight . w400 ) ) ,
] ,
) ,
Row (
children: [
Image . asset (
' images/ring.png ' ,
fit: BoxFit . cover ,
width:
16 , // Match the diameter of the CircleAvatar
height: 16 ,
) ,
Text ( ' Steel casing tankers ' ,
style: fontTextStyle (
10 ,
Color ( 0XFF2D2E30 ) ,
FontWeight . w400 ) ) ,
] ,
)
] ,
) ,
) ) ,
) ,
] ,
) ) ,
Card (
color: Colors . white ,
shape: RoundedRectangleBorder (
borderRadius: BorderRadius . circular ( 12 ) ) ,
child: Padding (
padding: const EdgeInsets . fromLTRB ( 12 , 12 , 12 , 0 ) ,
child: Column (
children: [
// Header Row
Row (
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
children: [
Text (
DateFormat . yMMM ( ) . format ( _focusedDay ) ,
style: fontTextStyle ( 12 , Color ( 0XFF646566 ) , FontWeight . w600 ) ,
) ,
Image . asset (
' images/calender.png ' ,
fit: BoxFit . cover ,
width: 16 , // Match the diameter of the CircleAvatar
height: 16 ,
) ,
] ,
) ,
Divider (
color: Colors . grey . shade300 ,
) ,
// Weekly Calendar
TableCalendar (
focusedDay: _focusedDay ,
firstDay: DateTime . now ( ) ,
lastDay: DateTime . now ( ) . add ( Duration ( days: 15 ) ) ,
calendarFormat: CalendarFormat . week ,
startingDayOfWeek: StartingDayOfWeek . sunday ,
availableCalendarFormats: const {
CalendarFormat . week: ' Week ' ,
} ,
selectedDayPredicate: ( day ) {
return isSameDay ( _selectedDay , day ) ;
} ,
onDaySelected: ( selectedDay , focusedDay ) {
setState ( ( ) {
_selectedDay = selectedDay ;
_focusedDay = focusedDay ;
} ) ;
} ,
headerVisible: false ,
calendarStyle: CalendarStyle (
todayDecoration: BoxDecoration ( ) , // No box for today
todayTextStyle: fontTextStyle ( 12 , Color ( 0XFF1D7AFC ) , FontWeight . w400 ) ,
selectedDecoration: BoxDecoration (
color: Color ( 0XFF1D7AFC ) ,
shape: BoxShape . circle ,
) ,
selectedTextStyle: fontTextStyle ( 12 , Color ( 0XFFFFFFFF ) , FontWeight . w400 ) ,
weekendTextStyle: TextStyle ( color: Colors . black ) ,
defaultTextStyle: TextStyle ( color: Colors . black ) ,
outsideDaysVisible: false ,
) ,
daysOfWeekStyle: DaysOfWeekStyle (
weekdayStyle: fontTextStyle ( 12 , Color ( 0XFF343637 ) , FontWeight . w400 ) ,
weekendStyle: fontTextStyle ( 12 , Color ( 0XFF343637 ) , FontWeight . w400 ) ,
) ,
) ,
] ,
) ,
) ) ,
SizedBox ( height: MediaQuery . of ( context ) . size . height * . 016 ) ,
Padding ( padding: EdgeInsets . fromLTRB ( 6 , 0 , 6 , 0 ) ,
child: Container (
width: double . infinity ,
padding:
EdgeInsets . symmetric ( horizontal: 8 , vertical: 4 ) ,
decoration: BoxDecoration (
color: Color ( 0XFFFCF0E7 ) ,
borderRadius: BorderRadius . circular ( 6 ) ,
border: Border . all
(
color: Color ( 0XFFEFA168 ) ) ,
) ,
child: Row (
crossAxisAlignment: CrossAxisAlignment . start ,
mainAxisAlignment: MainAxisAlignment . start ,
children: [
Image . asset (
' images/warning.png ' ,
fit: BoxFit . cover ,
width:
22 , // Match the diameter of the CircleAvatar
height: 22 ,
) ,
SizedBox ( width: MediaQuery . of ( context ) . size . width * . 020 ) ,
Expanded ( child: Text (
' The prices shown below are NOT inclusive of the transport charges. Transport charges are calculated based on the distance between the sourcing location and the delivery location. ' ,
style: fontTextStyle (
10 ,
Color ( 0XFF444444 ) ,
FontWeight . w400 ) ,
) , )
] ,
)
) , ) ,
SizedBox ( height: MediaQuery . of ( context ) . size . height * . 016 ) ,
// Supply Locations
Text ( ' SUPPLY LOCATIONS ' ,
style: fontTextStyle (
12 , Color ( 0XFF646566 ) , FontWeight . w400 ) , ) ,
SizedBox ( height: MediaQuery . of ( context ) . size . height * . 016 ) ,
Card (
elevation: 2 ,
shape: RoundedRectangleBorder ( borderRadius: BorderRadius . circular ( 8 ) ) ,
color: Color ( 0XFFFFFFFF ) ,
child: Padding (
padding: const EdgeInsets . all ( 12.0 ) ,
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
. . . [
{ ' name ' : ' Gandipet ' , ' type ' : ' Drinking water ' , ' km ' : ' 4.5 Km ' } ,
{ ' name ' : ' Nizampet ' , ' type ' : ' Drinking water ' , ' km ' : ' 7.3 Km ' } ,
{ ' name ' : ' Secunderabad ' , ' type ' : ' Bore water ' , ' km ' : ' 12.4 Km ' } ,
] . map ( ( location ) = > Padding (
padding: const EdgeInsets . only ( bottom: 12.0 ) ,
child: ListBody (
children: [
Row (
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
children: [
Text ( location [ ' name ' ] ! , style: fontTextStyle ( 12 , Color ( 0XFF2D2E30 ) , FontWeight . w500 ) , ) ,
Text ( location [ ' km ' ] ! , style: fontTextStyle ( 10 , Color ( 0XFF2D2E30 ) , FontWeight . w500 ) , ) ,
] ,
) ,
Text ( location [ ' type ' ] ! , style: fontTextStyle ( 10 , Color ( 0XFF515253 ) , FontWeight . w400 ) , ) ,
] ,
) ,
) ) ,
] ,
) ,
) ,
) ,
// Tankers
SizedBox ( height: MediaQuery . of ( context ) . size . height * . 016 ) ,
Text ( ' TANKERS ' ,
style: fontTextStyle (
12 , Color ( 0XFF646566 ) , FontWeight . w400 ) , ) ,
SizedBox ( height: MediaQuery . of ( context ) . size . height * . 016 ) ,
GridView . builder (
padding: const EdgeInsets . all ( 0 ) ,
itemCount: tankersList . length ,
shrinkWrap: true ,
physics: NeverScrollableScrollPhysics ( ) ,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount (
crossAxisCount: 2 ,
mainAxisSpacing: 12 ,
crossAxisSpacing: 12 ,
childAspectRatio: 1.6 ,
) ,
itemBuilder: ( context , index ) {
final tanker = tankersList [ index ] ;
final isAvailable = tankersList [ index ] . status = = ' Available ' ;
final isEnabled = tankersList [ index ] . enabled ;
return Card (
shape: RoundedRectangleBorder ( borderRadius: BorderRadius . circular ( 8 ) ) ,
elevation: 2 ,
color: Color ( 0XFFFFFFFF ) ,
child: Padding (
padding: const EdgeInsets . all ( 12 ) ,
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
Row (
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
children: [
Text (
tankersList [ index ] . capacity + ' Litres ' ,
style: fontTextStyle ( 12 , Color ( 0XFF2D2E30 ) , FontWeight . w500 ) ,
) ,
Text (
' \u20B9 ' + AppSettings . formDouble ( tankersList [ index ] . price ) ,
style: fontTextStyle ( 12 , Color ( 0XFF515253 ) , FontWeight . w400 ) ,
) ,
] ,
) ,
Text (
tankersList [ index ] . type_of_water ,
style: fontTextStyle ( 12 , Color ( 0XFF515253 ) , FontWeight . w400 ) ,
) ,
Expanded ( child: Row (
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
children: [
Container (
padding:
EdgeInsets . symmetric ( horizontal: 8 , vertical: 4 ) ,
decoration: BoxDecoration (
borderRadius: BorderRadius . circular ( 6 ) ,
border: Border . all (
color: isAvailable ? Color ( 0XFF098603 ) : Colors . grey , ) ,
) ,
child: Text (
tankersList [ index ] . status ,
style: fontTextStyle (
12 ,
isAvailable ? Color ( 0XFF098603 ) : Colors . grey ,
FontWeight . w500 ) ,
) ,
) ,
ElevatedButton (
onPressed: isEnabled
? ( ) async {
AppSettings . preLoaderDialog ( context ) ;
final product = tankersList [ index ] ;
final productId = product . id ;
final name = product . tanker_name ;
// Convert price safely from string like "1,000"
final priceString = product . price ;
final unitPrice = int . parse ( priceString . replaceAll ( ' , ' , ' ' ) ) ;
// Get current quantity, or 0 if not added yet
int currentQuantity = localQuantities [ productId ] ? ? 0 ;
int newQuantity = currentQuantity + 1 ;
// Calculate total price for UI use (unitPrice * quantity)
int totalPrice = unitPrice * newQuantity ;
// Prepare payload to send unit price and quantity only
final payload = {
" productId " : productId ,
" name " : name ,
" quantity " : newQuantity ,
" price " : unitPrice , // unit price ONLY
} ;
try {
final response = await http . post (
Uri . parse ( ' ${ AppSettings . host } cart/ ${ AppSettings . customerId } /add ' ) ,
headers: await AppSettings . buildRequestHeaders ( ) ,
body: jsonEncode ( payload ) ,
) ;
if ( response . statusCode = = 200 | | response . statusCode = = 201 ) {
setState ( ( ) {
Navigator . of ( context , rootNavigator: true ) . pop ( ) ;
// Update local tracking maps
localQuantities [ productId ] = newQuantity ;
localTotalPrices [ productId ] = totalPrice ;
// Update or add the cart list entry
int existingIndex = cart . indexWhere ( ( item ) = > item [ ' productId ' ] = = productId ) ;
if ( existingIndex ! = - 1 ) {
cart [ existingIndex ] [ ' quantity ' ] = newQuantity ;
cart [ existingIndex ] [ ' price ' ] = unitPrice ; // unit price
cart [ existingIndex ] [ ' total ' ] = totalPrice ; // total price for UI
} else {
cart . add ( {
" productId " : productId ,
" name " : name ,
" quantity " : newQuantity ,
" price " : unitPrice ,
" total " : totalPrice ,
} ) ;
}
} ) ;
// Optional: refresh cart from server if you want
await getCart ( ) ;
} else {
Navigator . of ( context , rootNavigator: true ) . pop ( ) ;
print ( ' Failed to add to cart: ${ response . body } ' ) ;
}
} catch ( e ) {
Navigator . of ( context , rootNavigator: true ) . pop ( ) ;
print ( ' Error adding to cart: $ e ' ) ;
}
}
: null ,
style: ElevatedButton . styleFrom (
backgroundColor: isEnabled ? primaryColor : Colors . grey [ 300 ] ,
foregroundColor: Colors . white ,
shape: RoundedRectangleBorder (
borderRadius: BorderRadius . circular ( 8 ) ,
) ,
minimumSize: Size ( 48 , 32 ) ,
padding: EdgeInsets . zero ,
) ,
child: Text (
' Add ' ,
style: fontTextStyle ( 12 , Color ( 0XFFFFFFFF ) , FontWeight . w400 ) ,
) ,
) ,
] ,
) )
] ,
) ,
) ,
) ;
} ,
) ,
] ,
) ,
) ,
bottomNavigationBar: cart . isNotEmpty
? Padding (
padding: EdgeInsets . all ( 0 ) ,
child: Container (
height: 56 ,
width: double . infinity ,
decoration: BoxDecoration (
color: Color ( 0XFFF5CD47 ) ,
borderRadius: BorderRadius . only (
topLeft: Radius . circular ( 12 ) ,
topRight: Radius . circular ( 12 ) ,
) ,
) ,
padding: EdgeInsets . symmetric ( horizontal: 24 ) ,
child: Row (
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
children: [
Text (
" Order updated " ,
style: fontTextStyle ( 12 , Color ( 0XFF232527 ) , FontWeight . w500 ) ,
) ,
GestureDetector (
onTap: ( ) {
Navigator . push (
context ,
MaterialPageRoute (
builder: ( context ) = > CartSummary ( details: cart , supplierDetails: widget . details , ) ) ,
) ;
} ,
child: Row (
children: [
Text (
" View summary " ,
style: fontTextStyle ( 12 , Color ( 0XFF232527 ) , FontWeight . w500 ) ,
) ,
Image . asset (
' images/arrow-right.png ' ,
fit: BoxFit . cover ,
width:
20 , // Match the diameter of the CircleAvatar
height: 20 ,
color: Color ( 0XFF343637 ) ,
) ,
] ,
) ,
) ,
] ,
) ,
) ,
)
: null ,
) ;
}
}