diff --git a/backend/src/__tests__/item.test.ts b/backend/src/__tests__/item.test.ts index 3d992f9..2f3de50 100644 --- a/backend/src/__tests__/item.test.ts +++ b/backend/src/__tests__/item.test.ts @@ -27,6 +27,10 @@ export const userPayload = { contactNumber: "0712345678", }; + +/** + * Main test suite + */ describe("site", () => { beforeAll(async () => { const mongoServer = await MongoMemoryServer.create(); @@ -37,8 +41,13 @@ describe("site", () => { await mongoose.disconnect(); await mongoose.connection.close(); }); + + /** + * Sub test suite 1 - POST operation + */ describe("create item route", () => { describe("given the user is not logged in", () => { + /* failure scenario */ it("should return a 403", async () => { const { statusCode } = await supertest(app).post("/api/items"); @@ -46,6 +55,7 @@ describe("site", () => { }); }); describe("given the user is logged in", () => { + /* success scenario */ it("should return a 200 and create the item", async () => { const jwt = signJwt(userPayload); @@ -60,14 +70,20 @@ describe("site", () => { }); }); }); + + /** + * Sub test suite 2 - GET operation (all) + */ describe("get item list route", () => { describe("given the user is not logged in", () => { + /* failure scenario */ it("should return a 403", async () => { const { statusCode } = await supertest(app).get("/api/items"); expect(statusCode).toBe(403); }); }); describe("given the user is logged in", () => { + /* success scenario */ it("should return a 200 and get the item list", async () => { const jwt = signJwt(userPayload); @@ -81,8 +97,13 @@ describe("site", () => { }); }); }); + + /** + * Sub test suite 3 - update site + */ describe("update item route", () => { describe("given the user is not logged in", () => { + /* failure scenario */ it("should return a 403", async () => { const oldItem = await createItem(itemPayload); const { statusCode } = await supertest(app).put( @@ -93,6 +114,7 @@ describe("site", () => { }); }); describe("given the user is logged in", () => { + /* success scenario */ it("should return a 200 and update the item", async () => { const jwt = signJwt(userPayload); diff --git a/backend/src/__tests__/site.test.ts b/backend/src/__tests__/site.test.ts index 7c3e153..4607609 100644 --- a/backend/src/__tests__/site.test.ts +++ b/backend/src/__tests__/site.test.ts @@ -27,6 +27,10 @@ export const userPayload = { contactNumber: "0712345678", }; + +/** + * Main test suite + */ describe("site", () => { beforeAll(async () => { const mongoServer = await MongoMemoryServer.create(); @@ -37,8 +41,14 @@ describe("site", () => { await mongoose.disconnect(); await mongoose.connection.close(); }); + + + /** + * Sub test suite 1 - POST operation + */ describe("create site route", () => { describe("given the user is not logged in", () => { + /* failure scenario */ it("should return a 403", async () => { const { statusCode } = await supertest(app).post("/api/sites"); @@ -47,6 +57,7 @@ describe("site", () => { }); describe("given the user is logged in", () => { + /* success scenario */ it("should return a 200 and create the site", async () => { const jwt = signJwt(userPayload); @@ -62,8 +73,12 @@ describe("site", () => { }); }); + /** + * Sub test suite 2 - GET operation (all) + */ describe("get sites list route", () => { describe("given the user is not logged in", () => { + /* failure scenario */ it("should return a 403", async () => { const { statusCode } = await supertest(app).get("/api/sites"); @@ -72,6 +87,7 @@ describe("site", () => { }); describe("given the user is logged in", () => { + /* success scenario */ it("should return a 200 and the sites", async () => { const jwt = signJwt(userPayload); @@ -92,8 +108,12 @@ describe("site", () => { }); }); + /** + * Sub test suite 3 - GET operation (particular site) + */ describe("get site by id route", () => { describe("given the site does not exist", () => { + /* failure scenario */ it("should return a 404", async () => { const siteId = "site-123"; const jwt = signJwt(userPayload); @@ -106,6 +126,7 @@ describe("site", () => { }); describe("given the site does exist", () => { + /* success scenario */ it("should return a 200 status and the site", async () => { const site = await createSite(sitePayload); const jwt = signJwt(userPayload); @@ -121,8 +142,12 @@ describe("site", () => { }); }); + /** + * Sub test suite 4 - update site + */ describe("update site route", () => { describe("given the user is not logged in", () => { + /* failure scenario */ it("should return a 403", async () => { const site = await createSite(sitePayload); const { statusCode } = await supertest(app).put( @@ -133,6 +158,7 @@ describe("site", () => { }); describe("given the user is logged in", () => { + /* success scenario */ it("should return a 200 and update the site", async () => { const site = await createSite(sitePayload); const jwt = signJwt(userPayload); diff --git a/backend/src/__tests__/user-management.test.ts b/backend/src/__tests__/user-management.test.ts index 1837900..afa4b4d 100644 --- a/backend/src/__tests__/user-management.test.ts +++ b/backend/src/__tests__/user-management.test.ts @@ -28,6 +28,9 @@ const userInput: CreateUserInput["body"] = { contactNumber: "0712345678", }; +/** + * Main test suite + */ describe("user-management", () => { beforeAll(async () => { const mongoServer = await MongoMemoryServer.create(); @@ -38,8 +41,12 @@ describe("user-management", () => { await mongoose.connection.close(); }); + /** + * Sub test suite 1 - POST operation + */ describe("create user route", () => { describe("given the user is not logged in", () => { + /* failure scenario */ it("should return a 403", async () => { const { statusCode } = await supertest(app).post( "/api/user-management" @@ -50,6 +57,7 @@ describe("user-management", () => { }); describe("given the user is logged in", () => { + /* success scenario */ it("should return a 201 and create the user", async () => { const jwt = signJwt(userPayload); @@ -65,8 +73,12 @@ describe("user-management", () => { }); }); + /** + * Sub test suite 2 - GET operation (all) + */ describe("get user list route", () => { describe("given the user is not logged in", () => { + /* failure scenario */ it("should return a 403", async () => { const { statusCode } = await supertest(app).get("/api/user-management"); @@ -75,6 +87,7 @@ describe("user-management", () => { }); describe("given the user is logged in", () => { + /* success scenario */ it("should return a 200 and the users", async () => { const jwt = signJwt(userPayload); @@ -89,8 +102,12 @@ describe("user-management", () => { }); }); + /** + * Sub test suite 4 - update user + */ describe("update user route", () => { describe("given the user is not logged in", () => { + /* failure scenario */ it("should return a 403", async () => { const { statusCode } = await supertest(app).put( `/api/user-management/${userPayload.userId}` @@ -101,6 +118,7 @@ describe("user-management", () => { }); describe("given the user is logged in", () => { + /* success scenario */ it("should return a 200 and update the user", async () => { const jwt = signJwt(userPayload); diff --git a/backend/src/service/deliver.service.ts b/backend/src/service/deliver.service.ts index 273d70d..0b033c3 100644 --- a/backend/src/service/deliver.service.ts +++ b/backend/src/service/deliver.service.ts @@ -19,6 +19,7 @@ export async function getDeliveryList( .populate("order") .populate("supplier") .populate("site") + .sort({ createdAt: -1 }) .exec(); } diff --git a/flutter_client/android/app/src/main/AndroidManifest.xml b/flutter_client/android/app/src/main/AndroidManifest.xml index bfb0ccc..9a93b93 100644 --- a/flutter_client/android/app/src/main/AndroidManifest.xml +++ b/flutter_client/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ { AuthBloc() : super(AuthInitial()) { + on(_getSiteManagerNameEventHandeler); on(_loginEventHandeler); on(_logoutEventHandeler); } } -void _loginEventHandeler(LoginEvent event, Emitter emit) async { +void _getSiteManagerNameEventHandeler( + GetSiteManagerName event, Emitter emit) async { + emit(SigningIn()); + AuthRepository authRepository = AuthRepository(); + emit(SiteManagerNameLoading()); + await authRepository.siteManagerName.then((siteManagerName) { + emit(SiteManagerName(siteManagerName)); + }).catchError((onError) { + emit(SiteManagerNameLoadFailed()); + }); +} - print("wwwwwwwwwwwwwwwwwwwwwwwwwwwwewewewewewewewe"); +void _loginEventHandeler(LoginEvent event, Emitter emit) async { emit(SigningIn()); AuthRepository authRepository = AuthRepository(); @@ -34,7 +45,6 @@ void _logoutEventHandeler(SignOut event, Emitter emit) async { AuthRepository authRepository = AuthRepository(); final isLoggedOut = await authRepository.logout(); if (isLoggedOut) { - // authRepository.isTokenAvailable(); emit(SignedOut()); emit(AuthInitial()); } else { diff --git a/flutter_client/lib/blocs/auth/auth_event.dart b/flutter_client/lib/blocs/auth/auth_event.dart index d9147cb..e5fd1d4 100644 --- a/flutter_client/lib/blocs/auth/auth_event.dart +++ b/flutter_client/lib/blocs/auth/auth_event.dart @@ -5,6 +5,10 @@ abstract class AuthEvent { const AuthEvent(); } +class GetSiteManagerName extends AuthEvent { + const GetSiteManagerName(); +} + class LoginEvent extends AuthEvent { final String username; final String password; diff --git a/flutter_client/lib/blocs/auth/auth_state.dart b/flutter_client/lib/blocs/auth/auth_state.dart index 01fc826..9386f3c 100644 --- a/flutter_client/lib/blocs/auth/auth_state.dart +++ b/flutter_client/lib/blocs/auth/auth_state.dart @@ -16,3 +16,13 @@ final class SigningOut extends AuthState {} final class SignedOut extends AuthState {} final class SignOutFailed extends AuthState {} + +final class SiteManagerName extends AuthState { + final String siteManagerName; + + SiteManagerName(this.siteManagerName); +} + +final class SiteManagerNameLoading extends AuthState {} + +final class SiteManagerNameLoadFailed extends AuthState {} diff --git a/flutter_client/lib/blocs/cart/cart_bloc.dart b/flutter_client/lib/blocs/cart/cart_bloc.dart index fd17a79..008ae68 100644 --- a/flutter_client/lib/blocs/cart/cart_bloc.dart +++ b/flutter_client/lib/blocs/cart/cart_bloc.dart @@ -104,6 +104,7 @@ class CartBloc extends Bloc { void _clearCartHandler(ClearCartEvent event, Emitter emit) { cart.clear(); + _temporaryProducts.clear(); emit(ProductCartUpdated(orderProducts: cart, cartTotal: 0)); } diff --git a/flutter_client/lib/blocs/goodReceipts/goods_receipt_bloc.dart b/flutter_client/lib/blocs/goodReceipts/goods_receipt_bloc.dart index 34c2046..7a23bc1 100644 --- a/flutter_client/lib/blocs/goodReceipts/goods_receipt_bloc.dart +++ b/flutter_client/lib/blocs/goodReceipts/goods_receipt_bloc.dart @@ -12,6 +12,7 @@ class GoodsReceiptBloc extends Bloc { GoodsReceiptBloc() : super(GoodsReceiptInitial()) { on(_getGoodsReceiptsHandler); + on(_markAsReceivedHandler); } // Load products @@ -34,4 +35,30 @@ class GoodsReceiptBloc extends Bloc { }, ); } + + // Mark as received + void _markAsReceivedHandler( + MarkAsReceivedEvent event, Emitter emit) async { + emit( + GoodsReceiptMarkingAsReceived( + goodsReceiptNumber: event.goodsReceiptNumber, + ), + ); + + await _goodsReceiptRepository + .markedAsReceived(event.goodsReceiptNumber) + .then( + (goodsReceipt) { + emit(GoodsReceiptMarkedAsReceived( + goodsReceiptNumber: event.goodsReceiptNumber)); + }, + ).catchError( + (error) { + emit( + const GoodsReceiptMarkedAsReceivedError( + message: "Error marking goods receipt as received."), + ); + }, + ); + } } diff --git a/flutter_client/lib/blocs/goodReceipts/goods_receipt_event.dart b/flutter_client/lib/blocs/goodReceipts/goods_receipt_event.dart index ff3626c..10c4c6f 100644 --- a/flutter_client/lib/blocs/goodReceipts/goods_receipt_event.dart +++ b/flutter_client/lib/blocs/goodReceipts/goods_receipt_event.dart @@ -8,3 +8,8 @@ abstract class GoodsReceiptEvent { class GetGoodsReceiptsEvent extends GoodsReceiptEvent { const GetGoodsReceiptsEvent(); } + +class MarkAsReceivedEvent extends GoodsReceiptEvent { + final String goodsReceiptNumber; + const MarkAsReceivedEvent(this.goodsReceiptNumber); +} diff --git a/flutter_client/lib/blocs/goodReceipts/goods_receipt_state.dart b/flutter_client/lib/blocs/goodReceipts/goods_receipt_state.dart index 7f554d4..07946ea 100644 --- a/flutter_client/lib/blocs/goodReceipts/goods_receipt_state.dart +++ b/flutter_client/lib/blocs/goodReceipts/goods_receipt_state.dart @@ -24,3 +24,27 @@ final class GoodsReceiptsLoaded extends GoodsReceiptState { required this.goodsReceipts, }); } + +final class GoodsReceiptMarkingAsReceived extends GoodsReceiptState { + final String goodsReceiptNumber; + + const GoodsReceiptMarkingAsReceived({ + required this.goodsReceiptNumber, + }); +} + +final class GoodsReceiptMarkedAsReceived extends GoodsReceiptState { + final String goodsReceiptNumber; + + const GoodsReceiptMarkedAsReceived({ + required this.goodsReceiptNumber, + }); +} + +final class GoodsReceiptMarkedAsReceivedError extends GoodsReceiptState { + final String message; + + const GoodsReceiptMarkedAsReceivedError({ + required this.message, + }); +} diff --git a/flutter_client/lib/blocs/order/order_bloc.dart b/flutter_client/lib/blocs/order/order_bloc.dart index fabc984..c0014a0 100644 --- a/flutter_client/lib/blocs/order/order_bloc.dart +++ b/flutter_client/lib/blocs/order/order_bloc.dart @@ -13,6 +13,20 @@ class OrderBloc extends Bloc { final AuthRepository _authRepository = AuthRepository(); OrderBloc() : super(const OrderInitial()) { on(_onCreateOrderHandler); + on(_onGetOrdersHandler); + } + + void _onGetOrdersHandler( + GetOrdersEvent event, + Emitter emit, + ) async { + emit(GettingOrders()); + try { + List orders = await _orderRepository.getOrders(); + emit(OrdersRetrieved(orders: orders)); + } catch (e) { + emit(ErrorRetrievingOrders(message: e.toString())); + } } void _onCreateOrderHandler( diff --git a/flutter_client/lib/blocs/order/order_event.dart b/flutter_client/lib/blocs/order/order_event.dart index 31e6faf..db12ba5 100644 --- a/flutter_client/lib/blocs/order/order_event.dart +++ b/flutter_client/lib/blocs/order/order_event.dart @@ -10,3 +10,7 @@ class CreateOrderEvent extends OrderEvent { const CreateOrderEvent({required this.order}); } + +class GetOrdersEvent extends OrderEvent { + const GetOrdersEvent(); +} diff --git a/flutter_client/lib/blocs/order/order_state.dart b/flutter_client/lib/blocs/order/order_state.dart index 3e77369..f3e77c7 100644 --- a/flutter_client/lib/blocs/order/order_state.dart +++ b/flutter_client/lib/blocs/order/order_state.dart @@ -19,3 +19,17 @@ class OrderNotCreated extends OrderInitial { const OrderNotCreated({required this.message}); } + +class GettingOrders extends OrderInitial {} + +class OrdersRetrieved extends OrderInitial { + final List orders; + + const OrdersRetrieved({required this.orders}); +} + +class ErrorRetrievingOrders extends OrderInitial { + final String message; + + const ErrorRetrievingOrders({required this.message}); +} diff --git a/flutter_client/lib/blocs/site/site_bloc.dart b/flutter_client/lib/blocs/site/site_bloc.dart index 576fbf3..5234c03 100644 --- a/flutter_client/lib/blocs/site/site_bloc.dart +++ b/flutter_client/lib/blocs/site/site_bloc.dart @@ -1,5 +1,3 @@ -import 'dart:developer' as developer; - import 'package:bloc/bloc.dart'; import 'package:flutter_client/models/site.dart'; import 'package:flutter_client/repositiories/sites/sites_repository.dart'; @@ -18,10 +16,8 @@ class SiteBloc extends Bloc { void _onGetSiteEventHandler( GetSitesEvent event, Emitter emit) async { emit(SiteLoading()); - developer.log("Sites", name: 'site_bloc'); await _sitesRepository.getSites().then((sites) { - developer.log("Sites: ${sites.length}", name: 'site_bloc'); emit(SiteLoaded(sites)); }).catchError((error) { emit(SiteError(error.toString())); diff --git a/flutter_client/lib/models/goods_receipt.dart b/flutter_client/lib/models/goods_receipt.dart index ae75811..4144bfe 100644 --- a/flutter_client/lib/models/goods_receipt.dart +++ b/flutter_client/lib/models/goods_receipt.dart @@ -25,6 +25,29 @@ class Supplier { } } +class Site { + final String id; + final String name; + final String address; + final String contactNumber; + + Site({ + required this.id, + required this.name, + required this.address, + required this.contactNumber, + }); + + factory Site.fromJson(Map json) { + return Site( + id: json['_id'], + name: json['name'], + address: json['address'], + contactNumber: json['contactNumber'], + ); + } +} + class GoodsReceiptItem { final Product item; final int quantity; @@ -37,13 +60,13 @@ class GoodsReceiptItem { class GoodsReceipt { final String id; - final String site; final String siteManager; final String goodsReceiptId; final GoodsReceiptStatus status; final List items; final Supplier supplier; final DateTime createdAt; + final Site site; GoodsReceipt({ required this.id, @@ -60,10 +83,10 @@ class GoodsReceipt { return GoodsReceipt( id: json['_id'], supplier: Supplier.fromJson(json['supplier']), - site: json['site'], + site: Site.fromJson(json['site']), siteManager: json['siteManager'], goodsReceiptId: json['goodReceiptId'], - status: json['status'] == 'shipped' + status: json['status'] == 'received' ? GoodsReceiptStatus.received : GoodsReceiptStatus.pendingShipping, items: json['items'] diff --git a/flutter_client/lib/models/order.dart b/flutter_client/lib/models/order.dart index c289177..ebdde4f 100644 --- a/flutter_client/lib/models/order.dart +++ b/flutter_client/lib/models/order.dart @@ -7,6 +7,9 @@ class Order { final String siteId; final String? siteManagerId; final List products; + final String? status; + final double? total; + final String? supplierName; Order({ required this.supplierId, @@ -14,26 +17,35 @@ class Order { required this.dateToBeDelivered, required this.siteId, this.siteManagerId, + this.status, required this.products, + this.total, + this.supplierName, }); - set setSupplierId(String supplierId) { - supplierId = supplierId; + factory Order.fromJson(Map json) { + return Order( + supplierId: json['supplier']['_id'], + supplierName: json['supplier']['name'], + orderId: json['orderId'], + dateToBeDelivered: + DateTime.now(), //DateTime.parse(json['dateToBeDelivered']), + siteId: json['site']['_id'], + siteManagerId: json['siteManager']['_id'], + products: (json['items'] as List).map((product) { + return OrderProduct.fromJson(product); + }).toList(), + status: json['status'], + total: int.parse(json['total'].toString()).toDouble(), + ); } set setProducts(List products) { products = products; } - factory Order.fromJson(Map json) { - return Order( - supplierId: json['supplier'], - orderId: json['orderId'], - dateToBeDelivered: DateTime.parse(json['dateToBeDelivered']), - siteId: json['site'], - siteManagerId: json['siteManager'], - products: [], - ); + set setSupplierId(String supplierId) { + supplierId = supplierId; } Map toJson() { diff --git a/flutter_client/lib/models/order_product.dart b/flutter_client/lib/models/order_product.dart index 859ddf0..92f2047 100644 --- a/flutter_client/lib/models/order_product.dart +++ b/flutter_client/lib/models/order_product.dart @@ -23,10 +23,10 @@ class OrderProduct { factory OrderProduct.fromJson(Map json) { return OrderProduct( - productId: json['item'], + productId: json['item']['itemId'], quantity: json['quantity'], - price: json['price'], - title: json['name'], + price: double.parse((json['item']['price']).toString()), + title: json['item']['name'], ); } diff --git a/flutter_client/lib/procument_mobile_app.dart b/flutter_client/lib/procument_mobile_app.dart index af7d2e7..21f053b 100644 --- a/flutter_client/lib/procument_mobile_app.dart +++ b/flutter_client/lib/procument_mobile_app.dart @@ -6,12 +6,7 @@ import 'package:flutter_client/blocs/auth/auth_bloc.dart'; import 'package:flutter_client/constants.dart'; import 'package:flutter_client/repositiories/auth/auth_repository.dart'; -import 'package:flutter_client/screens/delivery_details.dart'; -import 'package:flutter_client/screens/done.dart'; -import 'package:flutter_client/screens/home_screen.dart'; import 'package:flutter_client/screens/login_screen.dart'; -import 'package:flutter_client/screens/my_order_details.dart'; -import 'package:flutter_client/screens/my_orders.dart'; import 'package:flutter_client/screens/main_screen.dart'; @@ -44,7 +39,8 @@ class _ProcumentMobileAppState extends State { }); }).catchError((error) { developer.log( - error, + 'Error: ${error.toString()}', + error: error, name: 'procument_mobile_app.dart', stackTrace: error, ); @@ -91,9 +87,7 @@ class _ProcumentMobileAppState extends State { textTheme: GoogleFonts.interTextTheme(), useMaterial3: true, ), - // home: _isTokenAvailable ? const HomeScreen() : const LoginScreen(), home: _isTokenAvailable ? const MainScreen() : const LoginScreen(), - ), ); } diff --git a/flutter_client/lib/repositiories/auth/auth_repository.dart b/flutter_client/lib/repositiories/auth/auth_repository.dart index 23add08..287a23a 100644 --- a/flutter_client/lib/repositiories/auth/auth_repository.dart +++ b/flutter_client/lib/repositiories/auth/auth_repository.dart @@ -38,6 +38,34 @@ class AuthRepository extends BaseAuthRepository { } } + Future get siteManagerName async { + final SharedPreferences sharedPreferences = + await SharedPreferences.getInstance(); + final token = sharedPreferences.getString('jwt'); + if (token == null) { + throw AuthException('Not logged in'); + } else { + try { + // Verify a token (SecretKey for HMAC & PublicKey for all the others) + final jwt = JWT.verify(token, SecretKey('secret')); + if (jwt.payload['role'] == 'siteManager') { + return jwt.payload['name']; + } else { + throw UnauthorizedException('Failed to login'); + } + } on JWTExpiredException catch (e) { + developer.log(e.message, name: "AuthRepository"); + throw TokenExpiredException(e.message); + } on JWTException catch (ex) { + developer.log(ex.message, name: "AuthRepository"); + throw AuthException(ex.message); // ex: invalid signature + } catch (e) { + developer.log(e.toString(), name: "AuthRepository"); + throw AuthException(e.toString()); + } + } + } + Future isTokenAvailable() async { final SharedPreferences sharedPreferences = await SharedPreferences.getInstance(); diff --git a/flutter_client/lib/repositiories/goods_receipt/base_goods_receipt_repository.dart b/flutter_client/lib/repositiories/goods_receipt/base_goods_receipt_repository.dart index a5180ab..a264781 100644 --- a/flutter_client/lib/repositiories/goods_receipt/base_goods_receipt_repository.dart +++ b/flutter_client/lib/repositiories/goods_receipt/base_goods_receipt_repository.dart @@ -49,4 +49,24 @@ class GoodsReceiptRepository extends BaseGoodsReceiptRepository { return goodsReceiptList; } + + Future markedAsReceived(String goodsReceiptNumber) async { + final Uri goodsReceiptURL = + Uri.https(hostName, '$markAsReceivedPath/$goodsReceiptNumber/received'); + + final sharedPreferences = await SharedPreferences.getInstance(); + final token = sharedPreferences.getString('jwt'); + + final headers = { + 'Authorization': 'Bearer $token', + }; + + await http + .patch(goodsReceiptURL, headers: headers) + .then((response) => response.body) + .catchError((error) { + developer.log(error); + throw Exception(error); + }); + } } diff --git a/flutter_client/lib/repositiories/order/base_order_repository.dart b/flutter_client/lib/repositiories/order/base_order_repository.dart index 78a7282..7d02dc4 100644 --- a/flutter_client/lib/repositiories/order/base_order_repository.dart +++ b/flutter_client/lib/repositiories/order/base_order_repository.dart @@ -2,8 +2,5 @@ import 'package:flutter_client/models/order.dart'; abstract class BaseOrderRepository { Future> getOrders(); - Future getOrder(String id); Future createOrder(Order order); - Future updateOrder(Order order); - Future deleteOrder(String id); } diff --git a/flutter_client/lib/repositiories/order/order_repository.dart b/flutter_client/lib/repositiories/order/order_repository.dart index 174494e..145afd4 100644 --- a/flutter_client/lib/repositiories/order/order_repository.dart +++ b/flutter_client/lib/repositiories/order/order_repository.dart @@ -17,6 +17,7 @@ class OrderRepository extends BaseOrderRepository { final headers = { 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', }; // send request to get all suppliers @@ -35,16 +36,16 @@ class OrderRepository extends BaseOrderRepository { }, ); - developer.log("body: ${requestBody.toString()}", name: "OrderRepository"); final responseBody = await http.post(orderURL, headers: headers, body: requestBody); - // .then((response) => response.body) - developer.log("responseBody: ${jsonDecode(responseBody.body).toString()}", - name: "OrderRepository"); - developer.log("responseBody: ${responseBody.statusCode}", - name: "OrderRepository"); - return true; + if (responseBody.statusCode == 201) { + return true; + } else { + developer.log(responseBody.body, + name: "OrderRepository", error: responseBody.body); + return false; + } } on TypeError catch (e) { developer.log(e.toString(), stackTrace: e.stackTrace, error: e, name: "OrderRepository"); @@ -55,53 +56,38 @@ class OrderRepository extends BaseOrderRepository { } } - @override - Future deleteOrder(String id) async { - return Order( - supplierId: '1', - orderId: '1', - dateToBeDelivered: DateTime.now(), - siteId: '1', - siteManagerId: '1', - products: [], - ); - } - - @override - Future getOrder(String id) async { - return Order( - supplierId: '1', - orderId: '1', - dateToBeDelivered: DateTime.now(), - siteId: '1', - siteManagerId: '1', - products: [], - ); - } - @override Future> getOrders() async { - return [ - Order( - supplierId: '1', - orderId: '1', - dateToBeDelivered: DateTime.now(), - siteId: '1', - siteManagerId: '1', - products: [], - ) - ]; - } + final Uri orderURL = Uri.https(hostName, orderPath); + // get token from shared preferences + final sharedPreferences = await SharedPreferences.getInstance(); + final token = sharedPreferences.getString('jwt'); - @override - Future updateOrder(Order order) async { - return Order( - supplierId: '1', - orderId: '1', - dateToBeDelivered: DateTime.now(), - siteId: '1', - siteManagerId: '1', - products: [], - ); + final headers = { + 'Authorization': 'Bearer $token', + }; + + // send request to get all suppliers + try { + final responseBody = await http.get(orderURL, headers: headers); + if (responseBody.statusCode == 200) { + final List orders = jsonDecode(responseBody.body); + return orders.map((order) => Order.fromJson(order)).toList(); + } else { + developer.log(responseBody.body, + name: "OrderRepository", error: responseBody.body); + return []; + } + } on TypeError catch (e) { + developer.log(e.toString(), + stackTrace: e.stackTrace, error: e, name: "OrderRepository"); + throw Exception(e); + } on FormatException catch (e) { + developer.log(e.message.toString(), error: e, name: "OrderRepository"); + throw Exception(e); + } catch (e) { + developer.log(e.toString(), name: "OrderRepository", error: e); + throw Exception(e); + } } } diff --git a/flutter_client/lib/repositiories/paths.dart b/flutter_client/lib/repositiories/paths.dart index 835809b..166c121 100644 --- a/flutter_client/lib/repositiories/paths.dart +++ b/flutter_client/lib/repositiories/paths.dart @@ -4,3 +4,4 @@ const String orderPath = 'api/orders'; const String authPath = 'api/login'; const String sitePath = 'api/sites'; const String getGoodsReceiptPath = '/api/site-manager/deliveries'; +const String markAsReceivedPath = '/api/site-manager/deliveries'; diff --git a/flutter_client/lib/screens/create_requisition_order_page.dart b/flutter_client/lib/screens/create_requisition_order_page.dart index 5ec47f4..30484f1 100644 --- a/flutter_client/lib/screens/create_requisition_order_page.dart +++ b/flutter_client/lib/screens/create_requisition_order_page.dart @@ -33,10 +33,9 @@ class _CreateRequisitionOrderState extends State { builder: (context, state) { return Scaffold( appBar: AppBar( - // leading: const Icon(Icons.arrow_back_ios), title: Text( 'Create Requisition Order', - style: Theme.of(context).textTheme.titleMedium!.copyWith( + style: Theme.of(context).textTheme.titleLarge!.copyWith( fontWeight: FontWeight.w900, ), ), diff --git a/flutter_client/lib/screens/delivery_advices_list_screen.dart b/flutter_client/lib/screens/delivery_advices_list_screen.dart index bd45483..e585ef1 100644 --- a/flutter_client/lib/screens/delivery_advices_list_screen.dart +++ b/flutter_client/lib/screens/delivery_advices_list_screen.dart @@ -1,9 +1,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_client/blocs/goodReceipts/goods_receipt_bloc.dart'; -import 'package:flutter_client/constants.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_client/models/goods_receipt.dart'; -import 'package:flutter_client/screens/delivery_confirmation.dart'; import 'package:flutter_client/widgets/delivery_card.dart'; class Deliveryadvice extends StatefulWidget { @@ -24,45 +21,63 @@ class _DeliveryadviceState extends State { @override Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - return Scaffold( - appBar: AppBar( - title: const Text('Delivery Advices'), - ), - body: state is GoodsReceiptLoading || state is GoodsReceiptInitial - ? const Center( - child: CircularProgressIndicator(), - ) - : - // state is GoodsReceiptsLoaded - state is GoodsReceiptsLoaded - ? Padding( - padding: const EdgeInsets.all(25.0), - child: SingleChildScrollView( - child: ListView.separated( - separatorBuilder: (context, index) => const SizedBox( - height: 10, - ), - shrinkWrap: true, - itemCount: state.goodsReceipts.length, - itemBuilder: (context, index) { - return DeliveryAdviceCard( - goodsReceipt: state.goodsReceipts[index], - ); - }, - ), - ), - ) - : state is GoodsReceiptError - ? const Center( - child: Text('Error'), - ) - : const Center( - child: Text('Error'), - ), - ); + return BlocListener( + listener: (context, state) { + if (state is GoodsReceiptMarkedAsReceived) { + BlocProvider.of(context).add( + const GetGoodsReceiptsEvent(), + ); + } }, + child: BlocBuilder( + builder: (context, state) { + return Scaffold( + appBar: AppBar( + title: Text( + 'Delivery Advices', + style: Theme.of(context).textTheme.titleLarge!.copyWith( + fontWeight: FontWeight.w900, + ), + ), + ), + body: state is GoodsReceiptLoading || state is GoodsReceiptInitial + ? const Center( + child: CircularProgressIndicator(), + ) + : + // state is GoodsReceiptsLoaded + state is GoodsReceiptsLoaded + ? state.goodsReceipts.isNotEmpty + ? Container( + padding: + const EdgeInsets.fromLTRB(24.0, 8.0, 24.0, 8.0), + child: ListView.separated( + separatorBuilder: (context, index) => + const SizedBox( + height: 10, + ), + shrinkWrap: true, + itemCount: state.goodsReceipts.length, + itemBuilder: (context, index) { + return DeliveryAdviceCard( + goodsReceipt: state.goodsReceipts[index], + ); + }, + ), + ) + : const Center( + child: Text('No Delivery Advices'), + ) + : state is GoodsReceiptError + ? const Center( + child: Text('Error'), + ) + : const Center( + child: Text('Error'), + ), + ); + }, + ), ); } } diff --git a/flutter_client/lib/screens/delivery_confirmation.dart b/flutter_client/lib/screens/delivery_confirmation.dart deleted file mode 100644 index afe1c6b..0000000 --- a/flutter_client/lib/screens/delivery_confirmation.dart +++ /dev/null @@ -1,140 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_client/constants.dart'; - -class DeliveryConfirm extends StatefulWidget { - const DeliveryConfirm({super.key}); - - @override - State createState() => _DeliveryConfirmState(); -} - -class _DeliveryConfirmState extends State { - bool isChecked = false; - - - //check box - void _handleCheckbox(bool? value) { - setState(() { - isChecked = value ?? false; - }); -} - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Delivery confirmation'), - ), - body: Center( - child: Padding( - padding: const EdgeInsets.all(25.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Card( - // shape: const RoundedRectangleBorder( - // borderRadius: BorderRadius.circular(10), - // ), - elevation: 8, - color: kSeedColor, - child: SizedBox( - width: double.infinity, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Delivery Advice 1', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - fontSize: 14, - fontWeight: FontWeight.bold, - color: Colors.white), - ), - ], - ), - ], - ), - ), - ), - ), - const SizedBox(height: 10), - Card( - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.zero), - ), - elevation: 8, - child: SizedBox( - width: double.infinity, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Tokiyo Super Cement - 50KG', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - fontSize: 14, - fontWeight: FontWeight.bold, - ), - ), - const Spacer(), - Checkbox( - value: isChecked, - onChanged: _handleCheckbox, - ), - - ], - ), - // const SizedBox(height: 20), - // Text( - // 'Quantity', - // style: Theme.of(context) - // .textTheme - // .bodyMedium! - // .copyWith( - // fontSize: 12, - // fontWeight: FontWeight.bold, - // ), - // ), - // SizedBox( - // child: Text( - // '50', - // style: Theme.of(context) - // .textTheme - // .bodyMedium! - // .copyWith( - // fontSize: 12, - // fontWeight: FontWeight.normal, - // ), - // ), - // ), - // Checkbox( - // value: isChecked, - // onChanged: _handleCheckbox, - // ), - ], - ), - ), - ), - ), - ], - ), - ), - ), - ); - } -} diff --git a/flutter_client/lib/screens/delivery_confirmation_screen.dart b/flutter_client/lib/screens/delivery_confirmation_screen.dart new file mode 100644 index 0000000..486ed3d --- /dev/null +++ b/flutter_client/lib/screens/delivery_confirmation_screen.dart @@ -0,0 +1,205 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_client/blocs/goodReceipts/goods_receipt_bloc.dart'; +import 'package:flutter_client/constants.dart'; +import 'package:flutter_client/models/goods_receipt.dart'; + +class DeliveryConfirm extends StatefulWidget { + final GoodsReceipt goodsReceipt; + const DeliveryConfirm({super.key, required this.goodsReceipt}); + + @override + State createState() => _DeliveryConfirmState(); +} + +class _DeliveryConfirmState extends State { + late List _isChecked; + @override + void initState() { + super.initState(); + if (widget.goodsReceipt.status == GoodsReceiptStatus.received) + _isChecked = List.filled(widget.goodsReceipt.items.length, true); + else + _isChecked = List.filled(widget.goodsReceipt.items.length, false); + } + + @override + Widget build(BuildContext context) { + return BlocListener( + listener: (context, state) { + if (state is GoodsReceiptMarkedAsReceived) { + Navigator.of(context).pop(); + } + }, + child: BlocBuilder( + builder: (context, state) { + return Scaffold( + appBar: AppBar( + title: Text( + 'Delivery confirmation', + style: Theme.of(context).textTheme.titleLarge!.copyWith( + fontWeight: FontWeight.w900, + ), + ), + ), + body: Center( + child: Padding( + padding: const EdgeInsets.fromLTRB(16.0, 8.0, 16.0, 8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Card( + // shape: const RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(10), + // ), + elevation: 8, + color: kSeedColor, + child: SizedBox( + width: double.infinity, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.goodsReceipt.goodsReceiptId, + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.white), + ), + ], + ), + ], + ), + ), + ), + ), + const SizedBox(height: 10), + ListView.separated( + separatorBuilder: (context, index) => const SizedBox( + height: 10, + ), + shrinkWrap: true, + itemCount: widget.goodsReceipt.items.length, + itemBuilder: (context, index) { + return Card( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.zero), + ), + elevation: 8, + child: SizedBox( + width: double.infinity, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + widget.goodsReceipt.items[index].item.title, + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + const Spacer(), + Text( + 'Qty:', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + fontSize: 14, + fontWeight: FontWeight.w100, + ), + ), + Text( + widget.goodsReceipt.items[index].quantity + .toString(), + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + Checkbox( + value: _isChecked[index], + onChanged: (val) { + setState( + () { + _isChecked[index] = val ?? false; + }, + ); + }, + ), + ], + ), + ), + ), + ); + }, + ), + ], + ), + ), + ), + bottomNavigationBar: widget.goodsReceipt.status == + GoodsReceiptStatus.received + ? null + : Padding( + padding: const EdgeInsets.fromLTRB(25.0, 0, 25.0, 16.0), + child: SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + if (_isChecked.contains(false)) return; + if (state is GoodsReceiptMarkingAsReceived) return; + BlocProvider.of(context).add( + MarkAsReceivedEvent( + widget.goodsReceipt.goodsReceiptId, + ), + ); + }, + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + backgroundColor: _isChecked.contains(false) + ? Colors.grey[200] + : kSeedColor, + ), + child: state is GoodsReceiptMarkingAsReceived + ? const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + color: Colors.white, + ), + ) + : Text('Confirm', + style: TextStyle( + color: _isChecked.contains(false) + ? Colors.black + : Colors.white, + )), + ), + ), + ), + ); + }, + ), + ); + } +} diff --git a/flutter_client/lib/screens/delivery_details.dart b/flutter_client/lib/screens/delivery_details.dart index 0971e29..8d6d03c 100644 --- a/flutter_client/lib/screens/delivery_details.dart +++ b/flutter_client/lib/screens/delivery_details.dart @@ -24,7 +24,6 @@ class _MyAppState extends State { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - /// Input field for site location ----------------------------------------->>>>>>>>>>>>>>>>>>>>>>>>>>>>> const Text("Select Site Location"), Container( @@ -40,8 +39,7 @@ class _MyAppState extends State { return DropdownMenuItem( value: item, child: Padding( - padding: const EdgeInsets.all( - 8.0), + padding: const EdgeInsets.all(8.0), child: Text(item), ), ); @@ -58,7 +56,7 @@ class _MyAppState extends State { ), )), const SizedBox(height: 20), - + // date picker input -------------------------------------------------------->>>>>>>>>>>>>> const Text("Expected Delivery Date"), @@ -87,7 +85,7 @@ class _MyAppState extends State { onTap: () { _selectDate(context); }, - child: Icon(Icons.calendar_today), + child: const Icon(Icons.calendar_today), ), ), ], diff --git a/flutter_client/lib/screens/delivery_details_screen.dart b/flutter_client/lib/screens/delivery_details_screen.dart index 73b58a7..a4a4161 100644 --- a/flutter_client/lib/screens/delivery_details_screen.dart +++ b/flutter_client/lib/screens/delivery_details_screen.dart @@ -5,6 +5,7 @@ import 'package:flutter_client/blocs/order/order_bloc.dart'; import 'package:flutter_client/blocs/site/site_bloc.dart'; import 'package:flutter_client/models/order.dart'; import 'package:flutter_client/models/site.dart'; +import 'package:flutter_client/screens/order_sucess_screen.dart'; class DeliveryDetailsScreen extends StatefulWidget { const DeliveryDetailsScreen({super.key}); @@ -53,7 +54,7 @@ class _DeliveryDetailsScreenState extends State { appBar: AppBar( title: Text( "Delivery Details", - style: Theme.of(context).textTheme.titleMedium!.copyWith( + style: Theme.of(context).textTheme.titleLarge!.copyWith( fontWeight: FontWeight.w900, ), ), @@ -63,19 +64,25 @@ class _DeliveryDetailsScreenState extends State { BlocListener( listener: (context, state) { if (state is SiteLoaded) { - setState(() { - _sites.addAll(state.sites); - }); - - _dropdownValue = _sites[0].siteId; + if (state.sites.isNotEmpty) { + setState(() { + _sites.addAll(state.sites); + _dropdownValue = _sites[0].siteId; + _selectedSite = _sites[0].id; + }); + } } }, ), BlocListener( listener: (context, state) { if (state is OrderCreated) { - // BlocProvider.of(context).add(const ClearCartEvent()); - // Navigator.of(context).pop(); + BlocProvider.of(context).add(const ClearCartEvent()); + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const OrderSuccessScreen(), + ), + ); } if (state is OrderNotCreated) { @@ -128,9 +135,6 @@ class _DeliveryDetailsScreenState extends State { site.siteId == selectedSite) .first .id; - // _sites.firstWhere( - // (element) => - // element.siteId == newValue); }, ); }, @@ -200,60 +204,74 @@ class _DeliveryDetailsScreenState extends State { const SizedBox( height: 100, ), - ElevatedButton( - style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.0), - ), - backgroundColor: - Theme.of(context).colorScheme.primary, - ), - onPressed: () { - BlocProvider.of(context).add( - CreateOrderEvent( - order: Order( - supplierId: - BlocProvider.of(context) - .supplier - .id, - dateToBeDelivered: selectedDate, - siteId: _selectedSite, - products: - BlocProvider.of(context) - .cart, - ), - ), - ); - // Navigator.of(context).pop(); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'Total: LKR ${_cartTotal.toStringAsFixed(2)}', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - color: Colors.white, + BlocBuilder( + builder: (context, state) { + return state is! CreatingOrder + ? ElevatedButton( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(8.0), + ), + backgroundColor: Theme.of(context) + .colorScheme + .primary, ), - ), - const Spacer(), - Text( - 'Finish', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - color: Colors.white, + onPressed: () { + BlocProvider.of(context) + .add( + CreateOrderEvent( + order: Order( + supplierId: + BlocProvider.of( + context) + .supplier + .id, + dateToBeDelivered: + selectedDate, + siteId: _selectedSite, + products: + BlocProvider.of( + context) + .cart, + ), + ), + ); + }, + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Text( + 'Total: LKR ${_cartTotal.toStringAsFixed(2)}', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: Colors.white, + ), + ), + const Spacer(), + Text( + 'Finish', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: Colors.white, + ), + ), + const Icon( + Icons.chevron_right, + color: Colors.white, + ) + ], ), - ), - const Icon( - Icons.chevron_right, - color: Colors.white, - ) - ], - ), + ) + : const Center( + child: CircularProgressIndicator(), + ); + }, ), ], ), diff --git a/flutter_client/lib/screens/done.dart b/flutter_client/lib/screens/done.dart index d6c3481..94b8bfc 100644 --- a/flutter_client/lib/screens/done.dart +++ b/flutter_client/lib/screens/done.dart @@ -9,29 +9,26 @@ class Done extends StatelessWidget { body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, children: [ Image.asset( - "images/successfully-done.gif", - height: 150, + "images/successfully-done.gif", + height: 150, ), - - Text( + const Text( "Success!", style: TextStyle( fontSize: 19, fontWeight: FontWeight.w700, ), ), - - Text( + const Text( "Order Created.", style: TextStyle( fontSize: 15, fontWeight: FontWeight.w400, ), ), - ], ), ), diff --git a/flutter_client/lib/screens/home_screen.dart b/flutter_client/lib/screens/home_screen.dart index d91e2a7..f4a34ee 100644 --- a/flutter_client/lib/screens/home_screen.dart +++ b/flutter_client/lib/screens/home_screen.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_client/blocs/auth/auth_bloc.dart'; import 'package:flutter_client/screens/create_requisition_order_page.dart'; -import 'package:flutter_client/constants.dart'; import 'package:flutter_client/screens/my_orders_screen.dart'; import 'package:flutter_client/screens/delivery_advices_list_screen.dart'; @@ -11,9 +10,16 @@ class HomeScreen extends StatelessWidget { @override Widget build(BuildContext context) { + BlocProvider.of(context).add(const GetSiteManagerName()); return Scaffold( appBar: AppBar( - title: const Text('Home'), + title: Text( + 'Home', + style: Theme.of(context).textTheme.titleLarge!.copyWith( + fontSize: 24, + fontWeight: FontWeight.w900, + ), + ), actions: [ IconButton( onPressed: () { @@ -34,406 +40,202 @@ class HomeScreen extends StatelessWidget { }, ), ), - body: Center( - child: Padding( - padding: const EdgeInsets.all(25.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - "Hi, Supun", - style: TextStyle(fontSize: 24), - ), - const SizedBox(height: 15), - InkWell( - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => const CreateRequisitionOrder(), - ), - ); - }, - //Create Order Card - child: Card( - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.zero), - ), - elevation: 8, - color: Theme.of(context).colorScheme.primary, - child: SizedBox( - width: double.infinity, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Icon( - Icons.create_new_folder, - size: 24, - color: Colors.white, - ), - const SizedBox(height: 10), - Text( - 'Create order', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.white), + body: BlocBuilder( + builder: (context, state) { + return Center( + child: Padding( + padding: const EdgeInsets.all(25.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (state is SiteManagerNameLoading) + const CircularProgressIndicator() + else if (state is SiteManagerName) + Text( + state.siteManagerName, + style: Theme.of(context).textTheme.titleLarge!.copyWith( + fontWeight: FontWeight.w900, ), - const SizedBox(height: 10), - Text( - 'You can create orders from this.', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Colors.white), + ), + const SizedBox(height: 15), + InkWell( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + const CreateRequisitionOrder(), + ), + ); + }, + //Create Order Card + child: Card( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.zero), + ), + elevation: 8, + color: Theme.of(context).colorScheme.primary, + child: SizedBox( + width: double.infinity, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Icon( + Icons.create_new_folder, + size: 24, + color: Colors.white, + ), + const SizedBox(height: 10), + Text( + 'Create order', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.white), + ), + const SizedBox(height: 10), + Text( + 'You can create orders from this.', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Colors.white), + ), + ], ), - ], + ), ), ), ), - ), - ), - InkWell( - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) { - return const MyOrders(); - }, - ), - ); - }, - //My Orders Card - child: Card( - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.zero), - ), - elevation: 8, - color: Theme.of(context).colorScheme.primary, - child: SizedBox( - width: double.infinity, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Icon( - Icons.view_agenda, - size: 24, - color: Color.fromARGB(255, 255, 255, 255), - ), - const SizedBox(height: 10), - Text( - 'My orders', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - const SizedBox(height: 10), - Text( - 'You can access all the orders you have taken.', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Colors.white, - ), + InkWell( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) { + return const MyOrders(); + }, + ), + ); + }, + //My Orders Card + child: Card( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.zero), + ), + elevation: 8, + color: Theme.of(context).colorScheme.primary, + child: SizedBox( + width: double.infinity, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Icon( + Icons.view_agenda, + size: 24, + color: Color.fromARGB(255, 255, 255, 255), + ), + const SizedBox(height: 10), + Text( + 'My orders', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + const SizedBox(height: 10), + Text( + 'You can access all the orders you have taken.', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ], ), - ], + ), ), ), ), - ), - ), - InkWell( - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) { - return const Deliveryadvice(); - }, - ), - ); - }, - //Dummy Card - child: Card( - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.zero), - ), - elevation: 8, - color: Theme.of(context).colorScheme.primary, - child: SizedBox( - width: double.infinity, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Icon( - Icons.delivery_dining, - size: 24, - color: Color.fromARGB(255, 255, 255, 255), - ), - const SizedBox(height: 10), - Text( - 'Delivery advice', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.white), - ), - const SizedBox(height: 10), - Text( - 'You can indicate whether the delivery advice is completed or not.', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Colors.white), + InkWell( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) { + return const Deliveryadvice(); + }, + ), + ); + }, + //Dummy Card + child: Card( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.zero), + ), + elevation: 8, + color: Theme.of(context).colorScheme.primary, + child: SizedBox( + width: double.infinity, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Icon( + Icons.delivery_dining, + size: 24, + color: Color.fromARGB(255, 255, 255, 255), + ), + const SizedBox(height: 10), + Text( + 'Delivery advice', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.white), + ), + const SizedBox(height: 10), + Text( + 'You can indicate whether a delivery advice is completed or not.', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Colors.white), + ), + ], ), - ], + ), ), ), ), - ), + ], ), - ], - ), - ), + ), + ); + }, )); } } - -// appBar: AppBar( -// title: const Text('Home'), -// actions: [ -// IconButton( -// onPressed: () { -// BlocProvider.of(context).add(SignOut()); -// }, -// icon: const Icon(Icons.logout), -// ), -// IconButton( -// icon: const Icon(Icons.account_circle), -// onPressed: () {}, -// ), -// const SizedBox(width: 10), -// ], -// leading: IconButton( -// icon: const Icon(Icons.menu), -// onPressed: () { -// Scaffold.of(context).openDrawer(); -// }, -// ), -// ), - // body: Center( - // child: Padding( - // padding: const EdgeInsets.all(25.0), - // child: Column( - // mainAxisAlignment: MainAxisAlignment.start, - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // const Text( - // "Hi, Supun", - // style: TextStyle(fontSize: 24), - // ), - // const SizedBox(height: 15), - // InkWell( - // onTap: () { - // Navigator.of(context).push( - // MaterialPageRoute( - // builder: (context) => const CreateRequisitionOrder(), - // ), - // ); - // }, - // //Create Order Card - // child: Card( - // shape: const RoundedRectangleBorder( - // borderRadius: BorderRadius.all(Radius.zero), - // ), - // elevation: 8, - // color: Theme.of(context).colorScheme.primary, - // child: SizedBox( - // width: double.infinity, - // child: Padding( - // padding: const EdgeInsets.all(16.0), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // const Icon( - // Icons.create_new_folder, - // size: 24, - // color: Colors.white, - // ), - // const SizedBox(height: 10), - // Text( - // 'Create order', - // style: Theme.of(context) - // .textTheme - // .bodyMedium! - // .copyWith( - // fontSize: 16, - // fontWeight: FontWeight.bold, - // color: Colors.white), - // ), - // const SizedBox(height: 10), - // Text( - // 'You can create orders from this.', - // style: Theme.of(context) - // .textTheme - // .bodyMedium! - // .copyWith( - // fontSize: 12, - // fontWeight: FontWeight.bold, - // color: Colors.white), - // ), - // ], - // ), - // ), - // ), - // ), - // ), - // InkWell( - // onTap: () { - // Navigator.of(context).push( - // MaterialPageRoute( - // builder: (context) { - // return const MyOrders(); - // }, - // ), - // ); - // }, - // //My Orders Card - // child: Card( - // shape: const RoundedRectangleBorder( - // borderRadius: BorderRadius.all(Radius.zero), - // ), - // elevation: 8, - // color: Theme.of(context).colorScheme.primary, - // child: SizedBox( - // width: double.infinity, - // child: Padding( - // padding: const EdgeInsets.all(16.0), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // const Icon( - // Icons.view_agenda, - // size: 24, - // color: Color.fromARGB(255, 255, 255, 255), - // ), - // const SizedBox(height: 10), - // Text( - // 'My orders', - // style: Theme.of(context) - // .textTheme - // .bodyMedium! - // .copyWith( - // fontSize: 16, - // fontWeight: FontWeight.bold, - // color: Colors.white, - // ), - // ), - // const SizedBox(height: 10), - // Text( - // 'You can access all the orders you have taken.', - // style: Theme.of(context) - // .textTheme - // .bodyMedium! - // .copyWith( - // fontSize: 12, - // fontWeight: FontWeight.bold, - // color: Colors.white, - // ), - // ), - // ], - // ), - // ), - // ), - // ), - // ), - // InkWell( - - // onTap: () { - // Navigator.of(context).push( - // MaterialPageRoute( - // builder: (context) { - // return const Deliveryadvice(); - // }, - // ), - // ); - // }, - // //Dummy Card - // child: Card( - // shape: const RoundedRectangleBorder( - // borderRadius: BorderRadius.all(Radius.zero), - // ), - // elevation: 8, - // color: Theme.of(context).colorScheme.primary, - // child: SizedBox( - // width: double.infinity, - // child: Padding( - // padding: const EdgeInsets.all(16.0), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // const Icon( - // Icons.delivery_dining, - // size: 24, - // color: Color.fromARGB(255, 255, 255, 255), - // ), - // const SizedBox(height: 10), - // Text( - // 'Delivery advice', - // style: Theme.of(context) - // .textTheme - // .bodyMedium! - // .copyWith( - // fontSize: 16, - // fontWeight: FontWeight.bold, - // color: Colors.white), - // ), - // const SizedBox(height: 10), - // Text( - // 'You can indicate whether the delivery advice is completed or not.', - // style: Theme.of(context) - // .textTheme - // .bodyMedium! - // .copyWith( - // fontSize: 12, - // fontWeight: FontWeight.bold, - // color: Colors.white), - // ), - // ], - // ), - // ), - // ), - // ), - // ), - // ], - // ), - // ), - // )); - - - diff --git a/flutter_client/lib/screens/main_screen.dart b/flutter_client/lib/screens/main_screen.dart index 6b9ab8b..f8aa2d4 100644 --- a/flutter_client/lib/screens/main_screen.dart +++ b/flutter_client/lib/screens/main_screen.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:flutter_client/screens/create_requisition_order_page.dart'; import 'package:flutter_client/screens/home_screen.dart'; +import 'package:flutter_client/screens/my_orders_screen.dart'; class MainScreen extends StatefulWidget { const MainScreen({super.key}); @@ -13,7 +13,7 @@ class _MainScreenState extends State { int _currentIndex = 0; final _pages = [ const HomeScreen(), - const CreateRequisitionOrder(), + const MyOrders(), const HomeScreen(), ]; diff --git a/flutter_client/lib/screens/my_order_details.dart b/flutter_client/lib/screens/my_order_details.dart index d2b2547..945ad0a 100644 --- a/flutter_client/lib/screens/my_order_details.dart +++ b/flutter_client/lib/screens/my_order_details.dart @@ -1,8 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_client/constants.dart'; +import 'package:flutter_client/models/order.dart'; +import 'package:flutter_client/widgets/order_details_product_card.dart'; class MyOrderDetails extends StatefulWidget { - const MyOrderDetails({super.key}); + const MyOrderDetails({required this.order, super.key}); + final Order order; @override State createState() => _MyOrderDetailsState(); @@ -12,128 +15,68 @@ class _MyOrderDetailsState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text('Home'), - ), - body: Center( - child: Padding( - padding: const EdgeInsets.all(25.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Card( - // shape: const RoundedRectangleBorder( - // borderRadius: BorderRadius.circular(10), - // ), - elevation: 8, - color: kSeedColor, - child: Container( - width: double.infinity, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Order ID', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - fontSize: 14, - fontWeight: FontWeight.bold, - color: Colors.white - ), - ), - ], - ), - - - ], - ), - ), - ), - ), - - SizedBox(height: 10), - Card( - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.zero), + appBar: AppBar( + title: Text( + 'Order Details', + style: Theme.of(context).textTheme.titleLarge!.copyWith( + fontWeight: FontWeight.w900, ), - elevation: 8, - child: Container( - width: double.infinity, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Create order', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - fontSize: 14, - fontWeight: FontWeight.bold, - ), - ), - Spacer(), - Text( - 'Price', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - fontSize: 12, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - SizedBox(height: 20), - Text( - 'Quantity', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - fontSize: 12, - fontWeight: FontWeight.bold, - ), - ), - Container( - child: Text( - '50', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - fontSize: 12, - fontWeight: FontWeight.normal, - - - ), - ), - + ), + ), + body: Center( + child: Padding( + padding: const EdgeInsets.all(25.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + elevation: 8, + color: kSeedColor, + child: SizedBox( + width: double.infinity, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.order.orderId!, + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.white), + ), + ], ), - - ], + ], + ), ), ), ), - ), - ], + const SizedBox(height: 10), + ListView.separated( + shrinkWrap: true, + itemBuilder: (context, index) => OrderDetailsProductCart( + orderProduct: widget.order.products[index], + ), + separatorBuilder: (context, index) => const SizedBox( + height: 10, + ), + itemCount: widget.order.products.length, + ), + ], + ), ), - ), - ) - ); + )); } -} \ No newline at end of file +} diff --git a/flutter_client/lib/screens/my_orders.dart b/flutter_client/lib/screens/my_orders.dart index d80cd07..9b606dd 100644 --- a/flutter_client/lib/screens/my_orders.dart +++ b/flutter_client/lib/screens/my_orders.dart @@ -39,7 +39,12 @@ class MyOrders extends StatelessWidget { return Scaffold( appBar: AppBar( - title: const Text('My orders'), + title: Text( + 'My orders', + style: Theme.of(context).textTheme.titleLarge!.copyWith( + fontWeight: FontWeight.w900, + ), + ), ), body: Center( child: Padding( @@ -53,7 +58,7 @@ class MyOrders extends StatelessWidget { borderRadius: BorderRadius.all(Radius.zero), ), elevation: 8, - child: Container( + child: SizedBox( width: double.infinity, child: Padding( padding: const EdgeInsets.all(16.0), @@ -73,7 +78,7 @@ class MyOrders extends StatelessWidget { fontWeight: FontWeight.bold, ), ), - Spacer(), + const Spacer(), Text( 'Date', style: Theme.of(context) @@ -86,7 +91,7 @@ class MyOrders extends StatelessWidget { ), ], ), - SizedBox(height: 20), + const SizedBox(height: 20), Text( 'Supplier name', style: @@ -107,7 +112,7 @@ class MyOrders extends StatelessWidget { fontWeight: FontWeight.normal, ), ), - Spacer(), + const Spacer(), Container( decoration: BoxDecoration( color: buttonProperties.color, @@ -117,7 +122,7 @@ class MyOrders extends StatelessWidget { onPressed: null, child: Text( buttonProperties.text, - style: TextStyle(color: Colors.white), + style: const TextStyle(color: Colors.white), ), ), ), diff --git a/flutter_client/lib/screens/my_orders_screen.dart b/flutter_client/lib/screens/my_orders_screen.dart index bffb554..3d928a1 100644 --- a/flutter_client/lib/screens/my_orders_screen.dart +++ b/flutter_client/lib/screens/my_orders_screen.dart @@ -1,149 +1,66 @@ -import 'package:flutter_client/constants.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_client/screens/order_details_screen.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_client/blocs/order/order_bloc.dart'; +import 'package:flutter_client/widgets/my_orders_card.dart'; -class OrderButtonProperties { - final Color color; - final String text; - - OrderButtonProperties(this.color, this.text); -} - -class MyOrders extends StatelessWidget { +class MyOrders extends StatefulWidget { const MyOrders({super.key}); - final String orderState = 'placed'; + @override + State createState() => _MyOrdersState(); +} - OrderButtonProperties getButtonProperties(String state) { - switch (state) { - case 'pending': - return OrderButtonProperties(kViewPendingColor, 'Pending'); - case 'approved': - return OrderButtonProperties( - kApprovedAndOrderCompletedColor, 'Approved'); - case 'declined': - return OrderButtonProperties(kDeclined, 'Declined'); - case 'placed': - return OrderButtonProperties(kPlaced, 'Placed'); - case 'partially delivered': - return OrderButtonProperties( - kPartiallyDelivered, 'Partially delivered'); - default: - return OrderButtonProperties( - kApprovedAndOrderCompletedColor, 'Completed'); - } +class _MyOrdersState extends State { + @override + void initState() { + super.initState(); + BlocProvider.of(context).add(const GetOrdersEvent()); } @override Widget build(BuildContext context) { - final buttonProperties = getButtonProperties(orderState); - return Scaffold( appBar: AppBar( - title: const Text('My orders'), - ), - body: Center( - child: Padding( - padding: const EdgeInsets.all(25.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - InkWell( - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => const MyOrderDetails(), - ), - ); - }, - child: Card( - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.zero), - ), - elevation: 8, - child: SizedBox( - width: double.infinity, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Create order', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - fontSize: 14, - fontWeight: FontWeight.bold, - ), - ), - const Spacer(), - Text( - 'Date', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - fontSize: 12, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - const SizedBox(height: 20), - Text( - 'Supplier name', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - fontSize: 12, - fontWeight: FontWeight.bold, - ), - ), - Row( - children: [ - Text( - 'Rs.25.000', - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - fontSize: 12, - fontWeight: FontWeight.normal, - ), - ), - const Spacer(), - Container( - decoration: BoxDecoration( - color: buttonProperties.color, - borderRadius: BorderRadius.circular(8), - ), - child: TextButton( - onPressed: null, - child: Text( - buttonProperties.text, - style: const TextStyle(color: Colors.white), - ), - ), - ), - ], - ), - ], - ), - ), - ), - ), + title: Text( + 'My orders', + style: Theme.of(context).textTheme.titleLarge!.copyWith( + fontWeight: FontWeight.w900, ), - ], - ), ), ), + body: BlocBuilder( + builder: (context, state) { + if (state is GettingOrders) { + return const Center( + child: CircularProgressIndicator(), + ); + } else if (state is OrdersRetrieved) { + return ListView.separated( + shrinkWrap: true, + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + itemCount: state.orders.length, + itemBuilder: (context, index) { + return MyOrdersCard(order: state.orders[index]); + }, + separatorBuilder: (BuildContext context, int index) => + const SizedBox( + height: 10, + ), + ); + } else if (state is ErrorRetrievingOrders) { + return Center( + child: Text(state.message), + ); + } else { + return const Center( + child: Text('Something went wrong'), + ); + } + }, + ), ); } } diff --git a/flutter_client/lib/screens/order_details_screen.dart b/flutter_client/lib/screens/order_details_screen.dart index 248edc6..2d53080 100644 --- a/flutter_client/lib/screens/order_details_screen.dart +++ b/flutter_client/lib/screens/order_details_screen.dart @@ -13,7 +13,12 @@ class _MyOrderDetailsState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Order details'), + title: Text( + 'Order details', + style: Theme.of(context).textTheme.titleLarge!.copyWith( + fontWeight: FontWeight.w900, + ), + ), ), body: Center( child: Padding( diff --git a/flutter_client/lib/screens/order_sucess_screen.dart b/flutter_client/lib/screens/order_sucess_screen.dart new file mode 100644 index 0000000..a06a1a2 --- /dev/null +++ b/flutter_client/lib/screens/order_sucess_screen.dart @@ -0,0 +1,42 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +class OrderSuccessScreen extends StatelessWidget { + const OrderSuccessScreen({super.key}); + + @override + Widget build(BuildContext context) { + Timer(const Duration(seconds: 6), () { + Navigator.of(context).popUntil((route) => route.isFirst); + }); + return Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.asset( + "assets/images/successfully-done.gif", + height: 150, + ), + const Text( + "Success!", + style: TextStyle( + fontSize: 19, + fontWeight: FontWeight.w700, + ), + ), + const Text( + "Order Created.", + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + ); + } +} diff --git a/flutter_client/lib/screens/select_product_screen.dart b/flutter_client/lib/screens/select_product_screen.dart index 2841639..2c18583 100644 --- a/flutter_client/lib/screens/select_product_screen.dart +++ b/flutter_client/lib/screens/select_product_screen.dart @@ -33,8 +33,11 @@ class _SelectProductScreenState extends State { }, child: Scaffold( appBar: AppBar( - title: const Text( + title: Text( 'Select Product', + style: Theme.of(context).textTheme.titleLarge!.copyWith( + fontWeight: FontWeight.w900, + ), ), ), body: SingleChildScrollView( diff --git a/flutter_client/lib/screens/selected_products_screen.dart b/flutter_client/lib/screens/selected_products_screen.dart index fff80f0..416b9ef 100644 --- a/flutter_client/lib/screens/selected_products_screen.dart +++ b/flutter_client/lib/screens/selected_products_screen.dart @@ -61,7 +61,7 @@ class _SupplierProductsScreenState extends State { appBar: AppBar( title: Text( 'Products', - style: Theme.of(context).textTheme.titleMedium!.copyWith( + style: Theme.of(context).textTheme.titleLarge!.copyWith( fontWeight: FontWeight.w900, ), ), diff --git a/flutter_client/lib/widgets/delivery_card.dart b/flutter_client/lib/widgets/delivery_card.dart index f1488f5..ce67b1e 100644 --- a/flutter_client/lib/widgets/delivery_card.dart +++ b/flutter_client/lib/widgets/delivery_card.dart @@ -1,9 +1,7 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_client/constants.dart'; import 'package:flutter_client/models/goods_receipt.dart'; -import 'package:flutter_client/screens/delivery_confirmation.dart'; +import 'package:flutter_client/screens/delivery_confirmation_screen.dart'; import 'package:intl/intl.dart'; class OrderButtonProperties { @@ -19,12 +17,6 @@ OrderButtonProperties getButtonProperties(String state) { case 'Completed': return OrderButtonProperties( kApprovedAndOrderCompletedColor, 'Completed'); - // case 'Pending': - // return OrderButtonProperties(partialyDelivered, 'Pending'); - // case 'placed': - // return OrderButtonProperties(placed, 'Placed'); - // case 'partially delivered': - // return OrderButtonProperties(partialyDelivered, 'Partially delivered'); default: return OrderButtonProperties(kDeclined, 'Not completed'); } @@ -42,7 +34,9 @@ class DeliveryAdviceCard extends StatelessWidget { onTap: () { Navigator.of(context).push( MaterialPageRoute( - builder: (context) => const DeliveryConfirm(), + builder: (context) => DeliveryConfirm( + goodsReceipt: goodsReceipt, + ), ), ); }, @@ -81,6 +75,13 @@ class DeliveryAdviceCard extends StatelessWidget { ], ), const SizedBox(height: 20), + Text( + 'Supplier: ', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 12, + fontWeight: FontWeight.normal, + ), + ), Text( goodsReceipt.supplier.name, style: Theme.of(context).textTheme.bodyMedium!.copyWith( @@ -93,26 +94,21 @@ class DeliveryAdviceCard extends StatelessWidget { Row( children: [ Text( - 'Site Name: ${goodsReceipt.site}', + 'Site: ', style: Theme.of(context).textTheme.bodyMedium!.copyWith( fontSize: 12, fontWeight: FontWeight.normal, ), ), - const Spacer(), - Container( - decoration: BoxDecoration( - color: buttonProperties.color, - borderRadius: BorderRadius.circular(8), - ), - child: TextButton( - onPressed: null, - child: Text( - buttonProperties.text, - style: const TextStyle(color: Colors.white), - ), - ), + Text( + goodsReceipt.site.name, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 12, + fontWeight: FontWeight.bold, + ), ), + const Spacer(), + _statusDisplay(context, goodsReceipt.status), ], ), ], @@ -122,4 +118,54 @@ class DeliveryAdviceCard extends StatelessWidget { ), ); } + + Container _statusDisplay(context, GoodsReceiptStatus status) { + switch (status) { + case GoodsReceiptStatus.pendingShipping: + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: kViewPendingColor.withOpacity(0.9), + ), + padding: const EdgeInsets.all(8), + child: Text( + 'Pending Confirmation', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ); + case GoodsReceiptStatus.received: + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: kApprovedAndOrderCompletedColor.withOpacity(0.2), + ), + padding: const EdgeInsets.all(8), + child: Text( + 'Received', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ); + default: + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: kViewPendingColor, + ), + child: Text( + 'Error', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 12, + fontWeight: FontWeight.bold, + color: kDeclined, + ), + ), + ); + } + } } diff --git a/flutter_client/lib/widgets/my_orders_card.dart b/flutter_client/lib/widgets/my_orders_card.dart index c37be00..3d95d1a 100644 --- a/flutter_client/lib/widgets/my_orders_card.dart +++ b/flutter_client/lib/widgets/my_orders_card.dart @@ -1,70 +1,118 @@ import 'package:flutter/material.dart'; -import 'package:flutter_client/models/user_model.dart'; -import 'package:flutter_client/screens/selected_products_screen.dart'; +import 'package:flutter_client/constants.dart'; +import 'package:flutter_client/models/order.dart'; +import 'package:flutter_client/screens/my_order_details.dart'; -class MyOrdersCard extends StatelessWidget { - const MyOrdersCard({required this.supplier, super.key}); - final User supplier; +class OrderButtonProperties { + final Color color; + final String text; + + OrderButtonProperties(this.color, this.text); +} +class MyOrdersCard extends StatelessWidget { + const MyOrdersCard({required this.order, super.key}); + final Order order; @override Widget build(BuildContext context) { - void ontapHandler() { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => SelectedProductsScreen( - supplier: supplier, - ), - ), - ); + OrderButtonProperties getButtonProperties(String state) { + switch (state) { + case 'pending': + return OrderButtonProperties(kViewPendingColor, 'Pending'); + case 'approved': + return OrderButtonProperties( + kApprovedAndOrderCompletedColor, 'Approved'); + case 'declined': + return OrderButtonProperties(kDeclined, 'Declined'); + case 'placed': + return OrderButtonProperties(kPlaced, 'Placed'); + case 'partially delivered': + return OrderButtonProperties( + kPartiallyDelivered, 'Partially delivered'); + default: + return OrderButtonProperties( + kApprovedAndOrderCompletedColor, 'Completed'); + } } - return Card( - surfaceTintColor: Colors.white, - elevation: 5, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: ListTile( - selected: true, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.0), + return InkWell( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => MyOrderDetails( + order: order, + ), ), - title: Text( - supplier.name, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontWeight: FontWeight.w900, + ); + }, + child: Card( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + elevation: 8, + child: SizedBox( + width: double.infinity, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + order.orderId!, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + const Spacer(), + Text( + order.dateToBeDelivered.toString().split(' ')[0], + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ], ), - ), - subtitle: supplier.products.isNotEmpty - ? Column( + const SizedBox(height: 20), + Text( + order.supplierName!, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + Row( children: [ - ...supplier.products.map( - (product) => Row( - children: [ - Icon(Icons.circle, - size: 8.0, color: Colors.grey.shade500), - const SizedBox(width: 4.0), - Text( - product.title, - style: - Theme.of(context).textTheme.bodySmall!.copyWith( - fontWeight: FontWeight.normal, - color: Colors.black, - ), + Text( + 'LKR ${order.total!.toStringAsFixed(2)}', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 12, + fontWeight: FontWeight.normal, ), - const SizedBox(width: 16.0), - ], + ), + const Spacer(), + Container( + decoration: BoxDecoration( + color: getButtonProperties(order.status!).color, + borderRadius: BorderRadius.circular(8), + ), + child: TextButton( + onPressed: null, + child: Text( + getButtonProperties(order.status!).text, + style: const TextStyle(color: Colors.white), + ), ), ), ], - ) - : null, - trailing: Text( - supplier.contactNumber, - style: Theme.of(context).textTheme.bodySmall!.copyWith( - fontWeight: FontWeight.w900, ), + ], + ), ), - onTap: ontapHandler, ), ), ); diff --git a/flutter_client/lib/widgets/order_details_product_card.dart b/flutter_client/lib/widgets/order_details_product_card.dart new file mode 100644 index 0000000..791d946 --- /dev/null +++ b/flutter_client/lib/widgets/order_details_product_card.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_client/models/order_product.dart'; + +class OrderDetailsProductCart extends StatelessWidget { + const OrderDetailsProductCart({required this.orderProduct, super.key}); + final OrderProduct orderProduct; + + @override + Widget build(BuildContext context) { + return Card( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.zero), + ), + elevation: 8, + child: SizedBox( + width: double.infinity, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + orderProduct.title, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + const Spacer(), + Text( + "LKR ${orderProduct.price.toStringAsFixed(2)}", + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 20), + Text( + 'Quantity', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + Text( + orderProduct.quantity.toString(), + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 12, + fontWeight: FontWeight.normal, + ), + ), + ], + )), + ), + ); + } +} diff --git a/flutter_client/lib/widgets/supplier_details_card.dart b/flutter_client/lib/widgets/supplier_details_card.dart index 574c15f..2fa1265 100644 --- a/flutter_client/lib/widgets/supplier_details_card.dart +++ b/flutter_client/lib/widgets/supplier_details_card.dart @@ -12,6 +12,7 @@ class SupplierDetailsCard extends StatelessWidget { Widget build(BuildContext context) { void ontapHandler() { BlocProvider.of(context).supplier = supplier; + BlocProvider.of(context).add(const ClearCartEvent()); Navigator.of(context).push( MaterialPageRoute( builder: (context) => SelectedProductsScreen( @@ -47,9 +48,9 @@ class SupplierDetailsCard extends StatelessWidget { size: 8.0, color: Colors.grey.shade500), const SizedBox(width: 4.0), Text( - product.title.length < 20 + product.title.length < 16 ? product.title - : "${product.title.substring(0, 20)} ...", + : "${product.title.substring(0, 16)} ...", style: Theme.of(context).textTheme.bodySmall!.copyWith( fontWeight: FontWeight.normal, diff --git a/react_client/src/app/orders/page.tsx b/react_client/src/app/orders/page.tsx index 30e2b5d..f68fdae 100644 --- a/react_client/src/app/orders/page.tsx +++ b/react_client/src/app/orders/page.tsx @@ -1,4 +1,6 @@ "use client"; +import OrderStatus from "@/components/atoms/OrderStatus"; +import OrderView from "@/components/molecules/OrderView"; // import AddOrder from "@/components/organisms/OrderAdd"; // import OrderEdit from "@/components/organisms/OrderEdit"; import { OrderManagementContext } from "@/context/OrderManagement/OrderManagementContext"; @@ -25,43 +27,7 @@ const Orders = () => { columns={columns} loading={loading} expandable={{ - expandedRowRender: (record) => ( -
- {record.items.map((item) => { - return ( -
- Item: {item.item.name} -{" "} - {item.item.description} - {item.priceAtOrderTime} x{" "} - {item.quantity} = {item.priceAtOrderTime * item.quantity} -
-
- ); - })}{" "} - Total: {record.total} -
- Supplier: {record.supplier.name} -{" "} - {record.supplier.email} -
- Site Manager: {record.siteManager.name} -{" "} - {record.siteManager.email} -
- Site: {record.site.name} - {record.site.address} -
- Comments: {record.comments ?? "-"} -
- Date To Be Delivered: {record.dateToBeDelivered} -
- Status: {record.status} -
- Total: {record.total} -
- Created At:{" "} - {format(new Date(record.createdAt), "dd/MM/yyyy HH:mm:ss")} -
- Updated At:{" "} - {format(new Date(record.updatedAt), "dd/MM/yyyy HH:mm:ss")} -
- ), + expandedRowRender: (record) => , }} /> @@ -105,7 +71,7 @@ const columns = [ title: "Status", dataIndex: "status", key: "status", - render: (status: IOrder["status"]) => <>{status}, + render: (status: IOrder["status"]) => , }, // { // title: "Date To Be Delivered", diff --git a/react_client/src/app/page.tsx b/react_client/src/app/page.tsx index 9bfa167..5251f3e 100644 --- a/react_client/src/app/page.tsx +++ b/react_client/src/app/page.tsx @@ -9,10 +9,7 @@ export default function Home() { const { user } = useContext(AuthContext) as IAuthContext; return (
- {JSON.stringify(user)} - - - +

Welcome {user?.name}

); } diff --git a/react_client/src/app/supplier/deliveries/page.tsx b/react_client/src/app/supplier/deliveries/page.tsx index 56041fc..3f12fb7 100644 --- a/react_client/src/app/supplier/deliveries/page.tsx +++ b/react_client/src/app/supplier/deliveries/page.tsx @@ -1,4 +1,5 @@ "use client"; +import DeliveryStatus from "@/components/atoms/DeliveryStatus"; import OrderView from "@/components/organisms/OrderModal"; import { OrderDeliveryContext } from "@/context/OrderDelivery/OrderDeliveryContext"; import { OrderPlacementContext } from "@/context/OrderPlacement/OrderPlacementContext"; @@ -50,6 +51,9 @@ const Deliveries = () => { title: "Status", dataIndex: "status", key: "status", + render: (status: IGoodReceipt["status"]) => ( + + ), }, ]; return ( diff --git a/react_client/src/components/atoms/DeliveryStatus/index.tsx b/react_client/src/components/atoms/DeliveryStatus/index.tsx new file mode 100644 index 0000000..7940c18 --- /dev/null +++ b/react_client/src/components/atoms/DeliveryStatus/index.tsx @@ -0,0 +1,11 @@ +import { Tag } from "antd"; +import React from "react"; + +const DeliveryStatus = ({ status }: { status: IGoodReceipt["status"] }) => { + if (status === "pending-shipping") + return Shipping Pending; + + return Received; +}; + +export default DeliveryStatus; diff --git a/react_client/src/components/atoms/OrderStatus/index.tsx b/react_client/src/components/atoms/OrderStatus/index.tsx new file mode 100644 index 0000000..5eabc42 --- /dev/null +++ b/react_client/src/components/atoms/OrderStatus/index.tsx @@ -0,0 +1,15 @@ +import { Tag } from "antd"; +import React from "react"; + +const OrderStatus = ({ status }: { status: IOrder["status"] }) => { + if (status === "pending") return Pending; + if (status === "approved") return Approved; + if (status === "declined") return Declined; + if (status === "placed") return Placed; + if (status === "partially-shipped") + return Partially Shipped; + if (status === "shipped") return Shipped; + return Completed; +}; + +export default OrderStatus; diff --git a/react_client/src/components/molecules/HierarchyTable/index.tsx b/react_client/src/components/molecules/HierarchyTable/index.tsx index f73c6bc..97c172d 100644 --- a/react_client/src/components/molecules/HierarchyTable/index.tsx +++ b/react_client/src/components/molecules/HierarchyTable/index.tsx @@ -14,12 +14,12 @@ interface DataType { const columns: ColumnsType = [ { title: "Hierarchy Id", dataIndex: "hierarchyId", key: "hierarchyId" }, { - title: "Lower Bound Price", + title: "Lower Bound Price (Rs.)", dataIndex: "lowerBoundPrice", key: "lowerBoundPrice", }, { - title: "Upper Bound Price", + title: "Upper Bound Price (Rs.)", dataIndex: "upperBoundPrice", key: "upperBoundPrice", }, diff --git a/react_client/src/components/molecules/OrderView/index.tsx b/react_client/src/components/molecules/OrderView/index.tsx index 4d09893..d2abc75 100644 --- a/react_client/src/components/molecules/OrderView/index.tsx +++ b/react_client/src/components/molecules/OrderView/index.tsx @@ -1,6 +1,7 @@ import React from "react"; import OrderItemTable from "../OrderItemsTable"; import { format } from "date-fns"; +import OrderStatus from "@/components/atoms/OrderStatus"; interface OrderViewProps { order: IOrder; @@ -17,7 +18,9 @@ const OrderView = ({ order: order }: OrderViewProps) => {
Status: - {order.status} + + +
Supplier: diff --git a/react_client/src/components/organisms/Sider/index.tsx b/react_client/src/components/organisms/Sider/index.tsx index 07cf733..e7dd0cf 100644 --- a/react_client/src/components/organisms/Sider/index.tsx +++ b/react_client/src/components/organisms/Sider/index.tsx @@ -10,6 +10,7 @@ import { FileSyncOutlined, InfoCircleOutlined, InboxOutlined, + GiftOutlined, } from "@ant-design/icons"; import type { MenuProps } from "antd"; import { Layout, Menu, theme } from "antd"; @@ -127,19 +128,19 @@ const SidebarRoutes: MenuProps["items"] = [ }, { key: APP_ROUTES.DELIVERIES_SUPPLIER, - icon: , + icon: , // for supplier label: Deliveries, }, { key: APP_ROUTES.INVOICES_SUPPLIER, - icon: , + icon: , // for supplier label: Invoices, }, { key: APP_ROUTES.ORDER_HISTORY_SUPPLIER, - icon: , + icon: , // for supplier label: Order History, },